
운영에서 급한 버그가 나서 develop 에 고친 커밋을 release 브랜치로 cherry-pick 했다. 배포는 잘 됐다. 문제는 그다음이었다. 릴리스가 끝나고 develop 을 release 에 정식으로 합쳤더니, 같은 수정이 커밋 목록에 두 번 나왔다.
파일 내용은 멀쩡했다. 충돌도 안 났다. 그런데 히스토리에는 같은 제목의 커밋이 두 개 남았다. 나중에 "이 수정이 언제 들어갔지" 를 찾을 때 어느 쪽을 봐야 하는지 알 수 없었다.
같은 변경을 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.
하나 이상의 기존 커밋이 주어지면, 각 커밋이 도입하는 변경을 적용하고, 각각에 대해 새 커밋을 기록한다.
"recording a new commit" 이 전부다. 원본은 그대로 있고, 같은 변경을 담은 별개의 커밋이 하나 더 생긴다. 옮긴 게 아니라 복사한 것이고, 복사본은 원본과 다른 물건이다.
왜 다른 물건인가
커밋의 정체성은 변경 내용만으로 정해지지 않는다. 부모가 누구인지, 언제 만들어졌는지, 커밋 메시지가 무엇인지까지 합쳐서 해시가 나온다. 변경 내용이 같아도 부모가 다르면 다른 해시다.
cherry-pick 은 정의상 다른 브랜치에 붙이는 명령이다. 붙는 자리가 다르니 부모가 다르고, 부모가 다르니 해시가 다르다.
그래서 나중에 develop 을 release 에 merge 할 때, Git 은 F 와 F′ 를 같은 것으로 보지 않는다. 이름만 같고 남남인 커밋 두 개다. 둘 다 히스토리에 남는다.
그런데 충돌은 왜 안 났나
여기가 헷갈리는 지점이었다. 같은 줄을 두 번 고친 셈인데 충돌이 안 났다.
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 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 하는 경우라면 이 정보를 추가하는 것이 유용할 수 있다.
기준은 남이 그 해시를 조회할 수 있는가다. 내 로컬 브랜치의 해시를 적어 두면 아무도 찾을 수 없는 문자열이 커밋 메시지에 박힌다. develop → release 처럼 둘 다 공유 브랜치면 유용하다.
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>을 실행하는 것과 비슷하다.
rebase 후에 해시가 바뀌는 이유가 이것이다. merge와 rebase 편에서 다룬 내용이 여기서 다시 나온다. 두 명령은 같은 일을 한다. 커밋을 옮기는 게 아니라 다시 만든다. 개수가 다를 뿐이다.
대가
cherry-pick 을 쓸 때 감수하는 것은 셋이다.
첫째, 히스토리에 같은 변경이 여러 번 남는다. 이건 없앨 수 없다. -x 로 추적 가능하게 만드는 게 최선이다.
둘째, 나중에 충돌을 부를 수 있다. 앞에서 본 대로 값이 완전히 같으면 조용하지만, 어느 한쪽에서 그 코드를 더 손대면 충돌로 올라온다. 이때 조상만 보면 왜 갈렸는지 알기 어렵다.
셋째, 맥락이 빠진 채 복사된다. 그 커밋이 앞선 커밋에 기대고 있었다면, 변경만 떼어 온 자리에서는 동작이 달라질 수 있다. 버그 수정 하나가 그 앞의 리팩터링에 의존하는 경우가 그렇다.
그래서 여러 커밋을 여러 번 cherry-pick 하고 있다면, 그건 브랜치를 통째로 merge 해야 한다는 신호에 가깝다. cherry-pick 은 하나를 급히 가져오는 도구지 브랜치를 동기화하는 도구가 아니다.
정리
질문의 답은 명령 이름에 있었다. cherry-pick 은 커밋을 옮기는 게 아니라 변경을 적용해 새 커밋을 기록하는 명령이다. 부모가 다르니 해시가 다르고, Git 은 둘을 같은 것으로 볼 방법이 없다. 중복은 부작용이 아니라 이 명령의 정의에서 나오는 결과다.
그래서 실무 규칙은 둘로 줄어든다. 공유 브랜치 사이에서는 -x 를 붙여 출처를 남긴다. 그리고 여러 개를 반복해서 집어 오고 있으면 merge 를 검토한다.
'Git' 카테고리의 다른 글
| [Git] stash pop 하다 충돌나면 stash는 어디 있나 (0) | 2026.09.18 |
|---|---|
| [Git] 브랜치 전략은 팀 크기가 정하지 않는다 (0) | 2026.09.15 |
| [Git] detached HEAD 가 뭐고 왜 생기나 (0) | 2026.09.12 |
| [Git] force push 로 남의 커밋을 날렸다 (0) | 2026.09.11 |
| [Git] 충돌은 왜 나고, 나는 뭘 고르는 건가 (0) | 2026.09.10 |