빌드노트
cherry-pick이 empty로 멈췄을 때 skip 전에 확인할 것
cherry-pick이 empty로 멈춰도 새 충돌이 생긴 것은 아닐 수 있다. 대상 패치와 현재 파일·인덱스를 비교하고, skip으로 끝낼지 빈 커밋을 이력에 남길지 실제 실행 결과로 구분한다.
git cherry-pick이 The previous cherry-pick is now empty라고 멈췄다면, 이번 적용으로 새 커밋에 담을 차이가 없다는 뜻이다. 선택한 커밋의 수정이 다른 커밋을 통해 현재 브랜치에 이미 들어와 있을 수 있다. 미해결 충돌과 남은 diff를 확인하고, 대상 패치의 의도가 현재 파일에 있는지 비교한 뒤 skip을 결정한다. 빈 diff만으로 “이미 반영됐다”고 단정하지는 않는다.
같은 변경이 다른 해시로 들어와 있었다
Windows 11, Git 2.45.1.windows.1의 새 저장소에서 settings.txt 한 줄로 확인했다. 공통 부모의 내용은 color=red였다. 여기서 갈라진 두 브랜치에 각각 color=blue로 바꾸는 커밋을 따로 만들었다. 가져올 커밋 A는 5e8997f, 현재 브랜치의 커밋 B는 3bf873b였다. 두 해시는 다르고, B의 조상에 A는 없었다.
red 상태에서 A를 cherry-pick한 비교 실험은 정상 종료했고, HEAD가 blue 변경을 담은 커밋으로 이동했다. 반면 B 위에서 같은 A를 적용하자 종료 코드 1과 다음 메시지가 나왔다.
The previous cherry-pick is now empty, possibly due to conflict resolution.
이 경우에는 충돌 표시가 생기거나 파일을 수동으로 해결하는 과정이 없었다. 이미 blue인 상태에 같은 변경을 적용했기 때문이다. 메시지의 possibly는 충돌 해결이 원인일 수도 있다는 안내다. 실제 충돌이 있었다는 판정으로 읽으면 안 된다.
멈춘 뒤 git status에는 cherry-pick 진행 중이라는 안내와 nothing to commit, working tree clean이 함께 나왔다. 작업 파일이 깨끗해도 진행 상태는 남을 수 있다. 이번에는 git status --short가 아무것도 출력하지 않았으므로, 진행 여부까지 보려면 전체 status를 읽어야 했다.
두 diff와 가져오던 패치를 함께 읽는다
다음은 Git이 설치된 PowerShell에서, 일반적인 cherry-pick이 empty로 멈춘 저장소를 확인하는 명령이다. 먼저 첫 명령의 경로가 의도한 저장소인지 확인하고 그 루트로 이동한다. 이후 명령은 읽기만 한다. 명령 자체가 실패하면 빈 출력으로 간주하지 말고 오류를 먼저 확인한다.
git rev-parse --show-toplevel
git status
git ls-files --unmerged
git diff
git diff --cached
git show --format=short CHERRY_PICK_HEAD
git ls-files --unmerged는 인덱스에 남은 미해결 충돌 항목을 보여 준다. 출력이 있으면 아직 충돌을 해결해야 한다. 이번 실험에서는 출력이 없었다. 인덱스 자체가 비었다는 뜻은 아니다. settings.txt는 정상적인 인덱스 항목으로 남아 있었다. git ls-files 공식 문서 (새 탭에서 열림)
git diff는 작업 파일과 인덱스를, git diff --cached는 인덱스와 HEAD를 비교한다. HEAD는 현재 커밋이고, 인덱스는 다음 커밋에 넣을 내용을 준비하는 곳이다. 실험에서는 두 diff가 모두 비었다. 반대로 첫 diff에 내용이 있으면 아직 인덱스에 올리지 않은 수정이 있고, cached diff에 내용이 있으면 커밋에 담을 차이가 남아 있다. 그런 상태를 이 실험의 empty 상태와 같다고 취급하면 안 된다. git diff 공식 문서 (새 탭에서 열림)
CHERRY_PICK_HEAD는 여기서 적용하다 멈춘 커밋 A를 가리켰다. git show로 읽은 패치의 변경 줄은 다음과 같았다. 커밋 정보와 diff 헤더는 생략했다.
-color=red
+color=blue
실제 파일과 현재 커밋의 내용도 읽는다. 아래의 settings.txt는 예제 파일 이름이므로 자기 패치에 나온 경로로 바꾼다. 대상 커밋이 여러 파일을 바꿨다면 그 변경 전체를 확인해야 한다.
git show 'HEAD:settings.txt'
Get-Content -LiteralPath './settings.txt'
두 명령 모두 color=blue를 출력했다. 대상 패치는 red를 blue로 바꾸며, 현재 커밋과 작업 파일에는 이미 blue가 있고, 인덱스와의 차이도 없었다. 이 관찰을 합쳐 A의 변경이 B에 이미 반영돼 있다고 판단했다. 해시가 다르다는 사실만으로 수정이 빠졌다고 볼 수 없었다. git show는 커밋의 패치나 지정한 파일 객체를 읽는 명령이다. HEAD:경로는 현재 커밋 안의 파일을 지정한다. git show 공식 문서 (새 탭에서 열림), Git의 파일 지정 문법 (새 탭에서 열림)
CHERRY_PICK_HEAD 조회가 실패하면 현재 cherry-pick이 진행 중인지부터 다시 확인한다. 이미 종료했거나 다른 작업으로 멈춘 상태라면 이 절차의 전제가 맞지 않는다.
내용이 충분하면 skip, 이력에 남길 의도라면 빈 커밋
파일 내용이 목적대로 있고 새 커밋을 남길 이유가 없다면, 멈춘 현재 커밋을 건너뛴다. 아래 첫 HEAD 값은 실행 전에 기록해 둔다.
git rev-parse HEAD
git cherry-pick --skip
git rev-parse HEAD
git status
한 커밋만 적용하던 이번 실험에서 skip 전후 HEAD는 모두 3bf873b6cfe066d2b6f12e4ad801d03b912816dd였다. 파일은 blue를 유지했고 cherry-pick 진행 안내만 사라졌다. B를 취소하거나 원래 커밋 A를 삭제한 결과가 아니다. 현재 커밋의 적용을 건너뛴 것이다. 여러 커밋을 연속으로 적용 중이면 skip 뒤 다음 커밋으로 진행하므로, 그 경우까지 HEAD가 같다고 보장하지 않는다. git cherry-pick의 --skip 설명 (새 탭에서 열림)
반대로 내용 차이는 없어도 이번 적용을 커밋 이력으로 남기려는 의도가 있을 수 있다. 그런 경우에는 skip하기 전, 미해결 충돌과 두 diff가 없는 것을 확인한 상태에서 아래를 선택한다. 앞의 skip과 이어 실행하는 명령이 아니라 같은 멈춤 상태에서 고르는 다른 선택이다. 커밋할 이름·이메일 설정도 필요하다.
git commit --allow-empty --no-edit
git log -1 --oneline
git diff HEAD^ HEAD
별도 비교에서는 새 HEAD 8d70004가 생겼고, 부모는 B였다. 메시지는 A의 target blue를 사용했다. 부모와 새 커밋의 파일 트리는 같았으며 마지막 diff도 비었다. 즉 코드 변경을 추가한 것이 아니라 이력 한 칸을 더한 것이다. --allow-empty는 부모와 같은 트리를 가진 커밋을 허용하고, --no-edit는 선택된 메시지를 편집기 없이 사용한다. git commit 공식 문서 (새 탭에서 열림)
여기서 git commit --allow-empty와 git cherry-pick --allow-empty를 혼동하기 쉽다. cherry-pick의 --allow-empty는 원래부터 빈 커밋을 허용하는 옵션이다. 이번처럼 원래 수정이 있었지만 현재 이력 때문에 비게 된 커밋을 자동 보존하려면 --empty=keep을 쓴다. 설치 버전에 가까운 2.45.0 문서와 현재 공식 문서에서 이 차이를 확인했다. 실제 2.45.1 실험에서도 cherry-pick --allow-empty는 다시 멈췄고 --empty=keep은 빈 커밋을 만들었다. 이미 멈춘 뒤에는 위의 skip과 commit 중 의도에 맞는 처리를 고르면 된다. 2.45.0 cherry-pick 문서 (새 탭에서 열림), 현재 cherry-pick 문서 (새 탭에서 열림)
빈 diff인데 의도한 수정은 없을 수도 있다
같은 저장소에서 반대 조건도 확인했다. 현재 값을 green으로 바꾼 뒤 A를 적용하자 실제 충돌이 났다. 해결하면서 blue를 넣지 않고 green을 그대로 남겨 git add한 뒤 계속하자, 똑같은 empty 메시지로 멈췄다. 미해결 항목과 두 diff는 비었지만 파일은 여전히 color=green이었다.
이 경우에는 “blue가 이미 반영됐다”는 결론을 낼 수 없다. 충돌 해결 결과에 A의 변경을 남기지 않았기 때문에 새 차이가 없어진 것이다. blue가 필요한 수정이라면 해결 내용을 다시 확인해야 한다. empty는 남은 차이가 없다는 상태이며, 원하는 기능까지 갖췄다는 보증은 아니다.
직접 확인한 범위는 새 저장소의 한 줄짜리 텍스트 파일, 단일 비병합 커밋, 이미 같은 변경이 있는 경우와 수동 해결로 변경을 남기지 않은 경우다. 실제 프로젝트의 기능 검증, 여러 커밋의 연속 적용, 병합 커밋·이름 변경·바이너리 파일은 시험하지 않았다. 실제 코드에서는 대상 패치가 하려던 일을 현재 구현이 충족하는지도 확인한 뒤 skip을 선택한다.