Published on

Git rebase 후 강제푸시 없이 PR 살리는 법

Authors

서론부터 결론까지 한 줄로 요약하면 이렇습니다. rebase는 커밋을 “새로 만든다”(SHA 변경) → 기존 PR 브랜치와 히스토리가 갈라짐 → 보통은 --force-with-lease로 덮어씌우지만, 정책/권한/리스크 때문에 강제푸시가 막혀 있는 팀도 많습니다. 이 글에서는 강제푸시 없이도 PR을 유지하거나 새 PR로 안전하게 이어가는 방법들을 GitHub 중심으로 정리합니다.

> 참고로 빌드/테스트 파이프라인이 PR 단위로 캐시/아티팩트를 재사용하는 팀이라면, PR을 “살리는” 과정에서 CI가 다시 전부 도는 문제가 생길 수 있습니다. 이런 경우는 GitHub Actions 캐시 안 먹힘 원인 7가지도 같이 보면 좋습니다.

왜 rebase 후 PR이 망가지는가

git rebase는 단순히 커밋을 “옮기는” 게 아니라, 기존 커밋을 기반 위에서 다시 적용하며 새로운 커밋을 생성합니다.

  • 기존 커밋: A - B - C (각각 SHA: a1, b1, c1)
  • rebase 후 커밋: A - B' - C' (SHA: b2, c2)

이때 원격 PR 브랜치(예: origin/feature)에는 여전히 b1, c1이 있고, 로컬에는 b2, c2가 생깁니다.

  • 강제푸시를 하면: 원격 브랜치를 로컬 히스토리로 “재작성”
  • 강제푸시를 못 하면: 원격 브랜치의 히스토리를 유지한 채로, 새 히스토리를 다른 방식으로 반영해야 함

목표별 전략 지도

상황에 따라 최적해가 다릅니다.

  1. PR 번호/토론/리뷰 코멘트를 그대로 유지하고 싶다 → 가능하면 “원격 브랜치를 업데이트”해야 함(하지만 강제푸시 금지)
  2. PR은 새로 파도 된다. 대신 변경사항만 정확히 옮기고 싶다 → 새 브랜치/새 PR이 가장 안전
  3. 이미 rebase는 했고, 로컬에만 새 커밋이 있다. 원격 PR 브랜치에는 옛 커밋이 있다cherry-pick, merge --no-ff, “rebase를 되돌린 뒤 merge” 등 선택

이 글의 핵심은 1번을 “완벽히” 해결하기 어렵다는 점입니다. rebase 결과를 같은 브랜치에 반영하려면 원칙적으로 fast-forward가 불가능하기 때문입니다. 강제푸시 없이 PR을 “완전히 동일한 브랜치로 살리는” 건 제약이 큽니다. 대신 아래 패턴들로 리스크 없이 PR을 유지하거나, PR 컨텍스트를 최대한 보존할 수 있습니다.

방법 1) (가장 안전) 새 브랜치로 PR을 새로 만들고, 기존 PR은 연결만 남긴다

강제푸시가 금지된 조직에서 현실적으로 가장 많이 쓰는 방법입니다.

절차

  1. 현재 PR 브랜치(원격)를 기준으로 새 브랜치를 따고,
  2. rebase로 만들어진 “새 커밋들”을 cherry-pick으로 옮긴 뒤,
  3. 새 PR을 만들고 기존 PR에는 링크/설명을 남깁니다.
# 1) 최신 상태 동기화
git fetch origin

# 2) 기존 PR 브랜치에서 새 브랜치 생성
# 예: 기존 PR 브랜치가 feature/login
git switch -c feature/login-v2 origin/feature/login

# 3) rebase로 새로 만들어진 커밋들이 있는 로컬 브랜치로 이동
# 예: rebase 결과가 feature/login-rebased
git switch feature/login-rebased

# 4) 옮길 커밋 범위를 확인
git log --oneline --decorate --graph

# 5) 새 브랜치로 돌아가서 커밋들을 cherry-pick
git switch feature/login-v2

# 예: 커밋 3개를 연속으로 옮긴다면
git cherry-pick <oldest_sha>^..<newest_sha>

# 6) 푸시 후 새 PR
git push -u origin feature/login-v2

