The Git Recovery Guide: How to Undo Anything (Without Panic)
Git 복구 가이드: 무엇이든 되돌리는 법, 당황하지 않고
Git에서 커밋을 잃어버린 것처럼 보여도 실제로는 참조만 사라진 경우가 많습니다. reflog와 fsck로 커밋을 찾고, reset·revert·branch 명령으로 복구하는 상황별 절차와 복구가 불가능한 경우를 정리합니다.
- 주제
AI 요약
Git 명령을 잘못 실행해 작업이 사라진 것처럼 보여도, 커밋 자체가 즉시 삭제되는 경우는 드뭅니다. reset, 브랜치 삭제, rebase는 대개 커밋을 지우는 대신 브랜치나 HEAD 같은 참조를 다른 위치로 옮깁니다. 아무 참조도 가리키지 않는 커밋도 Git의 object database에 남아 있으며, garbage collection이 정리하기 전에는 복구할 여지가 있습니다.
복구의 기본 원리
가장 먼저 확인할 명령은 git reflog입니다. reflog는 현재 clone에서 HEAD와 브랜치가 이동한 기록을 보관합니다. 잘못된 reset 이전 위치나 삭제한 브랜치가 가리키던 커밋의 hash도 기록에 남습니다. git reflog --date=relative를 실행하면 HEAD@{2} 같은 항목을 상대 시간과 함께 확인할 수 있습니다. 원하는 hash를 찾은 뒤 git reset --hard <hash>로 HEAD를 되돌리거나, git branch recovered <hash>로 새 브랜치를 만들어 커밋에 다시 이름을 붙입니다.
reflog에서도 찾지 못하면 git fsck --unreachable | grep commit으로 도달할 수 없는 커밋을 검색합니다. 후보마다 git show <hash>를 실행해 내용을 확인한 다음 git branch recovered <hash>로 보존합니다. detached HEAD 상태에서 만든 커밋을 잃어버렸을 때도 같은 절차를 사용합니다.
작업 트리와 스테이징 복구
아직 커밋하지 않은 파일 하나의 수정 내용을 버리려면 git restore <file>을 사용합니다. 삭제했지만 삭제 내용을 아직 커밋하지 않았다면 같은 명령으로 HEAD의 파일을 되돌립니다. 스테이징만 취소하고 작업 내용은 남기려면 git restore --staged <file>을 실행합니다. 모든 미커밋 변경을 버리는 git restore .은 주의해야 합니다. 커밋되지 않은 작업은 reflog에도 기록되지 않으므로 이 명령으로 버린 내용은 Git이 복구해주지 않습니다.
마지막 커밋 메시지를 고치거나 파일을 빠뜨렸다면 git commit --amend를 사용합니다. 파일을 먼저 git add forgotten-file.js로 올린 뒤 git commit --amend --no-edit를 실행하면 기존 메시지를 유지하면서 마지막 커밋을 갱신합니다.
reset과 revert 구분
마지막 커밋을 취소하되 변경 내용을 계속 작업하려면 git reset --soft HEAD~1을 사용합니다. 커밋만 취소되고 변경 내용은 스테이징 상태로 남습니다. git reset --hard HEAD~1은 커밋과 변경 내용을 함께 버립니다. 이미 실행했다면 reflog에서 reset 이전 hash를 찾아 되돌릴 수 있지만, 실행 전에는 신중하게 확인해야 합니다.
이미 push했거나 다른 사람이 내려받은 커밋을 취소할 때는 reset 대신 git revert <commit-hash>를 사용합니다. revert는 기존 기록을 다시 쓰지 않고 변경 내용을 반대로 적용하는 새 커밋을 만듭니다. 로컬에서 혼자 작업하는 커밋에는 reset이 적합하고, 공유 브랜치에는 revert가 적합하다는 구분입니다.
브랜치, rebase, merge 복구
삭제한 브랜치는 라벨만 사라진 상태일 수 있습니다. reflog에서 마지막 커밋 hash를 찾아 git branch recovered-branch <hash>를 실행하면 브랜치를 다시 만들 수 있습니다. rebase를 망쳤다면 reflog에서 rebase (start)나 rebase (finish) 주변 기록을 찾아 rebase 이전 커밋으로 git reset --hard를 실행합니다.
잘못된 브랜치에 merge했고 아직 push하지 않았다면 git reset --hard ORIG_HEAD로 merge 직전 상태로 돌아갑니다. ORIG_HEAD는 merge 직전 위치를 Git이 저장하는 참조입니다. merge가 이미 공유된 뒤라면 reset 대신 git revert -m 1 <merge-commit-hash>를 사용합니다. -m 1은 보존할 기준 브랜치가 첫 번째 부모임을 지정합니다. merge를 revert한 뒤 같은 브랜치를 다시 merge하려면 먼저 revert 커밋을 다시 되돌려야 할 수 있습니다.
push와 stash를 잘못 다룬 경우
공유 브랜치의 push를 취소할 때는 revert 커밋을 만든 뒤 다시 push하는 방식이 안전합니다. 아무도 내려받지 않았고 기록을 다시 써야 한다면 좋은 커밋으로 git reset --hard <good-hash>를 실행한 뒤 git push --force-with-lease를 사용합니다. --force-with-lease는 그 사이 다른 사람이 push했다면 덮어쓰기를 거부합니다. 일반 --force는 동료의 작업을 조용히 덮어쓸 수 있으므로 피해야 합니다.
다른 사람이 force-push해 원격 기록을 덮어썼더라도 로컬 reflog에는 이전 커밋이 남아 있을 수 있습니다. 마지막 정상 커밋을 찾아 로컬 브랜치를 복구합니다. 삭제한 stash는 git fsck --unreachable | grep commit으로 후보를 찾고 git show <hash>로 확인한 뒤 git stash apply <hash>를 시도합니다.
복구가 안 되는 경우와 주의점
진행 중인 작업을 통째로 취소하려면 상황에 맞게 git merge --abort, git rebase --abort, git cherry-pick --abort를 사용합니다. 현재 작업을 건드리지 않고 예전 파일 내용을 확인하려면 git show <commit-hash>:<file>을 사용합니다. 특정 커밋의 파일을 작업 트리로 가져오려면 git restore --source=<commit-hash> <file>을 사용합니다.
git clean으로 삭제한 untracked 파일은 Git이 복구하지 못합니다. 실행 전 git clean -n으로 삭제 대상을 미리 확인해야 합니다. reflog 기록도 영구적이지 않습니다. 원문은 도달할 수 없는 커밋이 대략 30일, 도달 가능한 커밋이 대략 90일 뒤 만료될 수 있으며 git gc가 더 빨리 정리할 수 있다고 설명합니다. reflog는 clone마다 로컬에 저장되므로 새 clone에서는 다른 컴퓨터에서 발생한 실수를 복구하지 못합니다.
명령을 연달아 실행하기 전에 멈추고, 위험한 작업을 시도하기 전 git branch backup-before-i-do-something-scary처럼 백업 브랜치를 먼저 만드는 것이 안전합니다. 이 가이드의 절차는 대부분 원하는 커밋의 hash를 찾고, reset이나 branch 명령으로 다시 참조를 연결하는 흐름으로 정리됩니다.
원문: dev.to / 번역·요약: Trawling