Git

[Git] cherry-pick 이 중복 커밋을 만드는 이유

개발자 Noah 2026. 9. 14. 07:19

운영에서 급한 버그가 나서 develop 에 고친 커밋을 release 브랜치로 cherry-pick 했다. 배포는 잘 됐다. 문제는 그다음이었다. 릴리스가 끝나고 developrelease 에 정식으로 합쳤더니, 같은 수정이 커밋 목록에 두 번 나왔다.

파일 내용은 멀쩡했다. 충돌도 안 났다. 그런데 히스토리에는 같은 제목의 커밋이 두 개 남았다. 나중에 "이 수정이 언제 들어갔지" 를 찾을 때 어느 쪽을 봐야 하는지 알 수 없었다.

같은 변경을 cherry-pick 했을 뿐인데 왜 커밋이 두 개가 되는가?

환경

항목 버전
Git 2.50.1 (Apple Git-155)
확인한 문서 git-scm.com git-cherry-pick, git-merge (2026-08-10 기준)

cherry-pick 은 커밋을 옮기지 않는다

이름 때문에 오해하기 쉽다. 체리를 따서 다른 그릇에 옮겨 담는 그림이 떠오른다. 실제로는 옮기는 게 아니다. 문서의 첫 줄이 정확히 적고 있다.

Given one or more existing commits, apply the change each one introduces, recording a new commit for each.

하나 이상의 기존 커밋이 주어지면, 각 커밋이 도입하는 변경을 적용하고, 각각에 대해 새 커밋을 기록한다.

git-cherry-pick

"recording a new commit" 이 전부다. 원본은 그대로 있고, 같은 변경을 담은 별개의 커밋이 하나 더 생긴다. 옮긴 게 아니라 복사한 것이고, 복사본은 원본과 다른 물건이다.

왜 다른 물건인가

커밋의 정체성은 변경 내용만으로 정해지지 않는다. 부모가 누구인지, 언제 만들어졌는지, 커밋 메시지가 무엇인지까지 합쳐서 해시가 나온다. 변경 내용이 같아도 부모가 다르면 다른 해시다.

cherry-pick 은 정의상 다른 브랜치에 붙이는 명령이다. 붙는 자리가 다르니 부모가 다르고, 부모가 다르니 해시가 다르다.

develop의 커밋 F를 release로 cherry-pick하면 부모가 달라져 같은 변경이지만 해시가 다른 F′ 커밋이 새로 생긴다

그래서 나중에 developrelease 에 merge 할 때, Git 은 FF′ 를 같은 것으로 보지 않는다. 이름만 같고 남남인 커밋 두 개다. 둘 다 히스토리에 남는다.

그런데 충돌은 왜 안 났나

여기가 헷갈리는 지점이었다. 같은 줄을 두 번 고친 셈인데 충돌이 안 났다.

merge 는 공통 조상과 양쪽을 비교하는데, 양쪽이 같은 값으로 바뀌었으면 충돌로 넘기지 않는다. 문서의 충돌 예시 설명에 그 경우가 명시돼 있다 — "양쪽이 같은 방식으로 바꿔서 깨끗하게 해결된" 줄이라고 표현한다.

그러니 이 상황은 이렇게 갈린다.

층위 결과
파일 내용 양쪽이 같은 값이라 충돌 없음
커밋 히스토리 별개 커밋 두 개가 그대로 남음

파일은 조용히 맞아떨어지고 히스토리만 지저분해진다. 그래서 발견이 늦는다. 만약 cherry-pick 이후에 그 코드를 한쪽에서 더 고쳤다면, 그때는 충돌로 올라온다.

적용 — 출처를 커밋에 박아 둔다

중복 자체를 없앨 수는 없다. 두 브랜치에 각각 커밋이 필요해서 cherry-pick 을 한 것이므로, 커밋이 둘인 건 의도한 결과다. 문제는 나중에 둘이 한 뿌리라는 걸 알 수 없다는 것이다.

-x 옵션이 그걸 해결한다.

