Published on

Git rebase 중 duplicate patch 경고 해결법

Authors

서로 다른 브랜치에서 작업하다 보면 git rebase 중에 다음과 같은 경고를 마주칠 때가 있습니다.

  • warning: skipped previously applied commit ...
  • warning: ...: patch is empty
  • warning: duplicate patch ...

이 메시지는 대개 “지금 적용하려는 커밋의 변경 내용이, 이미 대상 브랜치 쪽 히스토리에 존재한다”는 신호입니다. 문제는 경고 자체보다도, 이를 무시했을 때 히스토리가 꼬이거나(중복 커밋, 의미 없는 커밋), 반대로 필요한 변경이 누락되는 상황이 생길 수 있다는 점입니다.

이 글에서는 duplicate patch 류 경고가 뜨는 대표 원인, 어떤 경우에 스킵해도 되는지, 어떤 경우에는 반드시 확인해야 하는지, 그리고 안전하게 정리하는 실전 커맨드 흐름을 정리합니다.

참고로 CI 환경에서 Git 권한 문제까지 겹치면 원인 파악이 더 어려워질 수 있는데, 그런 경우에는 GitHub Actions 403 권한 오류 해결 - GITHUB_TOKEN 글도 같이 보면 디버깅 시간이 줄어듭니다.

duplicate patch 경고가 의미하는 것

Git이 말하는 “패치(patch)”는 보통 커밋이 만들어낸 변경(diff)을 뜻합니다. rebase는 커밋을 하나씩 새로운 베이스 위에 “다시 적용”하는 과정인데, 적용하려는 커밋의 diff가 이미 베이스(혹은 그 이후 커밋들)에 존재하면 Git은 다음 중 하나로 판단합니다.

  • 이미 같은 변경이 들어가 있어서 적용할 게 없다
  • 적용하려고 했는데 결과가 빈 커밋이 됐다
  • 유사한 변경이 이미 적용된 것으로 보인다

이때 Git은 커밋을 스킵하거나(자동 혹은 안내), 혹은 사용자가 직접 액션을 선택하도록 합니다.

자주 보이는 메시지 패턴

  • skipped previously applied commit
    보통 --cherry-pick 관련 동작이나 --reapply-cherry-picks 옵션 여부에 따라 스킵 정책이 달라집니다.

  • patch is empty
    충돌 해결 과정에서 사용자가 이미 동일한 변경을 반영해버렸거나, 이전 커밋에서 같은 내용이 들어가 “이 커밋이 할 일이 없는” 상태가 된 경우입니다.

  • duplicate patch
    Git이 “이 패치가 이미 들어간 것 같다”고 판단했을 때 나타날 수 있습니다. 실제로는 완전히 동일하지 않아도 유사성 판단 때문에 뜰 수 있어 반드시 확인이 필요합니다.

원인 1: 같은 변경을 이미 다른 경로로 머지했다

가장 흔한 케이스입니다.

  • 기능 브랜치에서 A라는 커밋을 만들었다
  • 그 사이에 다른 사람이 같은 변경을 다른 커밋으로 main에 넣었다
  • 혹은 내가 이미 cherry-pick으로 main에 넣어둔 뒤, 다시 rebase를 시도했다

이 경우 rebase가 A를 적용하려고 보니, 결과가 이미 반영된 상태라 스킵 경고가 뜹니다.

확인 방법

먼저 “정말 같은 변경이 이미 들어갔는지”를 diff로 확인합니다.

# rebase 대상(예: origin/main)과 현재 브랜치의 차이를 요약
git fetch origin
git log --oneline --decorate --graph origin/main..HEAD

# 특정 커밋의 패치 내용을 확인
git show <commit_sha>

위 코드 블록에서 <commit_sha> 같은 부등호 표기는 MDX에서 문제가 될 수 있으니, 실제로는 커밋 해시를 그대로 넣어 실행하세요.

해결: 스킵해도 되는 경우

정말 동일 변경이 이미 main에 있다면 스킵이 정답입니다.

git rebase origin/main

# rebase 도중 빈 커밋/중복 커밋으로 멈춘 경우
# 해당 커밋을 건너뛰고 계속
git rebase --skip

다만 “스킵했더니 내 기능이 빠졌다”는 사고가 종종 발생합니다. 스킵 전에 git show로 변경 범위를 확인하는 습관이 중요합니다.

