Dev/VCS

Git fast-forward merge, 3-way merge

dragonhyeon 2024. 12. 24. 23:25
728x90
반응형

Git 병합

Git 에서 여러 브랜치에서 작업한 내용을 하나의 브랜치로 합치는 작업을 병합 (merge) 이라고 하며, 대표적으로 fast-forward merge 방식과 3-way merge 방식이 있습니다. 병합이 성공할 경우 3-way merge 에서는 병합 커밋이 자동으로 생성됩니다.

비병합 커밋 (Non-merge commit): 단일 브랜치에서 발생하는 커밋으로 하나의 부모 커밋만 가지며, 다른 브랜치와의 병합 없이 그 브랜치에서만 변화가 일어난 커밋입니다.
병합 커밋 (Merge Commit): 두 개 이상의 브랜치를 병합할 때 Git 이 자동으로 생성하는 커밋으로 두 개 이상의 부모 커밋을 가집니다. 하나의 병합을 진행하며 병합 충돌이 여러 번 발생하여도 병합 커밋은 최종적으로 병합이 성공하는 순간 한 개만 생성됩니다. git merge 의 설계는 병합 과정을 하나의 트랜잭션으로 처리하기 때문에 중간 과정에서 별도의 커밋을 생성하지 않고, 최종 병합 커밋에 모든 변경 사항이 포함됩니다. GitHub PR 에서 PR 이 열려 있는 동안 병합 충돌을 해결한 뒤 새 커밋이 추가되어 또 다시 병합 충돌이 발생하여 이를 해결한 경우, 각 병합 과정에서 발생한 두 병합 커밋은 서로 독립적이기 때문에 하나의 PR 내부에 병합 커밋이 여러 개 생성될 수 있으며 이는 위에서 설명한 개념과 충돌하는 상황이 아닙니다.
git merge a b c 와 같이 여러 개의 브랜치의 병합을 한 번에 처리할 수도 있습니다. 이 경우 Git 은 내부적으로 현재 브랜치와 a 를 병합한 후 b, c 를 순차적으로 병합합니다. 특정 브랜치를 병합하는 중에 충돌이 발생하면, 충돌을 해결한 뒤 git add 로 수정된 파일을 스테이징하고 git commit 혹은 git merge --continue 를 이용하여 계속해서 병합을 진행하면 됩니다. 이 경우에도 커밋은 모두 통틀어 병합 커밋 한 개만 생성됩니다. 하지만 GitHub 상에서는 여러 브랜치를 한 번에 병합하는 기능을 제공하지 않으며 실제 협업 환경에서 또한 거의 사용되지 않는 방식입니다.

fast-forward merge

fast-forward merge 는 기준이 되는 브랜치에 신규 커밋이 없고 가져오고자 하는 브랜치에만 신규 커밋이 있을 경우 발생합니다.

fast-forward merge 발생 상황

 

fast-forward merge 가 성공하면 아래와 같이 병합 커밋이 생성되지 않으며 단순히 가져오고자 하는 브랜치의 커밋 기록만 그대로 이어받습니다.

fast-forward merge 성공

3-way merge

3-way merge 는 병합하려는 두 브랜치가 공통의 조상을 가지고 있으며 서로 다른 변경 사항을 포함하고 있을때 발생합니다. Git 은 이를 처리하기 위해 세 가지 정보를 사용합니다.

  • 공통 조상: 두 브랜치의 분기점. 병합하려는 두 브랜치의 가장 최근의 공통 커밋
  • 현재 브랜치: 공통 조상으로부터 현재 브랜치의 변경 사항
  • 병합하려는 브랜치: 공통 조상으로부터 병합하려는 브랜치의 변경 사항

3-way merge 발생 상황

 

3-way merge 가 성공하면 아래와 같이 병합 커밋이 생성되며 현재 브랜치로 병합하려는 브랜치의 커밋 기록이 함께 남게됩니다.

3-way merge 성공

병합 충돌 해결

3-way merge 진행시 병합 충돌 (merge conflict) 이 발생할 수 있습니다.

git switch main    # main 브랜치로 이동
git merge develop  # develop 브랜치를 병합. 병합 충돌 발생

 