When recording the commit, append a line that says "(cherry picked from commit …)" to the original commit message in order to indicate which commit this change was cherry-picked from.

커밋을 기록할 때 원래 커밋 메시지에 "(cherry picked from commit …)" 이라는 줄을 덧붙여, 이 변경이 어느 커밋에서 cherry-pick 됐는지 표시한다.

git-cherry-pick

# 출처 해시를 커밋 메시지에 남긴다
git switch release
git cherry-pick -x 9a3f1c2

메시지 끝에 한 줄이 붙는다.

결제 금액 반올림 오류 수정

(cherry picked from commit 9a3f1c2b4e5d6a7f8091a2b3c4d5e6f708192a3b)

이 한 줄이 있으면 나중에 중복을 봤을 때 어느 쪽이 원본인지 즉시 알 수 있다.

다만 문서는 항상 쓰라고 하지 않는다. 쓸 자리를 구분한다.

Do not use this option if you are cherry-picking from your private branch because the information is useless to the recipient. If on the other hand you are cherry-picking between two publicly visible branches … adding this information can be useful.

개인 브랜치에서 cherry-pick 하는 경우에는 이 옵션을 쓰지 마라. 받는 사람에게 쓸모없는 정보이기 때문이다. 반면 공개된 두 브랜치 사이에서 cherry-pick 하는 경우라면 이 정보를 추가하는 것이 유용할 수 있다.

git-cherry-pick

기준은 남이 그 해시를 조회할 수 있는가다. 내 로컬 브랜치의 해시를 적어 두면 아무도 찾을 수 없는 문자열이 커밋 메시지에 박힌다. developrelease 처럼 둘 다 공유 브랜치면 유용하다.

rebase 와 같은 뿌리다

이 성질은 cherry-pick 만의 것이 아니다. rebase 문서가 자기 동작을 설명하면서 cherry-pick 을 끌어온다.

Replay the commits, one by one, in order. This is similar to running git cherry-pick <commit> for each commit.

커밋을 하나씩 순서대로 다시 재생한다. 각 커밋마다 git cherry-pick <commit> 을 실행하는 것과 비슷하다.

git-rebase

rebase 후에 해시가 바뀌는 이유가 이것이다. merge와 rebase 편에서 다룬 내용이 여기서 다시 나온다. 두 명령은 같은 일을 한다. 커밋을 옮기는 게 아니라 다시 만든다. 개수가 다를 뿐이다.

대가

cherry-pick 을 쓸 때 감수하는 것은 셋이다.

첫째, 히스토리에 같은 변경이 여러 번 남는다. 이건 없앨 수 없다. -x 로 추적 가능하게 만드는 게 최선이다.

둘째, 나중에 충돌을 부를 수 있다. 앞에서 본 대로 값이 완전히 같으면 조용하지만, 어느 한쪽에서 그 코드를 더 손대면 충돌로 올라온다. 이때 조상만 보면 왜 갈렸는지 알기 어렵다.

셋째, 맥락이 빠진 채 복사된다. 그 커밋이 앞선 커밋에 기대고 있었다면, 변경만 떼어 온 자리에서는 동작이 달라질 수 있다. 버그 수정 하나가 그 앞의 리팩터링에 의존하는 경우가 그렇다.

그래서 여러 커밋을 여러 번 cherry-pick 하고 있다면, 그건 브랜치를 통째로 merge 해야 한다는 신호에 가깝다. cherry-pick 은 하나를 급히 가져오는 도구지 브랜치를 동기화하는 도구가 아니다.

정리

질문의 답은 명령 이름에 있었다. cherry-pick 은 커밋을 옮기는 게 아니라 변경을 적용해 새 커밋을 기록하는 명령이다. 부모가 다르니 해시가 다르고, Git 은 둘을 같은 것으로 볼 방법이 없다. 중복은 부작용이 아니라 이 명령의 정의에서 나오는 결과다.

그래서 실무 규칙은 둘로 줄어든다. 공유 브랜치 사이에서는 -x 를 붙여 출처를 남긴다. 그리고 여러 개를 반복해서 집어 오고 있으면 merge 를 검토한다.