원인 2: 충돌 해결 중 이미 변경을 반영해 버렸다

rebase 도중 충돌이 나면 사용자는 파일을 열어 수동으로 수정합니다. 이때 충돌을 해결하면서 “다음 커밋이 하려던 변경까지” 미리 반영해 버리는 경우가 있습니다.

그 다음 커밋을 적용하려고 하면, Git 입장에서는 이미 반영된 변경이라 빈 패치가 되어 patch is empty 혹은 duplicate patch로 이어질 수 있습니다.

확인 방법: rebase 진행 상태와 다음 커밋 확인

# 어떤 단계에서 멈췄는지 확인
git status

# rebase todo 리스트 확인(가능한 경우)
cat .git/rebase-merge/git-rebase-todo

해결: 스킵 vs 빈 커밋 유지

대부분은 스킵이 맞습니다.

git rebase --skip

하지만 “커밋 메시지 자체가 의미가 있고, 변경이 없어도 히스토리에 남겨야 하는” 팀 규칙이 있다면(예: 릴리즈 노트용), 빈 커밋을 허용하는 전략도 있습니다. 다만 일반적으로는 권장하지 않습니다.

원인 3: 과거 커밋을 squash 하면서 동일 변경이 재등장했다

히스토리 정리를 위해 squashfixup을 하다 보면, 동일 파일/동일 라인 변경이 여러 커밋에 걸쳐 중복 기록되기도 합니다. 특히 다음 상황에서 빈 패치가 자주 나옵니다.

  • 커밋 1에서 코드 추가
  • 커밋 2에서 같은 코드를 수정
  • rebase 중 충돌 해결에서 최종 형태를 한 번에 만들어버림

그러면 커밋 2는 사실상 “할 일”이 없어집니다.

해결: 인터랙티브 rebase로 드롭하거나 squash

# 최근 N개 커밋을 정리
git rebase -i HEAD~5

에디터가 열리면 중복되거나 의미가 사라진 커밋을 다음처럼 처리합니다.

  • drop으로 제거
  • squash로 이전 커밋에 합치기

예시(개념):

pick 1111111 add feature flag
squash 2222222 tweak feature flag
drop 3333333 duplicate change already in main

위 예시에서 커밋 해시는 예시 값이며, 실제 환경에 맞게 조정하세요.

원인 4: cherry-pick을 했고, rebase에서 재적용 여부가 엇갈린다

Git은 “이미 다른 곳에 적용된 커밋”을 감지하기 위해 patch-id라는 개념을 활용합니다. 이때 cherry-pick으로 들어간 커밋은 내용이 같으면 “이미 적용됨”으로 판단될 수 있습니다.

여기서 중요한 옵션이 --reapply-cherry-picks 입니다.

  • 기본 동작에서는 이미 적용된 것으로 판단하면 스킵할 수 있습니다
  • 정말로 같은 변경을 다시 커밋으로 남기고 싶다면 재적용 옵션을 고려합니다

재적용이 필요한 경우

  • 커밋 해시를 유지하거나(정확히는 유지가 아니라 새 커밋이 생성됨), 특정 커밋 단위로 추적이 필요
  • 릴리즈 브랜치에 cherry-pick한 커밋을 기능 브랜치에도 동일 커밋 단위로 남겨야 하는 정책

커맨드

git rebase --reapply-cherry-picks origin/main

주의할 점은, 이 옵션은 “중복 커밋이 생기는 게 의도”일 때만 쓰는 게 좋습니다. 일반적인 제품 개발 브랜치에서는 중복 커밋이 히스토리를 지저분하게 만들 가능성이 큽니다.

실전 진단 절차: 스킵하기 전에 3가지만 확인

duplicate patch 경고를 봤을 때, 다음 순서로 확인하면 대부분 안전하게 결론을 낼 수 있습니다.

1) 내 브랜치에서 적용하려던 커밋이 무엇인지 확인

git show --stat

rebase 도중이라면 현재 적용 중인 커밋을 git show로 보면 대개 확인 가능합니다.

2) 대상 브랜치에 같은 변경이 이미 있는지 검색

# 파일 단위로 히스토리 검색
git log -p origin/main -- path/to/file

# 특정 문자열이 언제 들어갔는지 찾기
git log -S "someSymbol" --oneline -- path/to/file

3) 스킵 후 결과가 맞는지 최종 diff로 검증