장점

  • 강제푸시 불필요
  • 브랜치 보호 규칙/감사 정책 준수
  • 기존 PR의 토론/리뷰는 남기고, 새 PR로 깔끔하게 진행 가능

단점

  • PR 번호가 바뀜(하지만 기존 PR에 “Superseded by #1234” 식으로 연결하면 관리 가능)

운영 팁

  • 기존 PR을 닫지 말고 “Draft”로 전환하거나, 제목에 [Superseded]를 붙여 추적성을 남기세요.
  • 새 PR 본문에 기존 PR 링크를 명시하고, 변경 요약/리스크/테스트 결과를 다시 정리하면 리뷰어 피로가 줄어듭니다.

방법 2) 기존 PR 브랜치에 rebase 결과를 “merge commit”으로 합쳐서 강제푸시를 피한다

핵심 아이디어는 이겁니다.

  • rebase 결과는 기존 브랜치와 공통 조상은 같지만 히스토리가 갈라진 다른 라인
  • 강제푸시 없이 원격 브랜치에 반영하려면 fast-forward가 아닌 merge로 합칠 수밖에 없음
  • 즉, “rebase된 브랜치”를 “기존 PR 브랜치”에 merge commit으로 합쳐 PR을 진행

절차

git fetch origin

# 기존 PR 브랜치 체크아웃 (원격 기준)
git switch feature/login

# rebase 결과 브랜치(로컬)에 있는 커밋을 merge로 합침
# --no-ff를 주면 merge commit이 만들어져 히스토리가 명확해짐
git merge --no-ff feature/login-rebased

# 충돌 해결 후
git push origin feature/login

장점

  • 기존 PR 브랜치(그리고 PR 번호)를 유지 가능
  • 강제푸시 불필요

단점(중요)

  • 히스토리가 “예쁘게” 선형이 되지 않습니다. rebase의 장점(선형 히스토리)이 사라질 수 있음
  • 경우에 따라 동일 변경이 중복 적용된 것처럼 보이거나(특히 부분적으로 이미 반영된 경우), 리뷰 diff가 커질 수 있음

언제 쓰면 좋은가

  • 팀이 “선형 히스토리 강제”가 아니고, merge commit을 허용할 때
  • PR 번호/토론을 반드시 유지해야 할 때

방법 3) rebase를 되돌려 fast-forward 가능 상태로 만든다(로컬에서만)

이미 로컬에서 rebase를 했지만, 원격 브랜치를 살리고 싶고 강제푸시가 금지라면, 로컬에서 rebase를 “없던 일”로 만들고(즉 원격과 동일한 커밋 그래프로 복귀), 그 다음엔 정상적인 방식으로 base 브랜치 변경을 따라가야 합니다.

여기서 포인트는 “rebase 결과를 원격에 반영”하는 게 아니라,

  • 로컬을 원격과 동일하게 되돌린 다음
  • base 브랜치 변경분은 merge 또는 pull --rebase가 아닌 다른 방식으로 반영

절차(로컬 브랜치 복구)

git fetch origin

# 현재 작업 브랜치가 feature/login 이라고 가정
# reflog로 rebase 이전 커밋을 찾는다
git reflog --date=local | head -n 20

# 예: rebase 시작 직전이 HEAD@{7} 이었다면
git reset --hard HEAD@{7}

# 원격과 동일한지 확인
git log --oneline --decorate --graph --max-count=20

이후 base 브랜치(main)의 변경을 가져오는 방식은 팀 정책에 맞춰 선택합니다.

  • merge 허용 팀: git merge origin/main
  • 선형 히스토리 강제 팀: 원칙적으로 rebase가 필요한데, 이 경우 결국 강제푸시가 필요해집니다. 즉 “강제푸시 없이 선형 유지”는 구조적으로 충돌합니다.

장점

  • PR 브랜치가 원격과 다시 일치하므로 혼란이 줄어듦

단점

  • rebase로 정리해둔 커밋 스택을 포기해야 함

방법 4) 커밋을 “패치”로 만들어 적용한다(복잡한 상황에서 유용)

cherry-pick이 충돌을 과도하게 만들거나, 커밋 범위가 애매할 때는 패치 기반이 더 명시적일 수 있습니다.

git fetch origin

