Published on

Git rebase 후 강제푸시로 꼬인 PR 복구법

Authors

서로 다른 히스토리를 가진 브랜치에 git rebase를 적용한 뒤 --force로 푸시하면, PR의 커밋 그래프가 뒤집히거나(커밋이 사라진 것처럼 보임), 이미 리뷰된 커밋이 다시 나타나거나, 파일 변경이 중복/대량으로 표시되는 등 “PR 꼬임”이 자주 발생합니다. 특히 GitHub/GitLab은 PR을 “브랜치 간 커밋의 차이”로 계산하기 때문에, rebase로 커밋 SHA가 전부 바뀌면 같은 코드라도 완전히 다른 커밋으로 인식합니다.

이 글은 다음 상황을 전제로 합니다.

  • 내 브랜치에서 rebase를 수행했다
  • 원격에 git push --force 또는 git push --force-with-lease를 했다
  • PR이 이상해졌다(커밋이 사라짐, diff가 폭증, 이미 머지된 커밋이 다시 뜸, 리뷰 코멘트가 엉뚱한 곳에 달림)

아래는 “원인 파악 → 안전한 복구 루트 선택 → 재발 방지” 순서로 정리한 복구 플레이북입니다.

1) 먼저 해야 할 것: 원격 상태를 정확히 가져오기

복구의 시작은 “현재 원격 브랜치가 어디를 가리키는지”와 “강제푸시 이전 커밋이 무엇이었는지”를 확인하는 것입니다.

# 최신 원격 상태 동기화
git fetch --all --prune

# 내 브랜치와 원격 브랜치의 위치 확인
git status

git log --oneline --decorate --graph -n 30

# 강제푸시로 이동된 원격 브랜치의 HEAD 확인
git rev-parse origin/my-branch

여기서 핵심은 “강제푸시 이전의 커밋 SHA”를 찾는 것입니다. 로컬에는 흔히 흔적이 남아 있습니다.

reflog로 강제푸시 이전 커밋 찾기

reflog는 로컬에서 HEAD가 이동한 이력을 기록합니다. 강제푸시 전후로 rebase를 했다면, 대개 이전 커밋을 찾을 수 있습니다.

# 브랜치 이동/리베이스 흔적 확인
git reflog --date=iso | head -n 30

# 특정 브랜치의 reflog를 더 명확히 보고 싶다면
git reflog show my-branch --date=iso | head -n 30

reflog 출력에서 rebase (start) / rebase (finish) 또는 checkout 직전의 커밋을 찾고, “강제푸시 이전의 정상 커밋”을 후보로 잡습니다.

2) PR이 꼬이는 대표 원인 4가지

복구 방법은 원인별로 달라집니다. 아래 유형 중 어디에 해당하는지 먼저 분류하세요.

원인 A: rebase로 커밋 SHA가 전부 변경됨

  • 코드 변경은 같아 보이지만 PR 커밋이 “새 커밋”으로 다시 쌓임
  • 리뷰 코멘트가 “outdated”가 되거나 엉뚱한 라인에 붙음

이는 정상적인 현상에 가깝고, 해결은 “PR을 유지할지, 새 PR로 갈지”의 선택 문제입니다.

원인 B: base 브랜치가 바뀌었거나 잘못된 브랜치에 rebase함

예: main 기준 PR인데 실수로 develop에 rebase하거나, 반대로 develop PR인데 main을 기준으로 rebase해 diff가 폭증.

원인 C: merge commit이 섞인 브랜치를 rebase함

  • git merge main을 여러 번 했던 브랜치를 rebase하면 충돌 해결 과정에서 변경이 중복 반영되거나 커밋이 재배열되기 쉬움

원인 D: 강제푸시로 다른 사람의 커밋까지 덮어씀

  • 협업 브랜치에서 누군가가 푸시한 커밋을 내가 --force로 날려버림
  • 이 경우 복구는 “내 PR 복구”를 넘어 “팀 히스토리 복구”가 됩니다

3) 복구 루트 선택 가이드

PR 꼬임을 복구하는 방법은 크게 3가지입니다.

  1. 원격 브랜치를 강제푸시 이전 상태로 되돌리기(가장 확실, 단 팀 합의 필요)
  2. PR은 유지하되, 새 브랜치로 정리해서 교체(리뷰를 살리고 싶을 때)
  3. 새 PR을 만드는 쪽으로 전환(가장 단순, 리뷰 히스토리는 일부 포기)

아래에서 각각을 실전 명령어로 설명합니다.

4) 방법 1: 원격 브랜치를 “강제푸시 이전”으로 되돌리기

가장 깔끔한 복구는 원격 브랜치 포인터를 이전 커밋으로 되돌리는 것입니다. 단, 협업 중이라면 반드시 팀에 공지하고 진행하세요.

4-1) 되돌릴 커밋 SHA 확정