# rebase가 끝난 뒤, main 대비 변경이 기대와 같은지 확인
git diff origin/main...HEAD

여기서 ... 비교는 공통 조상 기준 비교라 rebase 검증에 유용합니다.

rebase 중 duplicate patch로 멈췄을 때의 안전한 처리 레시피

아래는 “멈췄을 때 무엇을 할지”를 선택하는 체크리스트입니다.

케이스 A: 변경이 이미 main에 있다

  • 조치: 스킵
git rebase --skip

케이스 B: 변경이 일부만 반영되어 있고 나머지가 필요하다

  • 조치: 충돌 해결처럼 수동 반영 후 계속
# 파일 수정 후
git add -A
git rebase --continue

이때 “일부만 반영”은 Git이 완전히 동일한 패치로 오판했거나, 과거 커밋에서 유사 변경이 들어간 상황일 수 있습니다.

케이스 C: 커밋 자체가 불필요해졌다(리팩터링으로 대체됨)

  • 조치: 인터랙티브 rebase에서 drop
git rebase -i origin/main
# 에디터에서 해당 커밋을 drop

케이스 D: 중복이지만 커밋 단위로 다시 남겨야 한다

  • 조치: --reapply-cherry-picks 고려
git rebase --reapply-cherry-picks origin/main

팀에서 재발을 줄이는 운영 팁

1) 기능 브랜치는 가능한 한 짧게 유지

브랜치 수명이 길어질수록 같은 파일을 여러 사람이 만지고, 동일 변경이 다른 경로로 들어갈 확률이 늘어 duplicate patch가 잦아집니다.

2) “같은 변경을 두 번 머지”하는 흐름을 줄이기

  • 핫픽스는 릴리즈 브랜치에만 넣고 나중에 main으로 머지할지
  • 혹은 main에 먼저 넣고 릴리즈 브랜치에는 cherry-pick만 할지

정책이 흔들리면 동일 변경이 여러 커밋으로 분산되어 rebase 시 중복 판정이 늘어납니다.

3) CI에서 rebase 기반 워크플로우를 쓴다면 권한과 토큰 정책도 점검

rebase 자체는 로컬 작업이지만, PR 자동 업데이트나 봇이 rebase를 수행하는 구조라면 토큰 권한 문제로 실패 로그가 섞여 원인 파악이 어려워질 수 있습니다. 이런 경우 GitHub Actions 403 권한 오류 해결 - GITHUB_TOKEN 같은 체크리스트가 도움이 됩니다.

자주 묻는 질문

duplicate patch가 떠도 그냥 무시하고 진행해도 되나

“정말 동일 변경이 이미 들어갔다”가 확실하면 스킵해도 됩니다. 하지만 확신이 없다면, 스킵 전에 최소한 git show로 해당 커밋 diff를 확인하고, rebase 완료 후 git diff origin/main...HEAD로 최종 결과를 검증하는 게 안전합니다.

스킵했는데 기능이 빠졌다

대개 다음 중 하나입니다.

  • Git이 유사 패치로 오판했다
  • 충돌 해결 중 일부 변경을 누락했다
  • 동일 변경이 main에 있는 줄 알았는데 실제로는 다른 구현이었다

이때는 rebase를 되돌린 뒤 다시 진행하는 게 빠를 때가 많습니다.

# rebase 시작 전 상태로 되돌리기(ORIG_HEAD가 남아있는 경우)
git reset --hard ORIG_HEAD

또는 git reflog로 rebase 전 커밋을 찾아 복구할 수 있습니다.

git reflog
git reset --hard <reflog_commit>

위의 <reflog_commit> 역시 실제 해시로 치환해 실행하세요.

마무리

git rebaseduplicate patch 경고는 “Git이 똑똑하게 중복을 감지했다”는 신호인 동시에, “내가 의도한 변경이 정말 이미 반영된 게 맞는지 확인하라”는 경고이기도 합니다.

정리하면 다음 원칙이 실전에서 가장 유용합니다.

  • 스킵은 빠르지만, 스킵 전 git show로 변경을 확인한다
  • rebase 완료 후 git diff origin/main...HEAD로 결과를 검증한다
  • 중복 커밋이 자주 나오면 브랜치 운영 정책과 cherry-pick 흐름을 점검한다

이 3가지만 지켜도 duplicate patch로 인한 누락/중복 사고는 대부분 예방할 수 있습니다.