Develop 브랜치의 여러 개의 커밋 중 특정 커밋만 제외하고 나머지를 모두 master 브랜치에 merge하는 방법이 없을까?

상황

먼저 이런 상황은 어떤 때 발생할까? 실제로 겪은 상황으로, 세부 사항은 아래와 같다.

  • Develop 브랜치의 총 다섯 개 커밋 중 두 번째, 세 번째 커밋만 테스트가 완료되지 않았다.
  • 나머지 커밋은 당장 master에 merge 후 deploy해야 한다.
  • 언제든 테스트가 완료되면, 두 번째 및 세 번째 커밋도 master에 merge해야 한다.

해결 방법: cherry pick

Develop 브랜치의 commit log는 아래와 같다.

git log --oneline

b9eea54 (HEAD -> develop) develop commit 5
589edb0 develop commit 4
7fb956e develop commit 3
cee7c6c develop commit 2
f14f086 develop commit 1
254035f (master) master commit

이제 여기서 commit 2, commit 3만 제외하고 master에 merge하려면 어떻게 해야 할까? 결론부터 말하자면 commit 2와 commit 3만 제외하고 모두 cherry pick하면 된다.

git checkout master
git checkout -b release

echo "제외할 커밋 전까지의 커밋을 모두 cherry pick 한다. 여기서는 commit 1 한 개만 있다."
git cherry-pick f14f086
echo "제외할 커밋 후의 커밋을 모두 cherry pick 한다. 참고로 ^ 기호는 inclusive를 의미한다."
git cherry-pick 589edb0^..b9eea54

참고로 cherry pick 대상이 여러 커밋이면 순서에 주의해야 한다. cherry pick 의 원리는 먼저 적힌 커밋부터 커밋하는 것이기 때문에, 두 개 이상의 커밋을 cherry pick 하려면 선행하는 것부터 먼저 입력해야 한다.

위 명령어를 실행한 후에 커밋 내용을 살펴보자.

git log --oneline

e6517ac (HEAD -> release) develop commit 5
ba8b1cf develop commit 4
ce727f9 develop commit 1
254035f (master) master commit

의도한대로 commit 2, commit 3을 제외하고 모두 포함됐다. 이제 이 release 브랜치를 master 브랜치에 merge하자. 아래와 같이 말이다.

git checkout master
git merge release

다른 방법

다른 방법은 없을까? 있긴 한데, 상황에 적절하지 않다.

Revert (비추천)

첫 뻔째 다른 방법은, merge한 후에 제외하고 싶은 커밋을 revert하는 것이다.

git checkout master
git checkout -b release

git merge develop
echo "revert는 최신 커밋부터 오래된 커밋 순으로 실행한다."
git revert 7fb956e
git revert cee7c6c

아래 git log 명령어를 통해 commit 내역을 살펴보자.

git log --oneline

8a96d59 (HEAD -> release) Revert "develop commit 2"
ea6cde1 Revert "develop commit 3"
b9eea54 (develop) develop commit 5
589edb0 develop commit 4
7fb956e develop commit 3
cee7c6c develop commit 2
f14f086 develop commit 1
254035f (master) master commit

이 방법에는 문제점이 있다. 나중에 develop 브랜치를 master 브랜치에 merge할 때, 8a96d59 (HEAD -> release) Revert "develop commit 2"ea6cde1 Revert "develop commit 3" 두 커밋을 다시 revert 해야 한다. 이 두 커밋을 revert하지 않으면, commit 2, commit 3은 영영 master에 반영되지 않는다. Develop branch를 통째로 master에 merge해도 말이다. 결국 개발자가 이 내용을 기억하고 있다가 revert를 실행해야 한다. 아래와 같이 말이다.

git checkout master
git checkout -b release
git merge develop

echo "commit 3과 commit 2를 revert한 커밋을 revert한다."
git revert 8a96d59
git revert ea6cde1

매우 번거롭다. 최악의 경우에는 까먹을 수도 있다.

merge strategy ours (이 글의 문제를 해결하지 못 한다)

Merge는 과거의 커밋까지 모두 가져온다. 그렇기 때문에 특정 커밋만 제외하는 것은 불가능하다. 하지만 merge를 하되, 변경 사항을 반영하지 않는 방법이 있다. git merge -s ours가 그것이다. -s ours는 ours 전략을 뜻하는데, 현재 git head가 위치한 브랜치가 ours로서, 해당 커밋에 대해서는 git head가 위치한 브랜치의 변경 사항을 유지하겠다는 뜻이다. 이게 무슨 이상한 설명일까? 알아보자.

아래는 master, develop 브랜치별 파일 목록이다.

git checkout master
ls

master commit

git checkout develop
ls

develop commit 1	develop commit 3	develop commit 5
develop commit 2	develop commit 4	master commit

참고로 커밋 하나당 하나의 파일을 만들었다. 아래와 같이 말이다.

git log develop --oneline

echo "실험을 위해 커밋 메시지와 동일한 파일을 커밋마다 생성했다."

b9eea54 (HEAD -> develop) develop commit 5
589edb0 develop commit 4
7fb956e develop commit 3
cee7c6c develop commit 2
f14f086 develop commit 1
254035f (master) master commit

이제 문제의 git merge -s ours를 실행해보자.

git checkout master
git checkout -b release

git merge f14f086

echo "merge 대상에 대해서 ours strategy 적용. 제외하고 싶은 commit 2, commit 3에 대해 적용."
git merge -s ours 7fb956e

git merge develop

이제 merge가 완료됐다. 커밋 내역을 확인해보자.

git log release --oneline

6dc6150 (HEAD -> release) Merge branch 'develop' into release
baab338 Merge commit '7fb956e' into release
b9eea54 (develop) develop commit 5
589edb0 develop commit 4
7fb956e develop commit 3
cee7c6c develop commit 2
f14f086 develop commit 1
254035f (master) master commit

develop과 동일한 commit 내역을 갖고 있다. 하지만 실제 release 브랜치가 가진 파일은 develop과 다르다.

git checkout release
ls

develop commit 1	develop commit 5
develop commit 4	master commit

git checkout develop
ls

develop commit 1	develop commit 3	develop commit 5
develop commit 2	develop commit 4	master commit

이제 위의 이상한 설명이 이해가 가는가? 사실 이 방법은 어디에 쓸지 잘 모르겠다. 일단 검색하다가 찾아서 정리해둔다.

References