git reflog: 잃어버린 커밋과 브랜치를 복구하는 방법
git reflog는 HEAD와 브랜치가 움직인 모든 기록을 담은 로컬 저장 로그입니다. 커밋, reset, checkout, rebase, merge 같은 작업마다 기록이 남습니다. 실수 직전의 항목(예: HEAD@{2})을 찾아 git reset --hard HEAD@{2}로 되돌리거나, git branch rescue HEAD@{2}로 복구용 브랜치를 만들면 됩니다.
git reflog는 참조 로그(reference log)입니다. Git은 HEAD와 브랜치 끝이 움직일 때마다 이를 기록합니다. 커밋을 하고, 다른 브랜치로 옮기고, reset, rebase, merge를 하면 그 작업 후 HEAD가 가리킨 해시가 로그에 한 줄 추가됩니다. 그래서 더 이상 어떤 브랜치도 가리키지 않는 커밋이라도 찾아서 되돌릴 수 있습니다.
꼭 알아 두어야 할 점은 reflog가 로컬이라는 것입니다. 여러분의 클론에만 있으며, 서버로 보내지지도 않고 git clone으로도 전달되지 않습니다. 그래도 자신의 저장소에서는 히스토리와 관련된 거의 모든 실수를 구해 줍니다.
git reflog 출력 읽는 법
git reflog
3c4d5e6 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
9a8b7c6 HEAD@{1}: commit: Добавить валидацию e-mail
1f2e3d4 HEAD@{2}: commit: Вынести конфиг в отдельный файл
3c4d5e6 (HEAD -> main) HEAD@{3}: checkout: moving from feature to main
7b6a5c4 (feature) HEAD@{4}: commit: Черновик формы входа
각 줄은 HEAD의 한 상태입니다. 가장 최근 것이 위에 있습니다.
- 해시 — 작업 후 HEAD가 가리킨 커밋;
- HEAD@{n} — "n번 움직이기 전 HEAD가 있던 곳". 완전한 참조이므로 어떤 명령에든 넘길 수 있습니다;
- 설명 — 작업의 종류(commit, reset, checkout, rebase, merge, pull, cherry-pick, commit (amend))와 세부 내용.
예시에서 "Вынести конфиг"와 "Добавить валидацию" 두 커밋을 했고, 그 뒤 reset HEAD~2로 브랜치를 되돌렸습니다. git log에서는 두 커밋이 보이지 않지만, reflog에는 그대로 남아 있습니다.
브랜치별 reflog
브랜치마다 자기 로그가 있습니다. 여러 브랜치를 오가며 작업했다면 이쪽이 더 짧고 유용합니다.
git reflog show feature
git reflog show origin/main # fetch할 때 원격 브랜치가 어떻게 바뀌었는지
시간으로 참조하기
번호 대신 시각을 쓸 수도 있습니다. Git은 그 시점에 참조가 어디를 가리켰는지 찾아 줍니다.
git show main@{yesterday}
git diff main@{"2 hours ago"} main
git log -g --since="1 day ago" --oneline
git log의 -g(--walk-reflogs) 옵션은 부모 연결 대신 reflog를 따라가며, 익숙한 형식과 필터를 쓸 수 있게 합니다. 날짜를 보려면 git reflog --date=iso가 편리합니다.
git reset --hard 이후 커밋 복구하기
가장 흔한 상황입니다. git reset --hard를 잘못된 커밋에 실행해서 작업이 "사라졌습니다". 사실 커밋은 어디에도 가지 않았고, 브랜치에서 보이지 않을 뿐입니다.
- 로그를 엽니다:
git reflog. reset: moving to ...줄 바로 앞을 찾습니다. 실수 이전 상태입니다. 위 예시에서는 해시9a8b7c6을 가진HEAD@{1}입니다.- 정말 원하는 것인지 확인합니다:
git log --oneline -5 HEAD@{1}. - 브랜치를 되돌립니다:
git reset --hard HEAD@{1}.
git reset --hard HEAD@{1}
HEAD is now at 9a8b7c6 Добавить валидацию e-mail
주의: 작업을 할 때마다 번호가 밀립니다. reset 후에는 방금의 HEAD@{1}이 HEAD@{2}가 됩니다. 그러므로 번호보다 해시를 복사하는 편이 안전합니다.
중요한 제한: reflog는 커밋된 내용만 기록합니다. reset --hard나 git checkout -- 파일로 지워진 커밋하지 않은 변경은 reflog로 되돌릴 수 없습니다. 파일을 인덱스에 올려 두었던 경우에는 아직 희망이 있으며, 이는 아래에서 설명합니다.
삭제된 브랜치 복구하기
이런 상황을 생각해 봅시다. git branch -D feature로 브랜치를 지웠는데, 그 안에 아직 push하지 않은 커밋이 있었습니다. 브랜치의 로그는 브랜치와 함께 지워졌지만, HEAD의 로그는 남아 있습니다. 여러분이 그 브랜치에서 작업할 때 HEAD가 그 커밋들을 거쳤기 때문입니다.
git reflog | grep feature
7b6a5c4 HEAD@{4}: commit: Черновик формы входа
3c4d5e6 HEAD@{3}: checkout: moving from feature to main
# 브랜치의 마지막 커밋은 그 브랜치를 떠나던 시점의 커밋
git branch feature 7b6a5c4
checkout: moving from feature to ... 줄을 찾으세요. 그 아래(목록에서는 더 뒤)의 해시가 브랜치의 마지막 상태입니다. 참고로 Git은 브랜치를 삭제할 때 해시를 출력합니다: Deleted branch feature (was 7b6a5c4). 터미널이 아직 열려 있다면 그 값을 쓰면 됩니다.
실패한 rebase 되돌리기
rebase는 커밋을 다시 씁니다. 과정에서 충돌을 잘못 해결했다면 결과가 원래보다 나빠질 수 있습니다. reflog에는 rebase를 시작하기 전 상태가 있습니다.
git reflog
e1f2a3b HEAD@{0}: rebase (finish): returning to refs/heads/feature
e1f2a3b HEAD@{1}: rebase (pick): Черновик формы входа
c4d5e6f HEAD@{2}: rebase (start): checkout main
7b6a5c4 HEAD@{3}: commit: Черновик формы входа
git reset --hard HEAD@{3}
rebase (start) 이전의 항목을 찾으세요. 더 짧은 방법도 있습니다. rebase, merge, reset 같은 위험한 작업 전에 Git은 이전 위치를 ORIG_HEAD 참조에 저장합니다. 그래서 rebase 직후라면 git reset --hard ORIG_HEAD가 바로 동작합니다. 다만 ORIG_HEAD는 다음 위험 작업에서 덮어써지지만, reflog는 전체 기록을 보관합니다.
git commit --amend 이전 커밋 되돌리기
--amend는 커밋을 고치는 것이 아니라, 그 자리에 새 커밋을 만듭니다. 예전 커밋은 reflog에 남습니다.
git reflog
5a6b7c8 HEAD@{0}: commit (amend): Добавить валидацию e-mail
9a8b7c6 HEAD@{1}: commit: Добавить валидацию e-mail
git diff HEAD@{1} HEAD # amend가 무엇을 바꿨는지
git reset --soft HEAD@{1} # 예전 커밋으로 돌아가고, amend의 변경은 인덱스에 남김
안전한 복구: reset 대신 브랜치 만들기
어떤 커밋이 필요한지 확신이 없다면 맹목적으로 reset --hard를 하지 마세요. 복구용 브랜치를 만드세요. 아무것도 깨지 않고, 찾은 커밋이 지워지지 않도록 보호합니다.
git branch rescue HEAD@{5}
git log --oneline rescue
git switch rescue
그다음 rescue의 커밋을 cherry-pick이나 merge로 가져오고, 모든 것이 제자리에 돌아왔을 때 rescue 브랜치를 지우세요.
reflog는 얼마나 보관되나
기록은 영원하지 않습니다. Git이 가끔 자동으로 실행하는 git gc가 기록을 정리합니다. 기간은 설정으로 정해집니다.
| 설정 | 기본값 | 의미 |
|---|---|---|
gc.reflogExpire | 90일 | 현재 브랜치에서 도달 가능한 커밋을 가리키는 기록의 보관 기간 |
gc.reflogExpireUnreachable | 30일 | 어떤 브랜치에서도 도달할 수 없는 커밋의 기록 보관 기간. 바로 "잃어버린" 커밋 |
gc.pruneExpire | 2주 | 아무것도 참조하지 않는 도달 불가능한 객체가 삭제되기까지의 기간 |
실무적인 결론: 잃어버린 커밋은 reflog를 통해 약 한 달 동안 확실히 접근할 수 있습니다. 복구가 끝나기 전에는 복구를 미루지 말고, git gc --prune=now나 git reflog expire --expire=now --all을 직접 실행하지 마세요.
reflog로도 안 되면: git fsck
reflog에 커밋이 없는 경우도 있습니다. 예를 들어 git stash가 만든 커밋을 git stash drop으로 지웠거나, 변경이 인덱스에만 있었던 경우입니다. 이때는 "떠다니는" 객체를 찾아봅니다.
git fsck --lost-found
dangling commit 2b3c4d5e6f...
dangling blob 8e9f0a1b2c...
git show 2b3c4d5e6f # 내용 보기
git branch recovered 2b3c4d5e6f
삭제된 stash에서 나온 떠다니는 커밋에는 "WIP on ..." 메시지가 있습니다. 떠다니는 blob은 한때 git add로 추가했지만 커밋하지 않은 파일의 내용입니다. 파일 이름은 없으므로 내용으로 알아봐야 하지만, reset --hard 후에도 작업을 구할 수 있는 경우가 있습니다. --lost-found 옵션은 이들을 .git/lost-found/에도 복사합니다.
reflog가 못 하는 일
- 컴퓨터 사이에서는 동작하지 않습니다. 동료가 force push로 서버의 커밋을 덮어썼다면, 그 커밋을 로컬에 가지고 있던 사람의 reflog를 찾아야 합니다. 예를 들어 덮어쓰기 전에 fetch를 한 사람이라면
git reflog show origin/main으로 확인할 수 있습니다. - 커밋하지 않은 것은 보지 못합니다. 그래서 위험한 작업 전의 가장 좋은 보험은 커밋이나
git stash입니다. - 역사를 영원히 보관하지 않습니다. 위의 기간이 적용되며, 새로 클론하면 로그는 비어 있습니다.
실습: 위험한 작업 전 보험 들기
reflog는 구해 주지만, 복구하려면 주의가 필요합니다. 비슷한 항목이 수십 개 있는 가운데 맞는 것을 찾아야 하기 때문입니다. 미리 표시를 해 두는 편이 훨씬 쉽습니다. rebase, 큰 reset, 히스토리 실험 전에 임시 브랜치나 태그를 만드세요.
git branch backup/before-rebase
git rebase -i main
# 뭔가 잘못됨
git reset --hard backup/before-rebase
# 잘 됨
git branch -D backup/before-rebase
명령 한 줄이면 되고, 해시를 찾아다닐 필요가 없습니다. reflog는 보험을 미리 들지 못했을 때를 위한 대비책으로 남겨 두세요.
원하는 항목을 빨리 찾는 법
긴 로그에서는 필터가 도움이 됩니다. 작업 종류나 커밋 메시지로 검색하고, 애매하면 내용을 직접 보세요.
git reflog | grep -E "reset|rebase \(start\)"
git log -g --grep="валидац" --oneline
git show --stat HEAD@{7}
git log -g에 --grep을 쓰면 reflog 속 커밋 메시지에서 찾고, git show --stat은 어떤 파일이 바뀌었는지 보여 줍니다. 메시지가 "wip"처럼 정보가 없더라도 파일 목록으로 찾을 수 있습니다.
정리
git reflog는 Git의 안전망입니다. 최근 몇 주 동안 여러분의 저장소에 커밋된 것은 모두 찾아서 되돌릴 수 있습니다. 절차는 항상 같습니다. 로그를 열고, 실수 직전의 항목을 찾고, git log나 git show로 확인한 뒤, 브랜치를 만들거나 reset --hard를 합니다. restore, reset, revert와 복구 방법은 Git 강의에서 실습으로 다룹니다.
자주 묻는 질문
git reflog와 git log는 무엇이 다른가요?
git log는 커밋의 부모 연결을 따라 브랜치의 히스토리를 보여 줍니다. git reflog는 저장소에서 HEAD와 브랜치가 어디로 움직였는지 보여 주며, 브랜치 히스토리에서 빠진 커밋도 포함합니다.
reflog가 GitHub나 GitLab에도 올라가나요?
아니요, reflog는 로컬 폴더 .git/logs에만 저장되며 push나 clone으로 전달되지 않습니다.
reflog를 비울 수 있나요?
네, git reflog expire --expire=now --all 후 git gc --prune=now를 실행하면 됩니다. 하지만 그 이후에는 잃어버린 커밋을 복구할 수 없습니다.
reflog는 실제로 어디에 있나요?
텍스트 파일 .git/logs/HEAD와 .git/logs/refs/heads/브랜치이름에 있으며, 움직임 하나당 한 줄씩 기록됩니다.
서버의 bare 저장소에서도 reflog가 동작하나요?
기본적으로 bare 저장소에서는 reflog가 꺼져 있으므로(core.logAllRefUpdates), 서버에서 이에 의존하면 안 됩니다.