세 가지 "되돌리기"의 차이를 이해하기 전에
초보자가 Git에서 가장 많이 혼동하는 질문은 "어떻게 되돌리냐"이다. 그 혼란의 근원은 "되돌리기"라는 말이 실제로 세 가지 전혀 다른 연산을 가리키기 때문이다.
첫 번째: 브랜치 포인터 이동 — 내가 아직 공유하지 않은 커밋 히스토리를 다시 쓰고 싶다. 이때 쓰는 것이 reset이다. 두 번째: 새 커밋으로 과거 취소 — 이미 공유한 커밋의 변경 내용을 되돌리고 싶지만 히스토리는 지우고 싶지 않다. 이때 쓰는 것이 revert다. 세 번째: 파일 단위 복원 — 특정 파일을 커밋된 버전으로 되돌리고 싶다. 이때 쓰는 것이 restore(또는 구식 checkout -- <file>)다.
이 세 가지는 목적과 작동 범위가 다르므로, 상황에 맞는 도구를 고르는 것이 핵심이다.
git reset — 브랜치 포인터 이동
git reset은 현재 브랜치가 가리키는 커밋을 다른 커밋으로 옮긴다. "포인터 이동"이 본질이다.
reset에는 세 가지 모드가 있으며, 차이는 "스테이징 영역(index)과 작업 트리를 어떻게 처리하느냐"다.
--soft: 브랜치 포인터만 이동. 스테이징 영역과 작업 트리는 그대로 둔다. 커밋은 사라지지만 변경 내용은 스테이지된 상태로 남는다. 여러 커밋을 하나로 합치는 "squash" 작업에 유용하다.
--mixed(기본): 브랜치 포인터 이동 + 스테이징 영역 초기화. 작업 트리는 그대로. 변경 내용은 unstaged 파일로 남는다. git reset HEAD~1은 --mixed다.
--hard: 브랜치 포인터 이동 + 스테이징 영역 초기화 + 작업 트리 초기화. 변경 내용이 완전히 사라진다. --hard 이후에는 git reflog를 통해 원래 커밋 SHA를 찾아 복구하지 않는 한 되돌릴 방법이 없다.
git reset --soft HEAD~1 # 포인터만 이동, staged 유지 git reset --mixed HEAD~1 # 포인터 + index 초기화 (기본값) git reset --hard HEAD~1 # 포인터 + index + 작업트리 초기화 git reset --hard origin/main # 원격 기준으로 완전 초기화
--hard 옵션은 저장되지 않은 모든 변경사항을 영구적으로 삭제한다. 공유 브랜치에서 reset 후 force push하면 팀원의 히스토리와 충돌한다. 공유된 커밋에는 절대 reset을 사용하지 말 것.git reflog에 최대 90일간 남아 복구 가능하다.git revert — 새 커밋으로 과거를 안전하게 취소
git revert는 지정한 커밋의 변경 내용을 정반대로 적용하는 새 커밋을 만든다. 원본 커밋은 히스토리에 그대로 남는다.
revert의 결정적 장점은 히스토리를 다시 쓰지 않는다는 것이다. 이미 원격에 push했거나 팀원과 공유한 커밋을 취소해야 할 때 revert가 올바른 선택이다. 원본 커밋은 "이런 변경이 있었다"는 기록으로 남고, revert 커밋은 "이 변경을 취소했다"는 기록을 추가한다.
revert는 지정한 커밋 하나의 diff를 역전시킨다. 따라서 "A → B → C" 히스토리에서 B를 revert하면, B가 추가한 변경만 취소하는 커밋 D가 추가된다. A와 C의 변경 내용은 유지된다. 만약 B와 C가 같은 파일을 건드렸다면 충돌이 발생할 수 있다.
여러 커밋을 순서대로 revert할 때는 보통 최신 커밋부터 역순으로 진행해야 충돌을 최소화할 수 있다.
git revert HEAD # 가장 최근 커밋 취소 git revert <sha> # 특정 커밋 취소 (새 커밋 생성) git revert HEAD~3..HEAD # 최근 3개 커밋 각각 revert git revert --no-commit HEAD # 커밋 없이 index에만 적용 (묶기용)
git restore — 파일 단위 복원
git restore는 Git 2.23에서 도입된 명령으로, 작업 트리 또는 스테이징 영역의 파일을 지정한 버전으로 복원한다. 브랜치나 히스토리에는 영향을 주지 않는다.
이전에는 이 역할을 git checkout -- <파일>이 담당했다. checkout이 브랜치 전환과 파일 복원이라는 두 가지 기능을 함께 맡았던 것을 Git이 분리한 것이다. 현재도 두 명령 모두 동작하지만, 명시적 의도 전달을 위해 restore를 권장한다.
git restore <파일>은 작업 트리의 파일을 스테이징 영역(index) 기준으로 복원한다. git restore --staged <파일>은 스테이징 영역을 마지막 커밋 기준으로 되돌린다(unstage). git restore --source=HEAD~2 <파일>은 2커밋 전의 버전으로 파일을 복원한다.
restore는 커밋을 생성하지 않으며, 작업 중인 변경사항을 즉시 버릴 때 사용한다. restore로 작업 트리를 복원한 경우 해당 변경사항은 복구할 수 없다 — 스태시에 저장하지 않았다면.
git restore <파일> # 작업트리를 index 기준으로 복원 git restore --staged <파일> # unstage (index → HEAD 기준) git restore --staged --worktree <파일> # 둘 다 복원 git restore --source=HEAD~2 <파일> # 2커밋 전 버전으로 복원 git checkout -- <파일> # 구식 동등 명령
reset 시각화 — 포인터 이동과 고아 커밋
아래 애니메이션은 reset --hard HEAD~1의 동작과, 같은 상황에서 revert를 선택했을 때의 차이를 단계별로 보여준다.