git merge 사용법 | conflict 해결, rebase squash 차이, 병합 전략
이번 포스팅은 git merge
방법에 대해 정리합니다. merge 방법부터 conflict 해결, merge commit의 특징 merge 전략, rebase, squash 와의 차이, git merge
명령어의 기타 옵션들을 알아보겠습니다.
1. branch merge 방법
git merge
명령어는 하나의 브랜치를 다른 브랜치로 병합할 때 사용하는 명령어입니다.
git branch 포스트에서 브랜치 이름이 가리키는 것은 해당 브랜치의 마지막 커밋이라는 이야기를 했는데요.
같은 개념으로, 브랜치를 병합하는 것은 결국 두 브랜치의 마지막 커밋의 변경사항을 모두 갖는 새 커밋을 만들고,
git merge
명령어를 실행한 작업 브랜치에 새로 등록하는 작업이라고 이해할 수도 있습니다.
git merge
명령어는 항상 merge commit 을 새로 생성할 브랜치에서 실행합니다.
예를 들어, main
브랜치에서 feature-a
브랜치를 병합한다면, 아래와 같은 명령어를 사용합니다.
$ git switch main
switched to branch 'main'
$ git merge feature-a
Merge made by the 'ort' strategy
위 예시에서는 2022년 변경된 Git의 최신 기본 merge 전략인 merge-ort
strategy 를 사용한 것을 알 수 있습니다.
1.1. ort 전략
다음 섹션으로 넘아가기 전에, Git의 최신 병합 알고리즘인 ort
strategy에 대해 간단히 알아봅니다.
ort
는 "Ostensibly Recursive's Twin" 의 줄임말로, 이전 기본 알고리즘인 recursive
전략을 대체하기 위해 만들었다는 의미를 갖습니다.
recursive
전략에 비해, conflict를 줄이고 이름이 수정된 파일도 알아서 관리한다고 합니다.
기본적으로 3-way merge 알고리즘을 기반으로 하는데, 이 방법은 두 브랜치의 동일 조상 커밋, 그리고 두 브랜치의 마지막 커밋 이렇게 3개에서 변경사항을 비교하여
merge하는 방법입니다. 두 브랜치를 병합할 때는 이전에 사용되던 resolve
, recursive
보다 더 나은 성능을 보이기 때문에 ort
전략을 주로 사용할 것입니다.
그외 알아둘 만한 전략은 두 개 이상의 브랜치를 병합하는 octopus
전략이나 subdirectory로 병합할 때 사용하는 subtree
전략 등이 있습니다.
지금은 이름만 익히고 넘어갑니다.
이러한 전략들은 다음과 같이 -s
혹은 --strategy
옵션으로 지정할 수 있습니다.
$ git merge -s recursive feature-a
2. merge conflict 해결 방법
merge conflict, 즉 병합 충돌은 두 브랜치에서 같은 파일의 같은 라인이 서로 다른 내용을 가지고 있을 때 발생합니다. strategy에 따로 옵션을 주지 않는 이상 Git은 데이터 보존을 위해 절대 임의로 특정 내용을 삭제하지 않습니다. 대신, 병합 과정을 잠시 멈추고 우리에게 선택권을 넘깁니다.
실제 실행 결과로 살펴보겠습니다.
일단, 1.md
파일을 main
과 develop
브랜치에서 각각 다른 내용으로 수정했습니다.
그 후, main
브랜치로 이동해서 develop
브랜치를 병합하려고 하니 병합 충돌이 발생합니다.
git commit
명령어의 -a
옵션이 처음이라면 git commit 포스트에서 확인해보세요.
이 때, Git은 충돌이 있는 파일의 내용에 <<<<<<<
HEAD
, =======
, >>>>>>>
branch-name
등의 기호를 추가해서 수정해야 할 부분을 정확히 알려줍니다.
1.md
파일을 열어보겠습니다.
Neovim 에디터로 열어 본 1.md
파일입니다. 우리는 이 부분에서 원하는 내용만 남기고 Git이 추가한 기호는 제거한 후, 커밋을 진행하면 됩니다.
VSCode 처럼 Git을 지원하는 에디터에서는 더 손쉽게 병합이 가능합니다. 하이라이트를 통해서 직관적으로 구분할 수 있으며, <<<<<<< HEAD
위의 4가지 문구의 버튼을 누르면 제외되는 내용와 Git의 기호는 자동으로 삭제됩니다.
내용 수정 후에는 일반적인 커밋처럼 git commit
명령어로 병합을 완료합니다.
3. rebase 와의 차이
git merge
명령어와 git rebase
명령어는 두 브랜치를 병합하는 방법이자, merge conflict 해결 방법이라는 공통점이 있습니다.
하지만 두 명령어가 동작하는 방식에 차이가 있기 때문에, 이 부분을 이해해야 원하는 바에 적합한 방법을 선택해 사용할 수 있습니다.
git merge
명령어는 위에서 본 것처럼 두 브랜치의 마지막 커밋을 부모 커밋으로 갖는 merge commit을 새로 생성합니다.
이 때문에 커밋 히스토리에는 2개의 브랜치에 해당하는 커밋들이 함께 존재합니다.
main
브랜치라는 큰 길에 feature-a
, feature-b
등의 샛길들이 나왔다가 사라집니다.
git rebase
명령어는 커밋 히스토리에서 샛길이 생기지 않도록 두 브랜치를 병합합니다.
동작 원리는 생각보다 간단합니다. 아래 이미지를 보면서 설명해보겠습니다.
공통 조상 커밋 A
에서 갈라진 feature-a
브랜치를 main
브랜치에 합친다고 가정할 때, git rebase
명령어는 feature-a
브랜치의 커밋과 같은 변경사항을 갖는 새로운 커밋을 만듭니다.
그림에서는 C
, D
커밋입니다.
그 다음, 이 두 커밋을 main
브랜치의 마지막 커밋인 B
에다가 붙입니다.
이렇게 하면 커밋 히스토리가 하나로 이어지면서도 feature-a
브랜치의 모든 변경사항이 main
브랜치에 fast-forward로 포함될 수 있는 결과를 만듭니다.
또한, 새 merge commit을 만들지 않아도 되기 때문에 히스토리는 더욱 심플합니다.
하지만 커밋 C
와 D
가 다른 브랜치에서 병합된 커밋인지 기존에 main
브랜치에 있었던 커밋인지, 한 눈에 알기는 쉽지 않습니다.
그래서, 기록이라는 관점에서 보았을 때는 정보를 잃는 결과이기도 합니다.
이 때문에, 팀에서 추구하는 방향에 따라, 적합한 상황에 merge 와 rebase 를 각각 사용해야 합니다.
git rebase
명령어에 대한 더 자세한 내용은 이 포스팅으로 정리해두었습니다.
4. squash 와의 차이
squash 는 Git에서 여러 개의 commit 을 하나로 압축하는 개념입니다.
따로, 명령어가 존재하지 않고, merge 나 rebase 명령어의 옵션으로 사용할 수 있습니다.
git merge
명령어에 --squash
옵션을 사용할 때 squash merge 라고 부릅니다.
아래 이미지로 squash merge 를 이해해봅시다.
현재 커밋 8c12
에서 main
과 develop
브랜치가 갈라진 상태입니다.
이 때, main
브랜치에서 squash merge 를 시도했고, 춤돌 없이 이루어졌습니다.
아래 git status
명령어의 결과를 보면, 모든 develop
브랜치의 커밋이 main
브랜치에 staging 되었습니다.
커밋한 이후 히스토리를 확인하면, develop
브랜치는 merged 되지 않은 상태로 머물러 있지만, 변경사항들은 main
의 새 커밋으로 등록되었습니다.
이렇게 이루어지는 것이 squash merge 입니다.
squash
옵션을 rebase 과정에서 사용할 수도 있는데, 이 때는 develop
브랜치의 히스토리가 위처럼 남이있지 않는다는 차이가 있습니다.
이 부분은 rebase 포스트에서 다시 다루겠습니다.
5. 마치며
Git 초보자들이 가장 당황하는 첫번째 경우가 merge conflict 상황이 아닐까 하는 생각이 듭니다. 하지만 merge 와 merge conflict 해결 방법은 Git 협업의 가장 핵심이기도 한 만큼, 충분히 이해하고 숙달하여 사용하시길 바랍니다. 이 포스팅이 도음이 되었길 바랍니다.