git reset --hard로 날린 커밋, reflog로 되찾기
reflog는 로컬 저장소에서 브랜치와 HEAD가 어디를 가리켰는지 기록합니다. 사라진 커밋을 찾아 브랜치로 살리는 방법과 기본 만료 기한(90일·30일), 복구가 안 되는 경우를 공식 문서 기준으로 정리했습니다.
git reset --hard HEAD~2를 실행한 뒤에야 그 두 커밋이 필요했다는 걸 깨닫는 경우가 있습니다. git log에는 더 이상 나오지 않지만, 대개 커밋 자체는 아직 저장소에 남아 있습니다. 그 위치를 알려주는 것이 reflog입니다.
reflog가 기록하는 것
Git 문서는 reflog를 “로컬 저장소에서 브랜치 끝과 다른 참조가 언제 갱신되었는지”를 기록하는 것으로 설명합니다. 커밋, reset, rebase, 브랜치 전환처럼 참조가 움직일 때마다 한 줄씩 쌓입니다. 특히 HEAD의 reflog에는 브랜치 전환도 기록됩니다.
git reflog는 git reflog show와 같고, 참조를 지정하지 않으면 HEAD의 기록을 보여줍니다. 각 항목은 HEAD@{n} 형식으로 가리킬 수 있습니다. HEAD@{2}는 “HEAD가 두 번 전 이동에서 있던 위치”이고, main@{yesterday}처럼 시간으로도 지정할 수 있습니다.
되찾는 순서
git reflog # reset 직전 항목을 찾는다
git branch rescue HEAD@{1} # 그 위치에 새 브랜치를 만든다
git branch <이름> <시작점>은 시작점에 커밋 ID나 reflog 표현을 받아 그 커밋을 가리키는 브랜치를 만듭니다. 현재 작업 트리는 건드리지 않으므로, 내용을 확인한 뒤 병합하거나 필요하면 그 커밋으로 reset하면 됩니다. reset 직후라면 git branch rescue ORIG_HEAD도 쓸 수 있습니다. git reset은 실행 전에 현재 브랜치 끝을 ORIG_HEAD에 저장하므로, 이 브랜치는 reset 전 위치를 가리킵니다.
언제까지 남아 있나
reflog 항목은 영원히 남지 않습니다. git gc가 내부적으로 git reflog expire를 실행할 때 기본값은 다음과 같습니다.
- 그 reflog를 가진 참조의 현재 끝에서 도달할 수 있는 항목:
gc.reflogExpire, 기본 90일 - 그 끝에서 도달할 수 없는 항목(reset이나 amend로 버려진 커밋 등):
gc.reflogExpireUnreachable, 기본 30일
reflog가 참조하는 객체는 git gc가 지우지 않습니다. 하지만 해당 reflog 항목이 만료되고 다른 참조에서도 도달할 수 없게 되면, 이미 gc.pruneExpire 기준(기본 2주)보다 오래된 객체는 같은 정리 과정에서 삭제될 수 있습니다. 2주는 객체 자체의 나이이고, reflog가 만료된 뒤 추가로 주어지는 유예 기간이 아닙니다. 만료·prune 설정을 바꾸거나 관련 명령을 직접 실행하면 삭제 시점이 더 앞당겨질 수 있으니, 커밋을 잃었다는 걸 알았다면 바로 복구하세요.
reflog로 안 되는 것
- 커밋하지 않은 변경: reflog는 참조의 이동을 기록할 뿐입니다.
reset --hard로 덮어쓴 작업 트리의 미커밋 내용은 reflog 항목이 되지 않습니다. - 다른 저장소의 기록: reflog는 각 로컬 저장소의 기록입니다. 날짜 표현
main@{yesterday}도 로컬main이 어제 어디였는지를 뜻합니다. - bare 저장소:
core.logAllRefUpdates는 작업 디렉터리가 있는 저장소에서는 기본true, bare 저장소에서는 기본false입니다. 이 경우 bare 저장소에는 reflog가 기본으로 생기지 않을 수 있습니다.
기본값은 설정으로 바뀔 수 있으므로, 실제 저장소에서는 git config --get gc.reflogExpire 등으로 확인하는 것이 좋습니다.