Git 머지 전략에 대하여
안녕하세요. 최근에는 프로젝트별로 혹은 브랜치별로 상황에 따라 적합한 git merge 전략을 사용하는 경우가 많습니다. 해당 포스팅에서는 각 방법의 특징에 대해 알아보고 어떤 경우에 어떤 방법을 사용하는게 좋을지에 대해 정리해보려고 합니다.
Merge 전략
먼저 git의 머지 전략은 아래와 같은 세 가지로 나눌 수 있습니다.
- merge commit
- rebase and merge
- squash and merge
github에서 pull request를 통해 머지할 때도 아래와 같이 어떤 전략을 사용할지 물어보는 것을 알 수 있습니다.
세 가지 중 어떤 전략을 사용하는지에 따라 커밋 히스토리가 달라지므로 따라서 상황에 맞는 머지 전략을 사용하면 히스토리를 원하는 방식으로 남길 수 있습니다.
Merge commit
가장 일반적으로 사용하는 머지 방식은 merge commit 방식입니다. 일반적으로 merge
명령어를 사용했을 때 실행되는 머지 방식으로 다음과 같은 방식으로 브랜치를 병합할 수 있습니다.
git checkout master git merge feature-branch
아래와 같이 master와 master에서 분기한 feature-branch가 있다고 가정해봅시다. 이 때 master의 가장 최근 커밋은 C이고 feature branch의 가장 최근 커밋은 F입니다.
A---B---C (master) \ D---E---F (feature-branch)
이제 feature branch에서 master로 머지를 수행할 경우 새로운 머지 커밋인 M이 생성됩니다. 이 때 M은 두 개의 부모를 가지게 되며 이는 C와 F를 가리킵니다.
A---B---C-----M (master) \ / D---E---F (feature-branch)
이처럼 merge commit 방식을 사용하게 될 경우 커밋 로그와 머지 로그가 동시에 기록됩니다. 이 때 커밋 로그는 커밋을 수행한 순서대로 기록되고 머지 로그는 병홥된 순서대로 기록됩니다. 즉 커밋 로그의 순서와 머지 로그의 순서가 다르기 때문에 히스토리 관리에 어려움이 있을 수 있습니다.
Squash and Merge
다음으로 squash and merge는 여러 개의 커밋을 하나로 합치고 이를 하나의 새로운 커밋으로 만들어 저장하는 방식을 말합니다. 주로 여러 개의 작은 커밋을 하나의 의미있는 커밋으로 정리하고 싶을 때 사용되며 아래와 같이 수행할 수 있습니다.
git checkout master git merge --squash feature-branch git commit -m "squash and merge"
squash and merge를 수행할 경우 다음과 같이 D, E, F를 통합하는 새로운 커밋인 S가 생성됩니다. 커밋 S는 병합된 브랜치인 master의 최신 커밋만을 부모로 가지게 됩니다. 이 때 feature branch의 커밋 히스토리는 변경되지 않습니다.
A---B---C---S (master) \ D---E---F (feature-branch)
squash and merge는 병합된 순서대로 master/main 브랜치에 기록되어 히스토리 관리에 용이하지만 feature branch의 커밋의 경우 master/main 브랜치에 기록되지 않으므로 atomic commit level로 rollback하는 것은 불가능합니다.
Rebase and Merge
마지막으로 rebase and merge는 현재 브랜치의 모든 커밋을 병합 대상 브랜치로 통합하는 방법을 말합니다. 즉 각 커밋들은 모두 하나의 부모를 가지게 되어 선형적으로 이어지게 됩니다. reabse and merge는 아래와 같은 방식으로 진행됩니다.
git checkout feature-branch git rebase master // conflict 발생시 commit이 아닌 rebase --continue를 사용해야 함 git add . git rebase --continue // rebase 취소하기 위해서는 --abort를 사용하면 된다. // 여기까지 진행하면 feature/task-2의 변경사항이 dev 브랜치의 앞 쪽으로 위치가 옮겨졌을 뿐 dev에는 아직 feature/task-2의 변경 사항이 적용되지 않았다. git checkout master git merge feature-branch
rebase and merge를 사용할 경우 지금까지의 경우와는 달리 기존 브랜치의 커밋들이 새로운 기점으로 이동하게 되며 이로 인해 기존 commit 히스토리가 변경됩니다. 따라서 feature-branch의 커밋들은 master 브랜치의 최신 커밋 위에 재배치되어 새로운 커밋들로 대체되며 아래와 같이 선형적인 히스토리를 가지게 됩니다. 이 때 머지를 위한 추가 commit은 생성되지 않습니다.
A---B---C---D'---E'---F' (master, feature-branch)
또한 rebase and merge의 경우 커밋 순서대로가 아닌 병합 순서대로 합쳐집니다. 즉 하나의 PR에 담긴 커밋이 다른 PR의 커밋과 섞이지 않습니다. 이로 인해 PR 단위의 히스토리 관리가 용이하며 squash merge와 달리 atomic level로의 rollback도 가능하지만 모든 커밋이 병합되는 브랜치에 되기 때문에 히스토리가 불필요하게 많아질 수 있습니다.
그렇다면 각각의 머지 전략은 언제 어디서 사용되는 것이 유리할까?
위에서 알아본 특징들을 활용하여 목적에 따라 머지 전략을 다르게 사용할 수 있습니다.
dev - feature merge
물론 팀마다 전략에 따라 다르지만 feature 브랜치에서 작업한 커밋의 경우 모든 커밋과 커밋 메시지가 dev에 들어갈 필요는 없는 경우가 많습니다.
그러므로 feature 브랜치를 dev로 머지할 때는 복잡한 커밋 히스토리를 하나로 묶어 새로운 커밋으로 만들 수 있는 squash and merge를 사용하는 것이 유리할 수 있습니다.
일반적으로 feature 브랜치를 병합한 후에 브랜치를 삭제해버리는 경우가 많으므로 feature의 커밋 히스토리를 모두 dev에 연관지어 남길 필요는 없는 경우가 많습니다.
master - dev merge
dev에서 master로 병합할 경우에는 모든 커밋 히스토리가 남아야 하고 각 커밋이 섞이면 좋지 않으므로 squash and merge가 유리할 수 있습니다.
hofix - dev, hotfix - main merge
merge 혹은 Squash and merge 모두 사용될 수 있습니다. hotfix 작업의 각 commit history가 모두 남아야 하는 경우 merge, 필요 없는 경우 squash and merge를 사용하면 좋습니다.