reflog에서 찾은 커밋을 예를 들어 abc1234라고 하겠습니다.

# 안전을 위해 복구용 태그를 먼저 만들어 둠
git tag backup/my-branch-before-fix origin/my-branch

git show --stat abc1234

4-2) 로컬 브랜치를 해당 커밋으로 이동 후 푸시

git checkout my-branch

# 로컬 브랜치를 정확히 그 커밋으로 맞춤
git reset --hard abc1234

# 원격 반영: 가능하면 --force-with-lease 사용
git push --force-with-lease origin my-branch
  • --force-with-lease는 “원격이 내가 마지막으로 본 상태에서 바뀌지 않았다면” 강제푸시를 허용합니다.
  • 협업 브랜치라면 --force 대신 이 옵션을 기본으로 쓰는 습관이 중요합니다.

이렇게 하면 PR도 대개 정상으로 돌아옵니다(원격 브랜치가 PR의 소스이므로).

5) 방법 2: PR은 유지, 새 브랜치로 정리해 교체하기

이미 PR에 리뷰/코멘트가 많이 달렸고, PR URL을 유지하고 싶다면 다음 전략이 유용합니다.

핵심 아이디어

  • 현재 PR의 소스 브랜치가 꼬였으면, 정상 베이스에서 새 브랜치를 만들어 변경분만 깔끔히 다시 올립니다.
  • 그 다음 PR의 소스 브랜치를 바꾸는 기능이 없다면(플랫폼에 따라 다름), 보통은 새 PR을 만들고 기존 PR을 닫되 링크로 연결합니다.

GitHub 기준으로는 “PR 브랜치 교체”가 제한적이어서, 실무에서는 새 PR로 가는 경우가 많습니다.

5-1) 올바른 base에서 새 브랜치 생성

git fetch origin

# PR의 base가 main이라고 가정
git checkout -b my-branch-clean origin/main

5-2) 변경분을 가져오는 2가지 방식

방식 A: cherry-pick으로 필요한 커밋만 선택

rebase 후 커밋이 뒤죽박죽이라면, 의미 있는 커밋만 골라 담는 게 안전합니다.

# 기존 꼬인 브랜치에서 가져올 커밋들을 확인
git log --oneline origin/my-branch

# 필요한 커밋만 순서대로 cherry-pick
git cherry-pick 111aaaa
git cherry-pick 222bbbb

# 충돌 시 해결 후
# git add ...
# git cherry-pick --continue

방식 B: merge-base 기준으로 변경분을 통째로 적용

커밋 단위가 아니라 “최종 코드 상태”만 안전히 옮기고 싶다면, diff를 패치로 떠서 적용할 수 있습니다.

# 두 브랜치의 공통 조상 찾기
BASE=$(git merge-base origin/main origin/my-branch)

# 공통 조상부터 my-branch까지의 변경을 패치로 생성
git diff "$BASE"..origin/my-branch > /tmp/my-change.patch

# 새 브랜치에서 패치 적용
git apply /tmp/my-change.patch

git status
git commit -am "Recreate changes on clean branch"

패치 방식은 “커밋 히스토리”를 보존하지 않지만, PR 꼬임을 빠르게 해소하는 데 강합니다.

5-3) 새 브랜치를 푸시하고 새 PR 생성

git push -u origin my-branch-clean

기존 PR에는 다음을 남겨 정리합니다.

  • 새 PR 링크
  • 기존 PR을 닫는 이유(히스토리 꼬임)
  • 리뷰어가 다시 봐야 하는 범위(변경 파일/핵심 기능)

6) 방법 3: 이미 머지된 PR이 꼬였을 때(리버트 vs 리셋)

이미 base 브랜치에 머지된 뒤에 문제가 발견됐다면, 선택지는 보통 둘입니다.

  • 공유 브랜치에서는 reset --hard 후 강제푸시를 지양
  • 대신 git revert로 되돌리는 커밋을 추가

6-1) revert로 안전하게 되돌리기

# 머지 커밋을 되돌릴 때는 -m 옵션이 필요할 수 있음
git log --oneline --decorate -n 20

# 예: 머지 커밋이 9f9f9f9 라면
git revert -m 1 9f9f9f9

git push origin main

revert는 히스토리를 보존하면서 “되돌림 커밋”을 추가하므로, 협업 환경에서 안전합니다.

7) PR diff 폭증(수천 줄 변경) 체크리스트

PR이 갑자기 수천 줄로 폭증하면 아래를 의심하세요.

  1. PR의 base 브랜치가 의도한 브랜치인지 확인(예: main vs develop)
  2. 내 브랜치가 base 최신을 포함하는지 확인
  3. 잘못된 rebase 대상(다른 기능 브랜치)을 기준으로 rebase했는지 확인

빠른 진단 명령어:

# 내 브랜치가 어느 커밋에서 갈라졌는지 확인
BASE=$(git merge-base origin/main origin/my-branch)

