콘텐츠로 이동

Git의 Merge 종류 - merge, rebase and merge, squash and merge

svn과 달리 git은 브랜치 개념을 도입해, 누구나 하나의 로컬에 여러 브랜치를 가질 수도 있고 여러 원격지를 둘 수도 있다.

브랜치 운영 전략의 예로 master를 기준으로 하는 git flow가 있다.

브랜치를 병합하는 방법으로 일반 merge, rebase and merge, squash and merge가 있다.

세 방식 모두 병합 결과 코드는 같고, 그래프 모양과 병합하는 방식이 다르다.

  1. 일반적인(전통적인) merge
    예를 들어 브랜치 c를 master로 merge하면, git은 두 브랜치의 공통 조상(merge base)과 각 브랜치의 최신 커밋을 비교하는 3-way merge로 한 번에 합친다. conflict가 있으면 merge가 멈추고, 한 번에 모든 conflict를 해결한 뒤 커밋한다.
    master가 분기 이후 앞으로 나아가지 않았다면 fast-forward로 끝나 새 커밋이 생기지 않는다. 그렇지 않으면 부모가 둘인 merge 커밋이 생긴다(“feature/collection”의 변경점이 추가되었다는 커밋).
  2. rebase and merge
    rebase, 즉 브랜치의 parent를 master의 head로 바꾸어 병합하는 방법이다. rebase는 브랜치의 커밋을 master의 head 위에 하나씩 다시 적용하므로, conflict도 커밋 단위로 rebase 시점에 나온다. rebase 후에는 master에서 브랜치까지 일직선이라 conflict 없이 합쳐진다.
    나는 rebase를 merge용이라기보다 내가 분기한 head를 최신화할 때 쓰는 기능으로 본다. rebase and merge 방식은 일종의 선머지라고도 부른다. 합칠 때 fast-forward로 처리되므로 merge 커밋이 생기지 않는다. 다만 커밋을 다시 적용하므로 커밋 해시는 바뀐다.
    git rebase는 -i(interactive) 옵션도 제공해, 커밋을 reword로 이름을 바꾸거나 squash하거나 rebase 대상에서 제외할 수 있다.
  3. squash and merge
    브랜치의 변경 전체를 새 커밋 하나로 만들어 master에 올리는 방식이다. 1과 달리 merge 커밋이 아니라 부모가 하나인 일반 커밋이 생기고, 브랜치의 개별 커밋은 master 이력에 남지 않는다.
    ‘컬렉션 개발#1’, ’컬렉션 개발#2’처럼 의미 없는 커밋들을 묶을 수 있어, 잘못된 커밋이 있어도 amend나 cherry-pick 없이
    머지 과정에서 ’컬렉션 개발’로 squash하면 master에 커밋 하나만 남길 수 있다.

커밋을 직접 정리할 수 있는 브랜치라면 3 대신 rebase -i로 커밋을 정리한 뒤 2로 합쳐도 결과가 비슷하다. 작업 중간 커밋이 많이 쌓이는 feature 브랜치는 아래처럼 3을 쓴다.

Git Flow를 따를 때 정리한 사용처는 다음과 같다.

  • develop - feature 간 merge: 3 방식을 쓴다. feature 브랜치에는 작업 중간 커밋이 쌓이므로 squash로 불필요한 그래프 노드를 최소화할 수 있다.
  • master - develop 간 merge: 2 방식을 써서 master 그래프에 별도의 새 커밋을 만들지 않는다.
  • hotfix - develop, hotfix - master 간 merge: 1 방식을 써서 별도의 커밋을 남기고, hotfix 이후 코드 리뷰나 회고에 활용한다.

당시 프로젝트는 feature 간 코드 의존성이 거의 없어 한 프로젝트 안에 서로 다른 프로젝트가 여럿 들어 있는 구조라 이 구분이 필요한 경우가 적었지만, 다른 프로젝트에 쓰려고 정리해 두었다.