지운 Git 커밋과 브랜치 살리기 — reflog 복구 절차

5 분 소요

git reset --hard를 실행하고 나서야 대상 커밋을 잘못 골랐다는 사실을 깨닫습니다. 또는 git branch -D로 지운 브랜치에 아직 머지하지 않은 커밋이 있었다는 사실이 뒤늦게 떠오릅니다. git log를 아무리 뒤져도 사라진 커밋은 보이지 않습니다. 이 상황에서 가장 먼저 알아야 할 결론을 앞에 두겠습니다. 한 번이라도 커밋했던 내용이라면 대부분 복구할 수 있습니다. 커밋은 즉시 삭제되지 않고, Git이 남겨 둔 이동 기록으로 되짚어 갈 수 있기 때문입니다. 그 기록이 reflog입니다.

reflog — HEAD의 이동 기록 #

git log가 커밋의 부모 사슬을 보여 준다면, reflog는 전혀 다른 축의 기록입니다. 내 저장소에서 HEAD와 각 브랜치가 어디를 가리켰는지, 그 이동의 전 과정을 시간순으로 남깁니다. 커밋, 브랜치 전환, merge, rebase, reset까지 참조를 움직이는 모든 명령이 한 줄씩 기록됩니다.

reflog 확인
$ git reflog
2f8a1c3 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
9d4b7e5 HEAD@{1}: commit: feat: 주문 취소 API 추가
6c1f0a2 HEAD@{2}: commit: feat: 주문 조회 API 추가
2f8a1c3 HEAD@{3}: commit: feat: 주문 모델 추가

HEAD@{N} 표기는 “HEAD가 N번 이동하기 전의 지점"이라는 뜻입니다. HEAD@{0}이 현재이고 숫자가 커질수록 과거입니다. git log에서 사라진 커밋도 이 목록에는 해시와 함께 그대로 남아 있습니다.

횟수 대신 시간으로 조회할 수도 있습니다. 사고 시점을 시간으로 기억하고 있다면 이쪽이 빠릅니다.

시간 기준 reflog 조회
git reflog --date=iso                       # 각 항목에 시각 표시
git log -g --oneline 'HEAD@{1 hour ago}'    # 1시간 전 HEAD 기준 조회

git log -g는 reflog 항목을 log 형식으로 보여 주는 옵션으로, 커밋 메시지 전문을 함께 확인하며 찾을 때 유용합니다.

두 가지 성질을 기억해 두면 판단이 빨라집니다.

  • reflog는 내 저장소 전용입니다. 커밋처럼 push되거나 clone을 따라가지 않습니다. 복구는 사고가 난 그 컴퓨터에서만 가능합니다.
  • 보존 기간이 있습니다. 기본값으로 현재 히스토리에서 도달 가능한 항목은 90일, 도달 불가능해진 항목은 30일 뒤 정리 대상이 됩니다. 어제의 사고는 거의 확실히 복구되지만, 몇 달 전의 사고는 장담할 수 없습니다.

시나리오 1 — reset –hard로 날린 커밋 복구 #

방금 커밋 2개를 git reset --hard HEAD~2로 날린 상황입니다. 복구는 두 단계입니다. reflog에서 잃어버린 커밋의 해시를 찾고, 그 해시로 브랜치를 되돌립니다.

1단계 — 잃은 커밋 찾기
$ git reflog
2f8a1c3 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
9d4b7e5 HEAD@{1}: commit: feat: 주문 취소 API 추가
6c1f0a2 HEAD@{2}: commit: feat: 주문 조회 API 추가

reset 직전에 HEAD가 있던 곳, 즉 HEAD@{1}9d4b7e5가 잃어버린 마지막 커밋입니다. 복구 방법은 두 가지 중에서 고릅니다.

2단계 — 되돌리기
# 방법 A — 안전: 잃은 커밋에 브랜치를 먼저 걸어 확인
git branch rescue 9d4b7e5
git log rescue --oneline        # 내용 확인 후 merge 등으로 회수