git show --oneline -s "$BASE"

# base 대비 변경 파일 수/라인 수를 대략 확인
git diff --stat origin/main...origin/my-branch

여기서 origin/main...origin/my-branch...(three-dot)는 “공통 조상 기준 비교”라 PR 관점과 유사합니다.

8) 강제푸시로 타인의 커밋을 덮어쓴 경우 복구

이 케이스는 우선순위가 다릅니다. “내 PR”보다 “팀 커밋 복원”이 먼저입니다.

8-1) 원격 브랜치의 잃어버린 커밋 찾기

다른 팀원이 로컬에 해당 커밋을 갖고 있을 확률이 높습니다. 그 팀원의 로컬에서:

git reflog show origin/my-branch
# 또는
git reflog

찾은 커밋 SHA를 공유받아 복원합니다.

8-2) 복원 브랜치 생성 후 PR로 복구

직접 원격 브랜치를 또 강제푸시하기보다, 복원 브랜치를 만들어 PR로 머지하는 방식이 안전합니다.

# 잃어버린 커밋이 deadbee 라고 가정
git checkout -b restore/my-branch deadbee

git push -u origin restore/my-branch

그 다음 restore 브랜치로 PR을 만들고, 원래 브랜치와의 차이를 검증한 뒤 머지합니다.

9) 재발 방지: 강제푸시를 “안전하게” 쓰는 규칙

강제푸시는 완전히 금지하기 어렵습니다. 대신 “사고를 줄이는 규칙”을 팀에 정해두면 PR 꼬임이 크게 줄어듭니다.

9-1) 기본은 --force-with-lease

git push --force-with-lease origin my-branch
  • 누군가가 원격 브랜치에 새 커밋을 올렸다면 푸시가 실패합니다.
  • 즉, “내가 모르는 변경을 덮어쓰는 사고”를 예방합니다.

9-2) rebase는 개인 브랜치에서만, 공유 브랜치에서는 금지

  • 개인 기능 브랜치: rebase 허용
  • 팀 공유 브랜치(예: develop, 릴리즈 브랜치): rebase 금지, 필요한 경우 merge 또는 PR로 정리

9-3) PR 올린 뒤 rebase가 필요하면, 리뷰 상태에 따라 전략 분기

  • 리뷰가 거의 없으면: rebase 후 --force-with-lease로 정리 가능
  • 리뷰가 많으면: 새 브랜치로 PR 새로 만들고 기존 PR은 링크로 연결

이 패턴은 캐시/상태가 꼬였을 때 “원인을 고립시키고 재생성”하는 접근과 유사합니다. 예를 들어 Next.js에서 RSC 캐시가 꼬일 때도, 캐시 키/경로를 명확히 분리해 재현성을 확보하는 게 핵심입니다. 관련해서는 Next.js 14 RSC 캐시 꼬임·stale 데이터 해결법도 함께 참고하면 문제를 구조적으로 보는 데 도움이 됩니다.

10) 실전 시나리오별 빠른 처방전

시나리오 1: rebase 후 강제푸시했더니 PR 커밋이 전부 새로 생김

  • 코드가 맞다면 “정상 동작”에 가깝습니다.
  • 리뷰가 많으면 새 PR 권장, 리뷰가 적으면 그대로 진행.

시나리오 2: PR diff가 폭증하고 base에 이미 있는 변경까지 포함됨

  • base 브랜치가 맞는지 확인
  • git merge-base로 공통 조상 확인
  • 필요하면 방법 2로 새 브랜치 재생성

시나리오 3: 다른 사람 커밋이 사라졌다고 함

  • 즉시 푸시 중단
  • 팀원 reflog로 잃어버린 커밋 SHA 찾기
  • 복원 브랜치로 PR 생성(직접 또 강제푸시로 덮지 않기)

11) 마무리: “되돌릴 수 있게” 작업하면 PR은 복구된다

PR 꼬임은 대부분 “히스토리 재작성”과 “원격 브랜치 강제 이동”이 결합될 때 발생합니다. 하지만 Git은 대개 흔적을 남깁니다.

  • git reflog로 이전 커밋을 찾고
  • 되돌릴지(원격 리셋), 재생성할지(새 브랜치/새 PR) 결정한 뒤
  • --force-with-lease로 사고를 줄이는 규칙을 적용하면

대부분의 PR 꼬임은 깔끔하게 복구할 수 있습니다.

추가로, 타입/상태가 “꼬였을 때” 원인을 분리해 재현성을 확보하는 접근은 다른 영역에서도 동일하게 통합니다. 예를 들어 TypeScript에서 좁히기가 깨지는 이슈도 원인을 분해해 조건을 명확히 해야 해결이 빠릅니다. 필요하면 TypeScript 5.5 좁히기 깨짐 - satisfies·in 해결도 함께 읽어보세요.