# rebase된 브랜치에서 패치 생성
git switch feature/login-rebased

git format-patch origin/main..HEAD -o /tmp/patches

# 기존 PR 브랜치 기반의 새 브랜치에 적용
git switch -c feature/login-v2 origin/feature/login

git am /tmp/patches/*.patch

git push -u origin feature/login-v2
  • git am은 메일 패치 적용 방식이라 커밋 메시지/작성자/순서를 비교적 잘 보존합니다.

GitHub PR을 “살린 것처럼” 보이게 하는 실무 운영 팁

기술적으로 PR 번호를 유지하지 못하는 경우가 많아서, 운영으로 커버하는 방법이 중요합니다.

1) 기존 PR 코멘트/리뷰를 새 PR로 이관하는 체크리스트

  • 기존 PR Description을 새 PR에 복사하되, 맨 위에 다음을 추가
    • 기존 PR 링크
    • 왜 rebase/브랜치 재생성이 필요했는지
    • 변경사항 요약(리뷰어가 다시 전체를 읽지 않게)
    • 테스트 결과/재현 방법

2) CI 비용 최적화

PR이 새로 만들어지면 캐시 키가 달라져 캐시 미스가 날 수 있습니다. 캐시 키를 브랜치명/PR번호에 강하게 묶어두면 이런 일이 잦습니다. 캐시 설계가 흔들릴 때는 GitHub Actions 캐시 안 먹힘 원인 7가지를 참고해 키 전략을 점검하세요.

3) “왜 강제푸시가 금지인가”를 팀 합의로 문서화

강제푸시 금지는 보통 아래 이유 때문입니다.

  • 감사/추적성(누가 무엇을 언제 바꿨는지)
  • 리뷰 코멘트가 특정 커밋 SHA에 달라붙어 무효화되는 문제
  • 다른 사람이 같은 브랜치에서 작업 중일 때 히스토리 재작성으로 인한 혼란

이런 맥락은 장애 대응/재현성 관점과도 닿아 있습니다. 예를 들어 운영 장애에서 원인 추적이 중요한 것처럼(예: 리눅스 OOM Killer 로그로 원인 프로세스 찾기), 코드 변경 이력도 “관찰 가능성”이 중요합니다.

자주 겪는 케이스별 처방

케이스 A: rebase를 이미 했고, 원격 푸시가 거부된다(브랜치 보호)

  • 정답: 방법 1(새 브랜치/새 PR)
  • 차선: merge commit 허용이면 방법 2

케이스 B: PR 브랜치에 다른 사람이 커밋을 추가했다

  • 강제푸시 금지 환경에서 가장 위험한 상황
  • 권장: 원격 PR 브랜치를 기준으로 새 브랜치 생성 → 필요한 커밋만 cherry-pick → 새 PR

케이스 C: 리뷰 코멘트를 유지해야 한다(커밋 단위 코멘트가 많음)

  • 현실적으로 완벽 보존은 어렵습니다.
  • 차선책:
    • 기존 PR을 닫지 않고 유지
    • 새 PR에서 “이 코멘트는 기존 PR의 특정 라인에 있었고, 여기로 이동했다”를 요약
    • 가능하면 커밋을 크게 바꾸지 말고(파일 이동/대규모 포맷팅 최소화) diff 안정성을 확보

결론: “강제푸시 없이 rebase 결과를 같은 PR에 반영”은 대체로 불가능, 대신 안전한 우회로를 택하자

정리하면 다음 우선순위가 실전에서 가장 덜 아프게 갑니다.

  1. 새 브랜치 + 새 PR(cherry-pick 또는 patch)로 안전하게 이어가기
  2. PR 번호를 꼭 유지해야 하고 merge commit 허용이면, rebase 결과를 기존 PR 브랜치에 merge
  3. 로컬 rebase를 되돌려 원격과 맞춘 뒤, 팀 정책에 맞는 방식으로 base 변경을 반영

원한다면, 현재 상황을 몇 줄만 알려주면(예: GitHub/Bitbucket, 브랜치 보호 규칙, “merge commit 허용 여부”, 이미 원격에 푸시된 커밋 유무) 그 조건에서 가장 깔끔한 커맨드 시퀀스로 재구성해 드릴 수 있습니다.