- Published on
Git rebase -i 충돌·커밋 꼬임 완전 복구 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
git rebase -i(interactive rebase)는 커밋 히스토리를 깔끔하게 정리하는 강력한 도구지만, 한 번 꼬이면 “내 커밋이 어디 갔지?”, “충돌을 해결했는데도 다음 커밋에서 또 충돌이 난다”, “rebase가 끝났는데 로그가 이상하다” 같은 상황이 쉽게 발생합니다. 특히 squash/fixup, 커밋 순서 변경, 여러 번의 충돌 해결이 겹치면 현재 브랜치가 원래 의도와 멀어지거나, 심지어 커밋이 사라진 것처럼 보일 수 있습니다.
이 글은 rebase -i 도중 충돌·커밋 꼬임이 발생했을 때 ‘완전 복구’하는 실전 가이드입니다. 핵심은 두 가지입니다.
- 절대 패닉하지 말고 reflog로 “마지막 정상 상태”를 찾는다
- 복구 후에는 안전장치(백업 브랜치/태그)와 올바른 재시도 절차로 재정렬한다
비슷한 맥락의 “완전 초기화/복구”가 필요한 상황으로 CI 캐시 꼬임을 다룬 글도 참고가 됩니다: GitLab CI 캐시 꼬임 - 빌드 완전 초기화 가이드
1) 먼저 확인: 지금 rebase가 진행 중인가?
rebase가 진행 중인지부터 확인해야 합니다. 진행 중이면 “되돌리기”와 “수습”의 선택지가 달라집니다.
# 방법 1) 상태 확인
git status
# 방법 2) .git 내부 상태(직접 확인)
ls .git | egrep 'rebase-apply|rebase-merge'
git status에You are currently rebasing가 보이면 진행 중입니다..git/rebase-merge또는.git/rebase-apply가 있으면 rebase 메타데이터가 남아 있습니다.
진행 중일 때 가능한 선택지 3가지
- 계속 진행: 충돌 해결 후
git rebase --continue - 한 단계 건너뛰기: 해당 커밋을 포기
git rebase --skip - 즉시 중단(원상복구):
git rebase --abort
여기서 많은 사람이 “일단 해결해보자”로 들어갔다가 더 꼬입니다. 커밋이 꼬였다고 느끼는 순간, 우선 abort 또는 reflog 기반 복구를 고려하세요.
2) 가장 안전한 응급조치: 현재 상태를 백업 브랜치로 고정
rebase 중이든 아니든, 현재 상태를 가리키는 포인터를 하나 만들어두면 복구 난이도가 급감합니다.
# 현재 HEAD를 backup 브랜치로 저장
git branch backup/rebase-mess-$(date +%Y%m%d-%H%M%S)
# 또는 태그로 고정
git tag rescue/rebase-mess-$(date +%Y%m%d-%H%M%S)
이 백업은 “지금 이 상태가 최악이든 아니든” 상관없이 보험입니다.
3) 1차 복구: rebase를 깨끗하게 중단하고 원래로 돌아가기
rebase 도중이라면 가장 빠른 복구는 --abort입니다.
git rebase --abort
- 성공하면: rebase 시작 전의 브랜치 상태로 돌아갑니다.
- 실패하거나 이상하면: 이미 일부가 적용되었거나 rebase 메타데이터가 깨졌을 수 있습니다. 이때는 reflog로 갑니다.
--abort가 안 되거나 상태가 이상할 때(메타데이터 꼬임)
가끔 .git/rebase-merge가 남아 --abort가 오작동하거나, 반대로 rebase는 끝났는데 뭔가가 남아 status가 이상한 경우가 있습니다.
- 우선 현재 상태 백업(위 2절) 후,
- rebase 메타데이터를 제거하기 전에 reflog로 복구 지점부터 확보하는 게 안전합니다.
4) 최종 복구의 핵심: git reflog로 “정상 시점” 찾기
reflog는 로컬에서 HEAD가 이동한 기록을 남깁니다. rebase로 커밋이 재작성되어도, 이동 기록은 남기 때문에 사실상 타임머신입니다.
# 최근 HEAD 이동 기록 확인
git reflog --date=local -n 30
# 더 보기
git reflog --date=local | less
reflog에서 자주 보이는 키워드:
rebase (start): rebase 시작 지점rebase (pick)/rebase (squash): rebase 진행 중 적용된 커밋rebase (finish): rebase 종료checkout: 브랜치 이동reset: reset 수행
복구 목표 1: “rebase 시작 직전”으로 돌아가기
reflog에서 rebase (start) 바로 이전 항목이 보통 “원래 정상 상태”입니다. 예:
abc1234 HEAD@{7}: rebase (start): checkout main
9fd0e11 HEAD@{8}: checkout: moving from feature to main
이때 HEAD@{8} 또는 해당 커밋 해시로 돌아가면 됩니다.
# 안전하게 브랜치를 그 시점으로 되돌림
# (현재 브랜치에서 작업 중이면 먼저 backup 브랜치 만들어둘 것)
git reset --hard HEAD@{8}
복구 목표 2: “rebase가 끝났는데 결과가 이상” → rebase 전/후를 비교
rebase가 끝났지만 커밋이 누락된 듯 보이면, 보통은:
- 커밋이 다른 해시로 재작성되어 보이지 않는 것(정상)
drop/fixup/squash과정에서 의도치 않게 합쳐지거나 제거된 것- 충돌 해결 중 변경을 잘못 커밋/스테이징한 것
reflog에서 rebase (finish) 직전/직후를 각각 체크아웃해서 비교하면 원인이 빨리 드러납니다.
# rebase 전 상태로 임시 이동(Detached HEAD)
git checkout HEAD@{8}
# rebase 후 상태로 이동
git checkout HEAD@{1}
# 다시 원래 브랜치로
git checkout feature
5) 커밋이 “사라진 것처럼” 보일 때: 잃어버린 커밋 찾기
5.1) git log 대신 git log --all로 먼저 확인
rebase 후 커밋 해시가 바뀌면 기존 해시로는 안 보입니다. 모든 참조를 대상으로 확인하세요.
git log --oneline --decorate --graph --all | head -n 80
5.2) reflog에서 커밋 메시지로 검색
# reflog에서 특정 키워드 찾기
git reflog | grep -i 'my commit message'
5.3) 최후의 수단: dangling commit 탐색
rebase/ reset 후 참조가 끊긴 커밋이 “dangling”으로 남아있을 수 있습니다.
# 도달 불가능한 객체 확인 (많이 나올 수 있음)
git fsck --lost-found
# dangling commit만 보기
git fsck --lost-found | grep 'dangling commit'
# 찾은 커밋을 확인
git show <dangling_commit_hash>
# 브랜치로 복구
git branch rescue/dangling <dangling_commit_hash>
주의: git gc가 돌면 dangling 객체가 정리될 수 있으니, 복구가 끝나기 전에는 git gc를 수동으로 실행하지 마세요.
6) 충돌이 반복되며 꼬이는 대표 원인과 해결 패턴
6.1) 같은 파일에서 커밋 여러 개가 연속으로 바뀌면 충돌이 “연쇄”로 난다
rebase는 커밋을 하나씩 다시 적용합니다. 동일 파일/라인을 여러 커밋이 건드리면, 첫 충돌을 해결해도 다음 커밋에서 또 충돌이 날 수 있습니다.
해결 팁:
- 충돌 해결 후
git diff로 최종 의도 상태를 확인 - 필요하면 해당 커밋을
edit로 바꿔서 중간 상태를 정리
# rebase todo를 다시 열어 edit로 변경
git rebase -i --rebase-merges <base>
# 또는 진행 중이라면
git rebase --edit-todo
6.2) 충돌 해결 후 실수로 잘못 스테이징한 채 continue
git add .를 무심코 하면 의도치 않은 파일까지 포함될 수 있습니다.
권장 루틴:
# 충돌 파일 확인
git status
# 충돌 마커 확인
rg -n "<<<<<<|>>>>>>|======="
# 스테이징 전 변경 검토
git diff
# 필요한 파일만 add
git add path/to/file1 path/to/file2
# 계속
git rebase --continue
6.3) “커밋이 두 번 들어간 것 같다” (중복 커밋)
원인:
- rebase 중단 후 다시 rebase를 시도하면서, 이미 적용된 변경을 또 적용
- cherry-pick/rebase를 섞어 사용
해결:
- “정상 시점”으로 reset 후, 한 번만 rebase를 수행
- 중복 여부는
patch-id로 확인 가능
# 두 커밋이 내용상 동일한지 비교(해시가 달라도)
git show <commitA> | git patch-id --stable
git show <commitB> | git patch-id --stable
7) 복구 후 ‘올바르게’ rebase -i 다시 하는 절차(안전 버전)
복구해서 정상으로 돌아왔으면, 같은 실수를 줄이기 위해 아래 절차를 추천합니다.
7.1) 베이스와 범위를 명확히 잡기
보통 feature 브랜치를 main 위로 정리하려면:
# 최신 main 반영
git fetch origin
# feature 브랜치에서
BASE=$(git merge-base HEAD origin/main)
echo $BASE
# 그 BASE부터 인터랙티브 리베이스
git rebase -i $BASE
merge-base를 쓰면 “어디부터 rebase해야 하는지”가 명확해져서 범위를 잘못 잡는 사고가 줄어듭니다.
7.2) todo 리스트에서 자주 쓰는 패턴
- 커밋 메시지만 정리:
reword - 커밋 순서 변경: 라인 순서 이동
- 여러 커밋 합치기:
squash(메시지 합침) 또는fixup(메시지 버림) - 중간에 작업 멈추고 수정:
edit
7.3) 충돌이 나면 “최종 결과”를 기준으로 해결
rebase는 중간 커밋 상태를 재현하는 과정이지만, 실무에서는 종종 “최종 파일 상태”만 맞으면 되는 경우가 많습니다. 충돌을 해결할 때는 다음을 습관화하세요.
# 충돌 해결 후 테스트/빌드까지 확인
npm test
# 또는
pytest
# 통과하면 continue
git rebase --continue
8) 원격 브랜치까지 꼬였을 때: force push를 안전하게 하는 법
rebase는 히스토리를 재작성하므로, 이미 원격에 push한 브랜치를 rebase했다면 일반 push가 거부됩니다. 이때 무작정 --force는 위험합니다.
권장: --force-with-lease
git push --force-with-lease origin feature
- 내 로컬이 알고 있는 원격 HEAD와 실제 원격 HEAD가 같을 때만 강제 푸시합니다.
- 누군가 그 사이에 원격에 추가 커밋을 올렸다면 푸시가 실패하여 사고를 막아줍니다.
force push 전 체크리스트
# 내가 올릴 커밋/원격 커밋 비교
git fetch origin
git log --oneline --decorate --graph --left-right origin/feature...feature
- 왼쪽(
<)은 원격에만 있는 커밋 - 오른쪽(
>)은 로컬에만 있는 커밋
원격에만 있는 커밋이 있는데 force push를 하면 동료 커밋을 날릴 수 있으니, 그 경우는 먼저 합의하거나 다른 방식(merge, cherry-pick)으로 정리해야 합니다.
9) “완전 복구” 요약 런북(복붙용)
아래는 rebase -i가 꼬였을 때 가장 재현성 높은 복구 흐름입니다.
# 0) 현재 상태 백업
git branch backup/rebase-mess-$(date +%Y%m%d-%H%M%S)
# 1) rebase 진행 중이면 우선 중단 시도
git rebase --abort || true
# 2) reflog로 정상 시점 찾기
git reflog --date=local -n 30
# 3) 정상 시점으로 하드 리셋 (예: HEAD@{8})
git reset --hard HEAD@{8}
# 4) 잃어버린 커밋이 의심되면 전체 로그/FSCK 확인
git log --oneline --decorate --graph --all | head -n 80
# 필요 시
git fsck --lost-found | grep 'dangling commit' || true
# 5) 다시 안전하게 rebase 수행 (merge-base 기반)
git fetch origin
BASE=$(git merge-base HEAD origin/main)
git rebase -i $BASE
10) 자주 묻는 함정 Q&A
Q1. git rebase --abort 했는데도 일부 변경이 남아있다
- 워킹 트리에 untracked 파일/새 파일이 남아 있을 수 있습니다.
- 또는 rebase 중 충돌 해결 과정에서 수동으로 만든 파일이 남았을 수 있습니다.
# 추적되지 않은 파일까지 포함해 정리(주의)
git clean -nd # 미리보기
git clean -fd # 실제 삭제
Q2. rebase 후 커밋 해시가 바뀌어서 PR이 이상해졌다
- rebase는 커밋을 새로 쓰므로 해시는 바뀌는 게 정상입니다.
- 원격 브랜치를 업데이트하려면
--force-with-lease가 필요합니다.
Q3. merge 커밋이 있는 브랜치를 rebase -i 했더니 더 난장판이다
- 기본 rebase는 merge 커밋을 평평하게 만들 수 있습니다.
- merge 구조를 보존하려면
--rebase-merges옵션을 고려하세요.
git rebase -i --rebase-merges <base>
결론
git rebase -i가 꼬였을 때의 정답은 “감으로 계속 만지기”가 아니라, 백업 포인터 생성 → reflog로 정상 시점 특정 → reset으로 복구 → 안전한 절차로 재시도입니다. 특히 reflog를 중심으로 복구 루트를 잡으면, 커밋이 사라진 것처럼 보여도 대부분 되찾을 수 있습니다.
마지막으로, 팀 작업 브랜치에 대해 rebase 후 force push가 필요하다면 반드시 --force-with-lease와 비교 로그를 습관화하세요. 히스토리를 정리하는 능력은 곧 협업 안정성과 직결됩니다.