이러한 경우 직접 병합 충돌을 해결하고 다시 병합을 마무리해야 합니다.

git add <conflicted-files>  # 병합 충돌 파일 수정 후 스테이징 영역에 추가
git merge --continue        # 병합 재시도
git merge --continue 대신 git commit 을 사용해도 무방합니다. 병합 상황에서는 git merge --continue -m 혹은 git commit -m 과 같이 뒤에 -m 옵션을 추가하지 않으면 자동으로 병합 메시지를 생성해주며 원한다면 자동으로 실행되는 커밋 메세지 편집기에서 자동 생성된 병합 커밋 메세지를 한 번 더 수정한 후 커밋할 수 있습니다.

GitHub 에서의 병합 충돌 해결

GitHub 상에서 PR 생성시 병합 충돌이 발생한 경우 병합 충돌을 해결하는 방법은 크게 두 가지 입니다.

  • GitHub 웹 인터페이스상에서 병합 충돌 해결 후 merge 수락
  • 병합 충돌 이후 로컬에서 해당 병합에 대한 병합 커밋을 생성하여 원격 저장소에 다시 한 번 push

병합 충돌 이후 로컬에서 해당 병합에 대한 병합 커밋을 생성하는 방식은 다음과 같이 두 가지가 있습니다. (예시는 develop 브랜치에서 main 브랜치에 PR 을 보낸 상황입니다.)

  • PR 보낸 브랜치에서 병합 충돌 해결 후 push
git switch develop          # develop 브랜치로 이동

git fetch origin/main       # main 원격 추적 브랜치 최신화
git merge origin/main       # 최신화된 main 브랜치를 병합. 병합 충돌 발생 및 확인.

git add <conflicted-files>  # 병합 충돌 해결 후 스테이징 영역에 추가
git commit                  # 병합 마무리

git push origin develop     # 원격 저장소에 병합 충돌 해결 사항 반영

 

이후 PR 히스토리가 병합 가능으로 업데이트 되며 main 브랜치 관리자가 PR 을 승인하면 해당 PR 은 처리 완료되어 닫힙니다.

  • PR 을 받은 브랜치에서 직접 병합 충돌 해결하여 push
git switch main             # main 브랜치로 이동

git fetch origin/develop    # develop 원격 추적 브랜치 최신화
git merge origin/develop    # 최신화된 develop 브랜치를 병합. 병합 충돌 발생 및 확인.

git add <conflicted-files>  # 병합 충돌 해결 후 스테이징 영역에 추가
git commit                  # 병합 마무리

git push origin main        # 원격 저장소에 병합 충돌 해결 사항 반영

 

해당 PR 은 자동으로 처리 완료되어 곧바로 닫힙니다.

 

작업을 진행하며 주기적으로 fetch 를 통해 원격 저장소와 동기화를 완료하여 작업에 혼동이 생기지 않도록 하는것이 좋습니다. 특히 병합 충돌을 해결 할 때 원격 저장소와 동기화 되지 않은 브랜치를 이용해 병합 충돌을 해결하려 할 경우 원격 저장소에 push 가 정상적으로 수행되지 않을 수 있습니다.

PR 을 열기 전에 미리 로컬에서 병합을 시도하여 병합 충돌을 확인 및 해결한 뒤 PR 을 열수도 있습니다. 이러한 경우도 PR 상에는 브랜치 분기 이후의 모든 커밋 내용이 동일하게 등록되기 때문에 어느 방법을 선택해도 무방합니다.
fast-forward merge 라고 할지라도 GitHub 상에서 PR 을 통해 병합이 진행되면 해당 병합에 대한 커밋 히스토리가 남습니다.
728x90
반응형

'Dev > VCS' 카테고리의 다른 글

Git 브랜치 삭제  (3) 2024.12.29
Git rebase, squash  (1) 2024.12.27
Git 협업 방법 clone, fork  (1) 2024.12.17
Git 원격 브랜치, 원격 추적 브랜치, 트래킹 브랜치  (4) 2024.12.17
Git 스테이징 영역  (2) 2024.12.13