- Published on
Git rebase 후 히스토리 꼬임 복구 - reflog
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서로 다른 브랜치의 커밋을 깔끔하게 정리하려고 git rebase를 썼는데, 갑자기 커밋이 사라진 것처럼 보이거나 히스토리가 이상하게 꼬이는 경험은 흔합니다. 특히 인터랙티브 리베이스 중 drop/squash를 잘못 적용했거나, 리베이스 도중 충돌을 해결한 뒤 잘못된 커밋을 만들었거나, 리베이스된 브랜치를 원격에 강제 푸시하면서 팀원 브랜치까지 영향을 주는 경우가 대표적입니다.
이 글에서는 “망했다” 싶은 순간에 가장 먼저 확인해야 할 git reflog를 중심으로, 리베이스 전 상태로 되돌리거나 잃어버린 커밋을 복구하는 절차를 실전 관점에서 정리합니다.
왜 rebase 후에 커밋이 사라진 것처럼 보일까
rebase는 커밋을 “이동”시키는 게 아니라, 기존 커밋을 기반으로 새 커밋을 다시 만들어 히스토리를 재작성합니다. 그래서 리베이스 전 커밋들은 브랜치 포인터에서 더 이상 도달할 수 없게 되고, 로그에서 안 보이거나 “사라진 것처럼” 보입니다.
하지만 Git은 바로 지우지 않습니다.
- 브랜치/태그에서 도달 불가능해진 커밋도 객체로는 남아있습니다.
HEAD가 어디를 가리켰는지, 브랜치 포인터가 어디였는지에 대한 “이동 기록”이 reflog에 남습니다.
즉, reflog는 “내 로컬에서 일어난 참조 이동의 블랙박스”에 가깝고, 리베이스 복구의 핵심 도구입니다.
복구 전 안전장치: 현재 상태를 먼저 보존
복구를 시도하기 전에, 지금 상태를 잃지 않도록 “안전 브랜치”를 하나 만들어 두는 습관이 좋습니다.
# 현재 브랜치/HEAD 상태를 임시로 보존
git branch backup/recovery-$(date +%Y%m%d-%H%M%S)
# 작업 트리가 지저분하면 우선 스태시
git status
git stash -u
이렇게 해두면 복구 과정에서 실수해도 다시 돌아갈 지점이 생깁니다.
reflog로 되돌릴 지점 찾기
가장 먼저 확인할 것은 HEAD의 이동 기록입니다.
git reflog
출력은 대략 이런 형태입니다(예시).
1a2b3c4 HEAD@{0}: rebase (finish): returning to refs/heads/feature/login
9f8e7d6 HEAD@{1}: rebase (pick): add validation
7a6b5c4 HEAD@{2}: rebase (start): checkout main
2b3c4d5 HEAD@{3}: checkout: moving from feature/login to main
3c4d5e6 HEAD@{4}: commit: WIP: login flow
여기서 중요한 포인트:
HEAD@{n}는 “과거의 HEAD 위치”를 의미합니다.rebase (start)직전 또는checkout직전이 보통 “정상 상태”일 가능성이 큽니다.rebase (finish)가 찍혀도 결과가 원하는 게 아닐 수 있으니,start이전을 후보로 봅니다.
브랜치 reflog도 확인하기
HEAD reflog만으로 부족하면, 브랜치 자체의 reflog를 봅니다.
git reflog show feature/login
브랜치 포인터가 어디로 움직였는지 더 직접적으로 확인할 수 있습니다.
가장 빠른 복구: rebase 이전 커밋으로 hard reset
리베이스 직전 상태로 “브랜치 자체를 되돌리는” 가장 단순한 방법은 reset --hard입니다.
reflog에서 되돌릴 지점(예:
HEAD@{4}또는 특정 SHA)을 고릅니다.현재 브랜치를 그 지점으로 되돌립니다.
# 예: 리베이스 전 상태가 HEAD@{4}라고 판단
git reset --hard HEAD@{4}
- 장점: 빠르고 확실합니다.
- 주의: 작업 트리/인덱스가 해당 시점으로 강제로 바뀌므로, 미커밋 변경사항이 있으면 날아갑니다(그래서 앞에서
stash를 권장).
원격에 이미 강제 푸시했다면
로컬만 되돌리고 끝나는 게 아니라, 원격 브랜치도 되돌려야 할 수 있습니다. 이때는 무작정 --force보다 --force-with-lease가 안전합니다.
# 원격이 다른 사람에 의해 갱신되지 않았다는 전제에서만 안전하게 강제 푸시
git push --force-with-lease origin feature/login
--force-with-lease는 “내가 마지막으로 알고 있던 원격 상태”와 다르면 푸시를 거부해, 팀원 작업을 덮어쓰는 사고를 줄여줍니다.
커밋 일부만 잃어버린 경우: cherry-pick으로 복구
리베이스 후 브랜치 전체를 되돌리고 싶지는 않고, 특정 커밋만 다시 가져오고 싶을 때가 있습니다. 이때는 reflog에서 잃어버린 커밋 SHA를 찾아 cherry-pick 합니다.
# 잃어버린 커밋이 3c4d5e6 라고 가정
git cherry-pick 3c4d5e6
여러 커밋을 연속으로 복구하려면 범위를 지정할 수도 있습니다.
# A..B는 A 다음부터 B까지를 의미
# 예: a1b2c3d 다음 커밋부터 f6e5d4c까지 복구
git cherry-pick a1b2c3d..f6e5d4c
충돌이 나면 해결 후 아래로 진행합니다.
git add -A
git cherry-pick --continue
rebase 도중 꼬였을 때: abort/continue/skip
리베이스를 “끝낸 뒤” 복구도 가능하지만, “진행 중”이라면 더 쉬운 버튼들이 있습니다.
# 리베이스 시작 전 상태로 즉시 되돌림
git rebase --abort
# 충돌 해결 후 계속 진행
git rebase --continue
# 현재 커밋 적용을 포기하고 다음으로 넘어감
git rebase --skip
--abort는 특히 강력합니다. 리베이스로 히스토리를 재작성하는 과정 자체를 취소하고, 시작 전 상태로 돌아갑니다.
실전 시나리오: "main 위로 rebase" 하다가 feature가 망가짐
상황
feature/login에서 작업 중main이 많이 앞서서feature/login을main위로 올리려고git rebase main수행- 충돌을 해결했는데 테스트가 깨지고 커밋도 일부 누락된 느낌
복구 절차
- 현재 상태 백업
git branch backup/before-reflog-fix
- reflog로 리베이스 전 커밋 찾기
git reflog
예를 들어 rebase (start) 직전의 HEAD@{7}이 정상으로 보이면,
- 그 시점으로 되돌리기
git reset --hard HEAD@{7}
- 올바른 방식으로 다시 시도
- 다시 리베이스를 하되, 작은 단위로 확인합니다.
git rebase main
# 충돌 해결
# 테스트
# 필요하면 중간에 abort로 되돌아갈 수 있게 단계별로 진행
reflog가 만능은 아니다: 언제 못 살릴 수 있나
대부분의 로컬 사고는 reflog로 복구되지만, 다음 경우에는 난이도가 올라갑니다.
- 해당 커밋이 가비지 컬렉션으로 정리된 경우(
git gc가 공격적으로 수행되고 시간이 지난 경우) - 로컬이 아니라 “원격에서만” 발생한 참조 이동(원격 reflog는 서버 설정에 따라 접근 불가)
- 작업 PC를 바꾸었고, 그 PC에만 reflog 기록이 있던 경우
그래도 보통은 시간이 많이 지나지 않았다면 커밋 객체가 남아있을 가능성이 높습니다.
재발 방지 체크리스트
1) rebase 전에 임시 태그/브랜치로 스냅샷 만들기
# 리베이스 직전 스냅샷
git tag backup/pre-rebase-20260224
# 또는
git branch backup/pre-rebase
태그는 “움직이지 않는 포인터”라서, 복구 기준점으로 특히 좋습니다.
2) 공유 브랜치에서는 rebase를 신중히
이미 여러 사람이 pull해서 쓰는 브랜치를 rebase 후 강제 푸시하면, 팀원 로컬의 히스토리와 충돌합니다. 가능한 전략:
- 개인 브랜치에서만 rebase
- 공유 브랜치에서는
merge를 허용하거나, rebase가 필요하면 팀 합의 후 짧은 시간에 처리
3) 강제 푸시는 --force-with-lease 우선
git push --force-with-lease origin feature/login
디버깅 글을 자주 보는 팀이라면: "복구 루틴"을 문서화하자
운영 장애 대응에서 런북이 중요한 것처럼, Git 사고도 자주 나는 팀이라면 “rebase 사고 시 복구 루틴”을 짧게 정리해두는 게 효과가 큽니다. 예를 들어 Argo CD나 Kubernetes처럼 원인 추적이 필요한 영역도 결국은 표준 절차가 시간을 줄입니다.
- Git 히스토리가 꼬이는 문제는 코드 자체 버그와 달리, 복구 절차를 아는지가 시간을 좌우합니다.
- 비슷한 성격의 트러블슈팅 문화는 배포/운영에서도 그대로 도움이 됩니다. 예를 들어 Argo CD Sync 실패 - OutOfSync 무한 반복 해결처럼 “증상-원인-절차” 형태로 남겨두면, 다음 사고 때 훨씬 빨리 수습할 수 있습니다.
요약
git rebase는 커밋을 재작성하므로, 리베이스 전 커밋이 “사라진 것처럼” 보일 수 있습니다.- 하지만 로컬에서는
git reflog가HEAD/브랜치 포인터 이동 기록을 남기므로 복구가 가능합니다. - 가장 빠른 복구는
git reset --hard HEAD@{n}로 리베이스 전 지점으로 되돌리는 것입니다. - 일부 커밋만 복구하려면 reflog에서 SHA를 찾아
git cherry-pick을 사용합니다. - 원격에 강제 푸시가 필요하면
git push --force-with-lease로 사고를 줄입니다.
필요하면, 실제 git reflog 출력(민감정보 제거)과 현재 브랜치 구조(git log --oneline --decorate --graph --all)를 공유해주면, 어떤 HEAD@{n}으로 돌아가는 게 최선인지 케이스별로 더 정확히 짚어드릴 수 있습니다.