git 을 사용하여 작업을 할 때 기능별로 작업별로 브랜치를 새로 생성하여 작업을 하게된다. 각각의 브랜치는 작업이 끝나면 병합을 진행해주어야 하는데 git 에서는 크게 merge 와 rebase 두가지 방법을 사용한다.
병합 전략이 여러가지인 이유는 병합을 진행한 후에 두 브랜치 간의 commit history 를 어떻게 남길지 선택해야 하기 때문이다. 어떤 전략을 선택하느냐에 따라서 각각의 브랜치에서 작업을 수행한 기록인 commit history 를 하나의 브랜치로 합쳤을 때 어떤 형식과 순서로 기록이 남겨질지 달라진다. 그렇기 때문에 개발자는 자신의 상황과 함께 개발하는 조직에 알맞은 방식을 사용하여서 commit history 가 관리되도록 해야한다.
1. merge
merge 는 일반적으로 알고있는 병합 전략이다.
git merge 를 통해서 병합을 수행하는 경우 merge 된 브랜치를 삭제하더라도 히스토리 그래프 상에서는 그대로 다른 가지로 commit history 가 표현이 되기 때문에 어떤 브랜치에서 어떤 커밋이 진행되었는지를 자세히 알 수 있다. 물론 자세히 표현되는 만큼 브랜치의 개수가 늘어남에 따라서 그래프의 복잡도가 올라가는 단점이 있다.
git merge 는 다음의 방식들로 수행된다.
- fast-forward
두 브랜치 중 하나의 브랜치가 다른 브랜치의 base branch 인 경우에 fast-forward 방식으로 수행된다.
예를 들어서 A 라는 브랜치에서 B 라는 브랜치를 딴 후, B 브랜치에서 A 브랜치로 merge 를 수행한다고 해보자. 이때 만약 A 브랜치가 B 브랜치를 생성한 이후로 다른 작업을 하지 않았다면 단순히 A 브랜치의 head 를 B 브랜치의 마지막 커밋으로 옮기기만 하면 된다.
이렇게 되는 경우에 별도의 merge 를 위한 커밋없이 자동적으로 B 브랜치의 commit history 가 모두 합쳐지고 A 브랜치와 B 브랜치가 같은 커밋을 가리키게 된다. 이러한 merge 방식을 fast-forward 라고 한다.
- 3-way-merge
만약에 두 브랜치의 base 가 다른 경우에는 어떻게 될까?
두 브랜치가 모두 서로의 base 가 아닌 경우에는 공통의 조상이 되는 커밋을 찾아야 한다. 공통 조상 커밋이 없이는 소스 변경에 대한 비교 기준이 명확하지 않기 때문이다.
git merge 를 실행하면 git 이 알아서 auto merge 를 실행하는데, 먼저 base 를 비교하여 공통 조상을 찾은 후, 각 브랜치를 base 커밋과 비교하여 병합을 수행한다. 이때는 merge 를 위해서 새로운 커밋이 생성되며, confilct 이 나는 경우에는 해당 conflict 을 수정하여 merge 커밋과 함께 push 하면 된다.
이와같이 공통 조상 커밋과 두 브랜치의 마지막 커밋, 총 3개의 커밋을 비교하여 병합을 수행하는 방식을 3-way-merge 라고 한다.
- squash and merge
squash 는 여러개의 커밋을 하나로 합치는 기능을 의미한다. 이 기능을 사용하게 되면 merge 할 브랜치의 커밋을 하나의 커밋으로 합친 뒤 타겟 브랜치에 커밋하는 방식으로 병합을 수행한다.
squash 기능을 사용하게 되면 자잘한 커밋들이 모두 보이는 것이 아니라 브랜치 병합에 대한 커밋 하나만 보이기 때문에 보다 깔끔하게 commit history 를 관리할 수 있다. 하지만 기록이 간결해지는 만큼 자세한 변경 사항을 확인하기 힘들다는 단점이 있다.
// --ff: 일반적인 merge 명령어로 브랜치 base 관계를 확인하여 fast-forward 를 사용할 수 있으면 fast-forward 를 사용하고 그렇지 않으면 merge 커밋을 생성하여 3-way-merge 를 진행한다.
git merge {branch_name}
// --no-ff: 현재 브랜치와 병합 대상 브랜치의 관계가 fast-forward 관계인지 상관없이 무조건 merge 커밋을 생성하여 병합한다.
git merge --no-ff {branch_name}
// --ff-only: 현재 브랜치와 병합 대상 브랜치가 fast-forward 관계인 경우에만 병합을 진행한다. merge 커밋을 생성하지 않는다.
git merge --ff-only {branch_name}
// --squash: 병합 대상 브랜치의 커밋들을 하나의 커밋으로 합쳐서 현재 브랜치에 병합한다.
git merge --squash {branch_name}
2. rebase
Rebase 는 merge 와 같이 브랜치를 병합하는 기능이다. 하지만 rebase 는 merge 와 달리 commit history 를 새롭게 만들어서 더 깔끔하게 선형적으로 commit history 를 유지할 수 있다.
Rebase 는 현재 브랜치의 base 를 타겟 브랜치로 재설정하여 merge 하는 방식으로 진행한다. rebase 를 실행하면 현재 브랜치의 커밋들이 타겟 브랜치의 마지막 커밋 다음으로 커밋된다. 그렇게 rebase 가 끝나고난 뒤에는 fast-forward merge 를 통해서 merge 를 완료한다. 작업이 완료된 뒤에는 현재 브랜치와 타겟 브랜치가 모두 병합된 마지막 커밋을 가리키도록 한다.
이때 현재 브랜치의 commit history 가 그대로 이동하는 것으로 보이는데, 실제로는 변경되는 커밋들을 복사하여서 타겟 브랜치에 붙여넣는다. 그렇기 때문에 rebase 전의 커밋 id 와 작업 후 커밋 id 가 다른 것을 확인할 수 있다.
3. cherry-pick
Cherry-pick 은 다른 브랜치에서 지정한 commit 들을 가져와서 합치는 기능을 의미한다.
주로 신규 브랜치를 생성하여 필요한 commit 들만 cherry-pick 한 다음 해당 브랜치를 master 로 PR 하는 방식으로 사용한다.
Cherry-pick 은 명령어 뒤에 commit id 들을 붙여서 원하는 commit 들을 지정할 수 있다. 여러개의 commit 을 선택하려는 경우 띄어쓰기로 구분하여 commit 들의 id 를 적어주면 된다. 만약 연속된 commit 들을 선택하는 경우 '..' 을 사용해서 ka0fj2ok..aji2kdl 와 같은 식으로 사용할 수 있다.
4. github PR 에서 merge 방법
github 에서 PR 을 올린 후 이를 merge 할 때, 어떤 방식을 사용하여 merge 를 진행할 지 선택할 수 있다.
- create merge commit: 3-way-merge 방식과 같이 merge commit 을 생성하여 병합을 수행한다.
- squashand merge: squash 기능을 사용하여 PR 의 커밋들을 모두 하나로 만들어서 병합을 수행한다.
- rebase and merge: rebase 방식을 사용하여 merge 를 수행한다.
[reference]
- https://wonyong-jang.github.io/git/2021/02/05/Github-Merge.html
- https://wonyong-jang.github.io/git/2021/02/05/Github-Rebase.html
- [10분 테코톡] 오리&코린의 Merge, Rebase, Cherry pick
'기타 > Git' 카테고리의 다른 글
[Web] JWT (JSON Web Token) (0) | 2023.03.11 |
---|---|
[git] git hooks / pre-commit (0) | 2023.02.03 |
[git] git stash (0) | 2021.10.30 |
[git] squash로 commit 합치기 (0) | 2021.10.30 |
[git] git submodule / subtree (0) | 2021.10.19 |