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 가 성공하면 아래와 같이 병합 커밋이 생성되지 않으며 단순히 가져오고자 하는 브랜치의 커밋 기록만 그대로 이어받습니다.
3-way merge
3-way merge 는 병합하려는 두 브랜치가 공통의 조상을 가지고 있으며 서로 다른 변경 사항을 포함하고 있을때 발생합니다. Git 은 이를 처리하기 위해 세 가지 정보를 사용합니다.
- 공통 조상: 두 브랜치의 분기점. 병합하려는 두 브랜치의 가장 최근의 공통 커밋
- 현재 브랜치: 공통 조상으로부터 현재 브랜치의 변경 사항
- 병합하려는 브랜치: 공통 조상으로부터 병합하려는 브랜치의 변경 사항
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 을 통해 병합이 진행되면 해당 병합에 대한 커밋 히스토리가 남습니다.
'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 |