Published on

Git rebase 후 히스토리 꼬임 복구 - reflog

Authors

서로 다른 브랜치의 커밋을 깔끔하게 정리하려고 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입니다.

  1. reflog에서 되돌릴 지점(예: HEAD@{4} 또는 특정 SHA)을 고릅니다.

  2. 현재 브랜치를 그 지점으로 되돌립니다.

# 예: 리베이스 전 상태가 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/loginmain 위로 올리려고 git rebase main 수행
  • 충돌을 해결했는데 테스트가 깨지고 커밋도 일부 누락된 느낌

복구 절차

  1. 현재 상태 백업
git branch backup/before-reflog-fix
  1. reflog로 리베이스 전 커밋 찾기
git reflog

예를 들어 rebase (start) 직전의 HEAD@{7}이 정상으로 보이면,

  1. 그 시점으로 되돌리기
git reset --hard HEAD@{7}
  1. 올바른 방식으로 다시 시도
  • 다시 리베이스를 하되, 작은 단위로 확인합니다.
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 reflogHEAD/브랜치 포인터 이동 기록을 남기므로 복구가 가능합니다.
  • 가장 빠른 복구는 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}으로 돌아가는 게 최선인지 케이스별로 더 정확히 짚어드릴 수 있습니다.