- Published on
Git rebase 후 강제푸시 충돌? reflog로 복구하기
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서로 다른 히스토리를 가진 브랜치에 git rebase를 적용한 뒤 --force 또는 --force-with-lease로 푸시하면, 팀원 로컬과 원격 브랜치 사이에 충돌과 혼란이 자주 발생합니다. 대표적인 증상은 다음과 같습니다.
- 원격에 있던 커밋이 “사라진 것처럼” 보인다
- PR의 커밋/리뷰 스레드가 뒤엉킨다
- 팀원이
git pull을 하자마자 대량의 충돌이 발생한다 git log에서 내가 작업한 커밋이 안 보인다
이 글은 “이미 강제푸시를 해버렸고, 뭔가 잘못된 것 같은데 되돌릴 수 있나?”에 대한 답을 git reflog 중심으로 제공합니다. 특히 로컬에서 커밋을 되살리고, 원격을 정상 상태로 복구하는 시나리오를 단계별로 다룹니다.
관련해서, rebase 후에도 강제푸시 없이 PR을 정리하는 방법은 아래 글에서 별도로 정리해두었습니다.
왜 rebase + 강제푸시가 사고로 이어지나
git rebase는 커밋을 “새로 만든다”에 가깝습니다. 즉, 같은 변경이라도 커밋 해시가 바뀌고, 기존 히스토리와 단절됩니다. 그 상태에서 원격 브랜치에 git push --force를 하면 원격 브랜치 포인터가 새 히스토리로 이동하면서, 원격에 있던 이전 커밋들은 브랜치에서 더 이상 참조되지 않습니다.
여기서 핵심은 “커밋이 실제로 즉시 삭제되는 건 아니지만, 브랜치가 가리키지 않게 되어 찾기 어려워진다”입니다. 이때 구명줄이 git reflog입니다.
reflog가 뭔가: 커밋이 아니라 ‘참조 이동 기록’
git reflog는 커밋 로그가 아니라, HEAD나 브랜치 참조가 어디에서 어디로 이동했는지의 이력을 로컬에 기록합니다. 그래서 다음 상황에서 특히 강력합니다.
- rebase 중에 꼬였을 때 rebase 이전
HEAD를 찾기 - reset을 잘못해서 커밋이 안 보일 때 이전 위치 찾기
- 강제푸시 직전 상태(로컬에서라도)를 복원하기
중요한 제약도 있습니다.
- reflog는 기본적으로 “로컬 저장소”에 있습니다. 즉, 강제푸시로 원격이 바뀌었더라도, 내 로컬 또는 누군가의 로컬에 reflog가 남아있다면 복구 가능성이 큽니다.
- 시간이 오래 지나 GC가 돌거나, reflog 만료 정책에 의해 기록이 사라지면 복구가 어려워집니다.
사고 유형별 복구 시나리오
아래는 현장에서 자주 만나는 3가지 사고를 기준으로 복구 절차를 정리합니다.
시나리오 A: rebase 하다가 커밋이 사라진 것처럼 보임(로컬)
증상:
git log에 내가 작업한 커밋이 없다- rebase를 하다 중간에 abort 했거나, reset을 했거나, 실수로 다른 브랜치로 이동했다
- 현재 상태 확인
git status
git branch --show-current
git log --oneline --decorate -n 20
- reflog에서 “사라지기 전” 위치 찾기
git reflog --date=iso
출력 예시는 대략 이런 형태입니다.
# 예시
# a1b2c3d HEAD@{0}: rebase (finish): returning to refs/heads/feature/login
# 9f8e7d6 HEAD@{1}: rebase (pick): add validation
# 1122334 HEAD@{2}: rebase (start): checkout main
# 5566778 HEAD@{3}: commit: WIP login api
여기서 HEAD@{3}가 rebase 이전에 내가 만들었던 커밋일 수 있습니다. 또는 rebase (start) 직전의 HEAD@{N}이 “rebase 전 브랜치 포인터”일 가능성이 큽니다.
- 안전하게 복구 브랜치 생성
바로 reset으로 덮어쓰기보다, 먼저 복구용 브랜치를 만들어두면 되돌리기 쉽습니다.
git switch -c recovery/feature-login 5566778
- 복구 브랜치에서 커밋 확인
git log --oneline --decorate -n 30
원하는 커밋이 보이면, 이제 기존 브랜치로 체리픽하거나 머지 전략을 선택합니다.
# 원래 브랜치로 돌아가서
git switch feature/login
# 필요한 커밋만 가져오기
git cherry-pick 5566778
시나리오 B: 강제푸시 후 원격 브랜치가 ‘이상한 상태’가 됨(원격 복구)
증상:
- 내가
git push --force를 했더니 원격 브랜치가 엉뚱한 커밋을 가리킨다 - PR에서 커밋이 대거 사라지거나 리뷰가 깨졌다
핵심은 “원격이 가리키던 이전 커밋 해시를 찾아서, 원격 브랜치를 그 지점으로 되돌리는 것”입니다.
1) 원격 상태와 내 로컬의 원격 추적 브랜치 확인
git fetch --all --prune
git log --oneline --decorate --graph -n 30 origin/feature/login
2) reflog에서 강제푸시 직전 커밋 찾기
강제푸시를 한 사람의 로컬 reflog가 가장 정확합니다.
git reflog --date=iso
push라는 단어가 reflog에 직접 찍히진 않지만, 강제푸시 전후로 rebase (finish) 또는 reset 같은 이벤트가 보일 가능성이 큽니다. 그 직전의 HEAD@{N}을 후보로 잡습니다.
3) 복구 브랜치로 검증 후 원격 복원
먼저 복구 브랜치를 만들어 검증합니다.
git switch -c recovery/remote-backup <복구대상커밋>
git log --oneline --decorate -n 20
문제가 없고 “원격을 이 커밋으로 되돌려야 한다”가 확실하면, 원격 브랜치를 해당 커밋으로 강제 업데이트합니다.
# 원격 브랜치(예: feature/login)를 복구 커밋으로 강제 푸시
# 반드시 팀에 공지하고, 가능하면 --force-with-lease 사용
git push --force-with-lease origin <복구대상커밋>:feature/login
--force-with-lease는 “내가 알고 있는 원격 상태에서만 강제 업데이트”하도록 안전장치를 제공합니다. 누군가 그 사이에 원격에 새 커밋을 올렸다면 푸시가 거절되어, 2차 사고를 줄입니다.
시나리오 C: 팀원이 강제푸시 이후 pull 했다가 로컬이 꼬임
증상:
- 팀원이
git pull후 충돌이 폭발 git status가 rebase 중 또는 merge 중으로 표시- 로컬 브랜치가 원격과 완전히 다른 히스토리
이 경우는 “팀원 로컬을 원격에 맞추는 것”이 목표입니다. 단, 팀원 로컬에만 있는 커밋이 있을 수 있으므로 먼저 백업 브랜치를 만듭니다.
- 로컬 변경 백업
git switch feature/login
git switch -c backup/feature-login-local
- 원격 최신 상태 가져오기
git fetch origin
- 로컬 브랜치를 원격과 동일하게 맞추기(주의)
# 로컬 브랜치를 원격과 동일하게 맞춤
# 로컬의 커밋/변경은 사라질 수 있으니 반드시 backup 브랜치를 만든 후 수행
git switch feature/login
git reset --hard origin/feature/login
- backup 브랜치에서 필요한 커밋만 체리픽
git log --oneline backup/feature-login-local -n 20
git cherry-pick <필요한커밋해시>
reflog로 찾은 커밋이 “dangling”일 때: 브랜치로 붙잡기
reflog에서 커밋 해시를 찾았는데, 그 커밋이 어떤 브랜치에도 연결되지 않으면 시간이 지나면서 정리될 수 있습니다. 이런 경우는 일단 브랜치나 태그를 만들어 “참조”를 걸어두는 게 중요합니다.
# 브랜치로 고정
git branch rescue/dangling <커밋해시>
# 또는 태그로 고정
git tag rescue-2026-02-24 <커밋해시>
이렇게 해두면 이후에 안전하게 비교/이관 작업을 할 수 있습니다.
재발 방지 체크리스트: 강제푸시가 불가피할 때의 최소 안전장치
강제푸시가 무조건 나쁜 건 아니지만, “팀 브랜치”에서 아무 준비 없이 하면 사고 확률이 높습니다.
1) --force 대신 --force-with-lease 사용
git push --force-with-lease origin feature/login
- 원격이 내가 마지막으로 본 상태에서 바뀌었으면 푸시를 막아줍니다.
- 협업 중 2차 덮어쓰기 사고를 크게 줄입니다.
2) 푸시 전에 원격 백업 브랜치 만들기
원격 브랜치가 중요한 경우, 강제푸시 전에 “원격 백업 포인터”를 하나 만들어두면 복구가 매우 쉬워집니다.
# 현재 원격 feature/login을 backup 브랜치로 복제(로컬에서)
git fetch origin
git branch backup/feature-login origin/feature/login
git push origin backup/feature-login
3) PR 정리는 “강제푸시 없는 전략”도 고려
리뷰가 붙은 PR은 강제푸시로 커밋 해시가 바뀌면 리뷰 컨텍스트가 깨질 수 있습니다. 가능한 경우 아래 글의 방법처럼 강제푸시 없이 정리하는 전략을 고려하세요.
4) 모노레포/서브모듈 환경에서는 더 보수적으로
서브모듈 또는 서브트리, 모노레포 운영에서는 브랜치 히스토리 변경이 다른 팀/파이프라인에 연쇄 영향을 주기 쉽습니다. 관련 함정은 아래 글도 참고할 만합니다.
실전: “강제푸시로 망쳤다”에서 복구까지 10분 루틴
아래 루틴은 가장 흔한 사고(내가 rebase 후 강제푸시, 그리고 뭔가 이상함)에서 빠르게 복구 방향을 잡는 순서입니다.
- 즉시 멈추고, 추가 푸시 금지(상태를 더 덮지 않기)
git fetch --all --prune로 원격 상태 동기화git reflog --date=iso로 rebase 전후HEAD@{N}후보를 찾기git switch -c recovery/... <커밋>으로 복구 브랜치 생성git log --graph --decorate로 원하는 히스토리인지 검증- 원격을 되돌려야 하면
git push --force-with-lease origin <커밋>:<브랜치>로 복구 - 팀원들에게 “로컬 reset 가이드” 공유(backup 브랜치 만들고
reset --hard origin/...)
마무리
git rebase와 강제푸시는 히스토리를 깔끔하게 만들 수 있지만, 협업 브랜치에서 잘못 사용하면 커밋 유실처럼 보이는 사고가 쉽게 발생합니다. 다행히 대부분의 경우 git reflog가 “내가 어디에 있었는지”를 기록하고 있어 복구의 실마리를 제공합니다.
핵심은 두 가지입니다.
- reflog에서 찾은 커밋을 즉시 브랜치나 태그로 붙잡아 두기
- 원격 복구는 검증 브랜치로 확인한 뒤, 가능하면
--force-with-lease로 제한적으로 수행하기
이 원칙만 지켜도, rebase 후 강제푸시로 인한 대다수의 사고는 짧은 시간 안에 복구 가능합니다.