# 방법 B — 직행: main을 그 지점으로 다시 이동
git reset --hard 9d4b7e5

내용 확인 없이 다시 reset --hard를 실행하는 것이 불안하다면 방법 A가 좋습니다. 브랜치는 커밋을 가리키는 포인터일 뿐이므로, rescue 브랜치를 걸어 두는 순간 그 커밋은 다시 도달 가능해지고 보존 기간 걱정에서도 벗어납니다.

시나리오 2 — 지운 브랜치 복구 #

머지하지 않은 브랜치를 git branch -D로 지운 경우입니다. 브랜치는 사라졌지만 그 끝에 있던 커밋은 남아 있습니다.

삭제 직후라면 가장 빠른 단서는 삭제 명령의 출력 자체입니다.

삭제 출력에 남은 해시
$ git branch -D feature-coupon
Deleted branch feature-coupon (was 4e7a9b1).

괄호 안의 4e7a9b1이 브랜치가 가리키던 마지막 커밋입니다. 이 해시로 브랜치를 다시 만들면 복구가 끝납니다.

브랜치 재생성
git branch feature-coupon 4e7a9b1

터미널을 이미 닫아 출력이 없다면 reflog에서 찾습니다. 지운 브랜치에서 마지막으로 작업했던 커밋 메시지나 브랜치 전환 기록을 단서로 삼습니다.

reflog에서 단서 찾기
$ git reflog | grep -n "feature-coupon\|쿠폰"
7: 4e7a9b1 HEAD@{6}: commit: feat: 쿠폰 만료 검증 추가
9: c3d5f80 HEAD@{8}: checkout: moving from main to feature-coupon

브랜치별 reflog가 남아 있는 동안은 git reflog show feature-coupon으로 그 브랜치의 이동 기록만 볼 수도 있습니다.

reflog로도 복구되지 않는 것 #

reflog는 만능이 아닙니다. 다음 세 가지는 reflog의 범위 밖입니다.

  • 커밋한 적 없는 수정git restore로 버린 작업 디렉터리의 수정은 Git 어디에도 기록된 적이 없으므로 복구할 수 없습니다. 되돌리기 명령의 위험 범위는 Git 기초 #5 되돌리기 — restore·reset·revert 구분에서 정리했습니다.
  • 보존 기간이 지난 커밋 — 도달 불가능 상태로 30일이 지나 가비지 컬렉션이 정리한 커밋은 되찾을 수 없습니다.
  • 다른 컴퓨터의 커밋 — reflog는 로컬 기록이므로, 동료의 저장소나 서버에서 벌어진 이동은 내 reflog에 없습니다.
노트
git stash drop으로 지운 stash는 reflog에 남지 않지만 복구 가능성이 있습니다. git fsck --unreachable | grep commit으로 어떤 브랜치에도 속하지 않은 커밋 목록을 뽑으면 drop된 stash가 커밋 형태로 나타나는 경우가 많습니다. 해시를 찾았다면 git stash apply <해시>로 되살립니다.

마무리 #

이번 글의 절차를 한 줄로 요약하면 이렇습니다. reflog에서 잃어버린 지점의 해시를 찾고, 그 해시에 브랜치를 걸거나 reset으로 돌아갑니다. 커밋과 브랜치 삭제 사고의 대부분은 이 두 단계로 끝납니다.

그리고 이 복구 수단의 전제는 하나뿐입니다. 커밋이 존재해야 한다는 것입니다. 커밋하지 않은 수정에는 reflog도 소용이 없으므로, 어중간한 시점이라도 커밋을 자주 만들어 두는 습관이 어떤 복구 기술보다 확실한 예방입니다. 작업 단위가 애매하면 나중에 정리하면 됩니다. 기록되지 않은 것만이 정말로 사라집니다.

X