Git에서 로컬 및 원격 커밋을 안전하게 취소하는 방법
Git으로 작업할 때 실수를 바로잡는 능력은 원활한 개발 워크플로우의 기본입니다. 너무 일찍 커밋했거나, 민감한 데이터를 실수로 포함했거나, 단순히 일련의 변경 사항을 되돌려야 하는 경우, 로컬 및 원격 작업을 안전하게 취소하는 방법을 이해하는 것이 중요합니다. 이 가이드에서는 버전 관리 정리의 주요 도구인 git reset, git revert, git reflog를 쉽게 이해할 수 있도록 설명합니다. 이 명령들을 마스터하면 귀중한 작업을 잃지 않고 변경 사항을 삭제하거나 되돌릴 수 있어 히스토리를 자신 있게 관리할 수 있습니다.
일반적으로 안전한 로컬 히스토리 수정과, 협업자에게 심각한 문제를 일으킬 수 있는 공유 원격 히스토리 재작성을 구분하는 것이 중요합니다. 두 시나리오 모두에 대한 가장 안전한 방법에 중점을 둘 것입니다.
변경 사항 되돌리기를 위한 핵심 도구 이해하기
Git은 제거하거나 수정하려는 커밋을 처리하기 위한 여러 메커니즘을 제공합니다. 도구 선택은 커밋이 공유 저장소로 푸시되었는지 여부와 커밋을 히스토리에서 완전히 삭제하고 싶은지 또는 해당 커밋의 효과를 무효화하는 새 커밋을 도입하고 싶은지에 따라 전적으로 달라집니다.
1. git reset: 로컬 히스토리 재작성
git reset은 로컬 커밋 히스토리를 조작하는 가장 강력하고 (잠재적으로 위험할 수 있는) 도구입니다. 현재 브랜치 포인터(HEAD)를 다른 커밋으로 이동시킵니다. git reset의 핵심적인 측면은 --soft, --mixed, --hard 옵션으로 제어되는 스테이징 영역 및 작업 디렉토리를 처리하는 방식입니다.
git reset 모드
| 모드 | HEAD에 대한 영향 | 스테이징 영역(인덱스)에 대한 영향 | 작업 디렉토리에 대한 영향 |
|---|---|---|---|
--soft |
HEAD 포인터를 이동합니다. | 변경 없음. | 변경 없음. |
--mixed (기본값) |
HEAD 포인터를 이동합니다. | 새 HEAD에 맞게 재설정됩니다. | 변경 없음. |
--hard |
HEAD 포인터를 이동합니다. | 새 HEAD에 맞게 재설정됩니다. | 새 HEAD에 맞게 재설정됩니다. (주의: 커밋되지 않은 변경 사항이 손실됩니다.) |
사용 사례: 로컬에서 커밋했지만 커밋해서는 안 된다는 것을 깨달았거나, 작업 디렉토리에서 변경 사항을 유지하면서 스테이징을 해제하고 싶을 때 git reset을 사용합니다.
git reset 실용 예제
A. 커밋 취소(변경 사항 스테이징 유지):
커밋했지만 푸시하기 전에 더 많은 파일을 추가해야 한다는 것을 깨달았다면 --soft를 사용합니다:
# HEAD를 한 커밋 뒤로 이동시키지만, 변경 사항은 스테이징된 상태로 유지합니다(다음 커밋에 추가할 준비).
git reset --soft HEAD~1
B. 스테이징 취소 및 변경 사항 로컬 유지:
커밋한 후 모든 스테이징을 취소하고 작업 디렉토리에서 파일 수정을 유지하고 싶을 때:
# HEAD를 이동시키고 변경 사항의 스테이징을 취소하지만, 작업 공간에서 파일 수정은 유지합니다.
git reset --mixed HEAD~1
# 또는 간단히:
git reset HEAD~1
C. 완전 삭제 (최근 커밋에 대해 위험):
마지막 커밋을 완전히 버리고 해당 커밋 이후의 모든 로컬 수정을 버리려면 (이전 커밋 상태로 되돌림):
# 경고: 이 작업은 지정된 커밋 이후의 모든 작업을 삭제합니다.
git reset --hard HEAD~1
⚠️ 모범 사례 경고: 공유 원격 저장소로 이미 푸시된 커밋에 대해서는
git reset --hard를 절대 사용하지 마십시오. 다른 사람이 해당 커밋을 기반으로 작업을 진행하지 않았다고 100% 확신하지 않는 한, 공유 히스토리를 재작성하는 것은 협업자에게 혼란을 야기합니다.
2. git revert: 푸시된 커밋 안전하게 취소하기
git revert는 이미 공개적으로 공유된 변경 사항을 취소하는 데 선호되는 방법입니다. 히스토리를 재작성하는 대신, git revert는 특정 이전 커밋이 도입한 변경 사항을 명시적으로 취소하는 새로운 커밋을 생성합니다. 히스토리는 그대로 유지되므로 협업에 친화적입니다.
사용 사례: 원격 서버에 이미 있는 변경 사항(예: 버그를 유발한 기능)을 되돌려야 할 때 git revert를 사용합니다.
git revert 실용 예제
커밋 해시 a1b2c3d4가 버그를 유발했다고 가정합니다. 해당 커밋의 효과를 취소하는 커밋을 생성하려면:
# a1b2c3d4에서 도입된 변경 사항을 되돌리는 새 커밋을 생성합니다.
git revert a1b2c3d4
# revert 커밋 메시지를 위해 편집기를 열고 싶지 않다면:
git revert -n a1b2c3d4
# 그런 다음 변경 사항을 수동으로 커밋합니다.
git commit -m "Revert: a1b2c3d4에서 도입된 문제 수정"
3. git reflog: 안전망
파괴적인 git reset --hard를 수행한 후 몇 시간의 작업이 삭제되었음을 깨달으면 어떻게 될까요? 이때 git reflog가 유용합니다. 참조 로그(reflog)는 로컬 저장소의 HEAD에 대한 모든 변경 사항(모든 커밋, 리셋, 병합, 체크아웃)을 추적합니다. 이것은 당신의 로컬 취소 히스토리입니다.
사용 사례: 공격적인 git reset으로 인해 손실된 커밋을 복구하거나 이전에 방문했던 임시 상태로 이동하는 경우.
git reflog로 보기 및 복구하기
먼저 HEAD 이동 기록을 확인합니다:
$ git reflog
a1b2c3d HEAD@{0}: reset: HEAD~2로 이동
4f5e6d7 HEAD@{1}: commit: 기능 X 완료
b8a9c0d HEAD@{2}: commit: 기능 X 구현 시작
...
실수로 4f5e6d7(이것은 HEAD@{1}이었음) 이전으로 리셋했다면 쉽게 복원할 수 있습니다:
# 파괴적인 작업 이전 상태로 돌아갑니다.
git reset --hard HEAD@{1}
팁:
git reflog항목은 일반적으로 로컬에서 90일 동안 보관됩니다.reset또는 브랜치 삭제와 관련된 작업에 대한 궁극적인 로컬 안전망입니다.
원격 변경 사항 취소 (강제 푸시)
이미 원격으로 푸시된 커밋을 제거하기 위해 git reset을 사용했다면, 로컬 히스토리는 원격 히스토리와 달라집니다. Git은 표준 git push가 비정상적(non-fast-forward) 업데이트를 포함하므로 이를 방지합니다.
원격 저장소를 새롭고 재작성된 로컬 히스토리와 동기화하려면 강제 푸시를 사용해야 합니다.
# --force 대신 더 안전한 대안으로 --force-with-lease를 사용합니다.
git push origin <branch-name> --force-with-lease
--force-with-lease를 사용하는 이유는 무엇인가요?
--force-with-lease는 무차별적인 --force 옵션보다 안전합니다. 이는 마지막으로 풀한 이후 다른 사람이 원격 브랜치에 새 커밋을 푸시하지 않았는지 확인합니다. 만약 그 사이에 다른 사람이 원격을 업데이트했다면, 푸시는 거부되어 의도치 않게 그들의 작업을 삭제하는 것을 방지합니다.
어떤 명령을 언제 사용해야 하는지에 대한 요약
올바른 도구를 선택하는 것은 커밋의 상태와 대상에 따라 달라집니다:
- 로컬, 푸시되지 않은 커밋: 스테이징을 조정하거나 히스토리를 완전히 삭제하려면
git reset(soft, mixed, 또는 hard)을 사용합니다. - 푸시되고 공유된 커밋: 공개 히스토리를 보존하기 위해 반대되는 커밋을 생성하려면
git revert를 사용합니다. - 실수로 히스토리를 잃은 경우:
git reflog를 사용하여 이전에 손실된HEAD상태를 찾고 복원합니다. - 원격 업데이트 강제: 푸시된 커밋에 대해
git reset을 사용하여 로컬에서 히스토리를 안전하게 재작성한 후에만git push --force-with-lease를 사용합니다.