- Published on
Git rebase 후 force push 충돌 복구 체크리스트
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서로 다른 사람이 같은 브랜치(또는 PR 브랜치)를 만지는 팀에서 git rebase 후 --force(혹은 --force-with-lease)로 푸시하면, 원격 브랜치의 기준 커밋이 바뀌면서 동료의 작업이 “사라진 것처럼 보이거나”, PR이 엉뚱한 커밋을 포함하거나, 머지 충돌이 연쇄적으로 발생하는 일이 흔합니다.
이 글은 “이미 force push가 나가버린 뒤”를 전제로, 손실을 최소화하고 빠르게 정상 상태로 되돌리는 복구 체크리스트를 제공합니다. 핵심은 두 가지입니다.
- 절대 덮어쓰지 말고 증거(커밋 포인터)를 먼저 확보한다.
- 원격/로컬/동료 브랜치의 “정상 기준점”을 찾고, 그 위에 필요한 커밋만 재적용(cherry-pick/rebase) 한다.
> 참고: CI가 꼬여서 캐시/빌드가 이상해 보일 때는 원인이 Git 히스토리 변경(캐시 키 변화)일 수도 있습니다. 관련해서는 GitHub Actions 캐시 안 먹힘 원인 7가지도 함께 보면 좋습니다.
0) 상황 분류: 지금 어떤 문제가 발생했나
복구 전략은 “어떤 형태로 꼬였는지”에 따라 달라집니다.
- A. 내 로컬에서 rebase 후 force push → 원격이 내 히스토리로 덮임
- 동료가 원격을 기준으로 작업 중이었다면, 동료 커밋이 원격에서 안 보이게 됨(실제로는 dangling 상태일 수 있음)
- B. 동료가 force push → 내 로컬이 원격과 완전히 diverge
git pull이 fast-forward 불가, 충돌/중복 커밋 발생
- C. PR 브랜치에 rebase+force push → 리뷰 코멘트가 옛 커밋에 달림
- GitHub UI상 “Outdated”가 늘고, 커밋 비교가 난해해짐
- D. merge commit이 있는 브랜치를 rebase → 히스토리 재작성으로 충돌 폭발
rebase --rebase-merges필요 가능
아래 체크리스트는 A~D를 포괄하되, 가장 치명적인 데이터 손실(A) 기준으로 안전하게 진행합니다.
1) 1분 안전조치: 덮어쓰기 중단 + 백업 브랜치 만들기
1-1. 추가 force push 금지
팀에 즉시 공유합니다.
- “지금부터 해당 브랜치에 push 금지”
- 누가 마지막으로 force push 했는지, 언제 했는지 확인
1-2. 로컬 상태 백업 브랜치 생성
현재 로컬 HEAD가 무엇이든, 일단 포인터를 남깁니다.
# 작업 중인 브랜치에서
current=$(git rev-parse --abbrev-ref HEAD)
git branch backup/${current}-$(date +%Y%m%d-%H%M%S)
1-3. 원격 브랜치 상태도 별도 포인터로 보관
원격 추적 브랜치 포인터를 로컬 브랜치로 저장합니다.
git fetch --all --prune
git branch backup/origin-${current}-$(date +%Y%m%d-%H%M%S) origin/${current}
이 두 개만 해도 “최악의 경우”에서 돌아갈 수 있는 발판이 생깁니다.
2) 증거 수집: reflog로 “사라진 커밋” 찾기
force push로 원격에서 커밋이 안 보이더라도, 로컬/동료 로컬에는 남아 있을 가능성이 큽니다.
2-1. 내 로컬 reflog 확인
git reflog --date=iso
rebase 직전/직후, reset, pull 등의 이벤트가 시간순으로 나옵니다. 여기서 다음을 찾습니다.
- rebase 전 HEAD:
rebase (start)이전의 커밋 - force push 이전의 로컬 브랜치가 가리키던 커밋
원하는 커밋 해시를 찾았다면, 즉시 브랜치로 고정합니다.
# 예: abc1234가 잃어버린 기준 커밋이라면
git branch rescue/lost-abc1234 abc1234
2-2. 원격에 남아있는 흔적 확인 (GitHub)
GitHub는 가끔 “force push 이전 커밋”이 UI에서 접근 가능한 경우가 있습니다.
- PR의 “force-pushed” 이벤트
- PR의 이전 커밋 목록
- 브랜치 보호 규칙/감사 로그(조직 설정)
하지만 UI만 믿지 말고, 최종 복구는 커밋 해시 기반으로 진행하는 게 안전합니다.
3) 원인 확인: 왜 충돌/중복이 생겼나 (짧은 진단)
3-1. diverge 상태 확인
git fetch origin
git log --oneline --decorate --graph --left-right --cherry-pick \
HEAD...origin/${current}
- 왼쪽(
<)은 로컬에만 있는 커밋 - 오른쪽(
>)은 원격에만 있는 커밋 --cherry-pick은 패치가 동일한 커밋을 “중복처럼” 표시하는 데 도움
3-2. “중복 커밋”인지 “진짜 다른 변경”인지
rebase 후에는 커밋 해시가 바뀌므로, 동일 변경이 다른 해시로 존재할 수 있습니다.
# 특정 커밋이 같은 패치인지 확인
git show <commit>
git patch-id < <(git show <commit>)
(실전에서는 git log --cherry/--cherry-pick이 더 편합니다.)
4) 복구 전략 선택: 3가지 패턴 중 하나로 끝낸다
여기서부터는 목표를 명확히 합니다.
- 목표 1: 원격 브랜치를 “정상 기준점”으로 되돌린다
- 목표 2: 사라진 커밋을 다시 얹는다
- 목표 3: PR/CI를 가능한 덜 흔들며 정리한다
전략 A) “원격을 이전 상태로 되돌리기” (가장 직관적)
force push가 잘못 나갔고, 이전 원격 HEAD가 명확할 때.
- 이전 원격 HEAD(또는 정상 커밋) 해시를 찾습니다(예:
good1234). - 로컬에서 해당 커밋으로 브랜치를 맞춥니다.
--force-with-lease로 원격을 되돌립니다.
# good1234가 되돌릴 기준 커밋
git checkout ${current}
git reset --hard good1234
git push --force-with-lease origin ${current}
--force대신--force-with-lease를 쓰면, 내가 마지막으로 본 원격 상태에서 누군가 또 푸시했을 때 덮어쓰기를 막아줍니다.
전략 B) “원격은 유지, 내/동료 커밋을 다시 얹기” (협업 친화)
이미 원격에 새 커밋이 쌓였고, 되돌리면 더 큰 혼란이 예상될 때.
- 원격 최신을 기준으로 새 브랜치를 판다.
- 잃어버린 커밋들을 cherry-pick으로 선별 복구한다.
git fetch origin
git checkout -b restore/${current} origin/${current}
# 잃어버린 커밋들을 순서대로 적용
# (여러 개면 커밋 범위나 리스트를 사용)
git cherry-pick <c1> <c2> <c3>
# 충돌 시 해결 후
# git add ...
# git cherry-pick --continue
복구 브랜치를 PR로 올려서 리뷰/CI를 통과한 뒤, 최종적으로 원래 브랜치에 반영하는 방식이 안전합니다.
전략 C) “rebase를 다시 제대로 수행” (히스토리 정리 목적)
히스토리를 깔끔히 만들려다 꼬인 경우, 올바른 rebase 옵션으로 재정렬합니다.
- merge commit이 있으면
--rebase-merges고려 - 충돌이 반복되면
--rerere-autoupdate로 재충돌 비용 감소
git fetch origin
git checkout ${current}
# 원격 기준으로 재정렬
# merge 커밋을 유지하고 싶다면 --rebase-merges
# 반복 충돌 완화: --rerere-autoupdate
git rebase --rebase-merges --rerere-autoupdate origin/main
# 검증 후
# (가능하면 팀 합의 후) push
git push --force-with-lease origin ${current}
5) “사라진 커밋”이 원격 어디에도 없을 때: dangling 커밋 복구
동료가 아직 로컬에 커밋을 갖고 있다면, 가장 확실한 복구 경로입니다.
5-1. 동료에게 요청할 것
- 해당 브랜치에서
git reflog --date=iso출력 - “사라진 커밋”의 해시 또는 브랜치 포인터
- 가능하면 임시 브랜치로 푸시
# 동료 로컬에서
git branch rescue/from-alice <lost_commit>
git push origin rescue/from-alice
그 다음 내 쪽에서 해당 브랜치를 기준으로 cherry-pick/rebase 하면 됩니다.
5-2. 내 로컬에서 dangling 객체 스캔 (최후 수단)
# 도달 불가능한 커밋/객체를 찾아줌
git fsck --lost-found
# .git/lost-found/commit 아래에 커밋이 생길 수 있음
찾은 커밋 해시를 브랜치로 고정한 뒤 복구를 진행합니다.
6) 충돌을 “빨리” 끝내는 실전 팁
6-1. conflict 범위를 줄이는 재배치
cherry-pick/rebase는 커밋 단위라 충돌이 누적됩니다. 충돌이 큰 커밋은 쪼개는 게 낫습니다.
# 인터랙티브 rebase로 커밋 쪼개기
git rebase -i <base>
# pick -> edit 로 바꾸고
git reset HEAD^
# 파일 단위로 add 후 커밋을 여러 개로
git add -p
git commit -m "split: part 1"
git add -p
git commit -m "split: part 2"
git rebase --continue
6-2. rerere로 반복 충돌 자동화
같은 충돌을 여러 번 해결하는 상황이라면 rerere를 켜면 체감이 큽니다.
git config --global rerere.enabled true
git config --global rerere.autoupdate true
6-3. “내가 맞다/상대가 맞다” 빠른 선택
충돌 파일에서 한쪽을 통째로 선택해야 하는 경우.
# 현재 브랜치(ours)로 통일
git checkout --ours path/to/file
# 상대 브랜치(theirs)로 통일
git checkout --theirs path/to/file
git add path/to/file
7) 푸시 단계 체크리스트: 다시는 덮어쓰지 않기
7-1. force push는 무조건 with-lease
git push --force-with-lease origin ${current}
- 단, 여러 사람이 같은 브랜치에 푸시하는 환경이라면, 애초에 force push를 금지하는 브랜치 보호 규칙이 더 낫습니다.
7-2. 원격이 내가 아는 것과 같은지 확인
git fetch origin
git rev-parse HEAD
git rev-parse origin/${current}
7-3. PR/CI 관점의 검증
히스토리 재작성은 캐시 키/빌드 결과를 흔들 수 있습니다. CI가 이상하게 보이면 캐시 미스도 함께 의심하세요.
8) 팀 운영 체크리스트(재발 방지)
8-1. 브랜치 보호 규칙
main,release/*는 force push 금지- PR 브랜치도 팀 규칙으로 “리뷰 시작 이후에는 rebase 금지(또는 제한)”
8-2. rebase 정책 명문화
- 개인 브랜치: 자유롭게 rebase 가능
- 공유 브랜치: 원칙적으로 merge 또는 squash merge
- 예외적으로 rebase가 필요하면:
- 사전 공지
- 작업 중단 시간 합의
--force-with-lease만 허용
8-3. 복구용 커맨드 템플릿을 팀 위키에 고정
사고는 반복됩니다. 아래 템플릿만 있어도 복구 속도가 달라집니다.
# 1) 백업
b=$(git rev-parse --abbrev-ref HEAD)
git branch backup/$b-$(date +%Y%m%d-%H%M%S)
git fetch origin
git branch backup/origin-$b-$(date +%Y%m%d-%H%M%S) origin/$b
# 2) 증거
git reflog --date=iso | head -n 30
# 3) 비교
git log --oneline --graph --left-right --cherry-pick HEAD...origin/$b
9) 최종 점검: “복구 완료” 정의
복구가 끝났다고 말하려면, 최소한 아래를 만족해야 합니다.
- 원격 브랜치가 의도한 커밋 집합을 포함한다(누락/중복 없음)
- PR diff가 “의도한 변경”과 일치한다
- CI가 정상이며(필요 시 캐시 무효화 포함), 배포 파이프라인이 기대대로 동작한다
- 팀원이 각자 로컬에서 원격을 다시 추적할 수 있다
동료가 로컬을 원격에 맞추는 방법(안전한 쪽)을 함께 안내하면 마무리가 깔끔합니다.
# 로컬 변경이 없다는 전제에서, 원격을 기준으로 브랜치 재정렬
git fetch origin
git checkout ${current}
git reset --hard origin/${current}
rebase+force push 사고의 본질은 “커밋 해시가 바뀌는 재작성”과 “원격 포인터 덮어쓰기”가 결합된 것입니다. 그래서 복구도 결국 포인터를 백업하고(reflog/backup branch), 정상 기준점을 찾고, 필요한 커밋만 재적용하는 절차로 수렴합니다. 위 체크리스트를 팀 표준으로 만들어두면, 사고가 나도 10분 내로 통제 가능한 수준으로 떨어뜨릴 수 있습니다.