[Git] 깃 머지
깃 머지에 대하여 알아보자
소개
병합(Merge)이란 서로 다른 브랜치의 변경 사항을 하나로 통합하는 과정이다. 여러 개발자가 각자의 브랜치에서 작업한 내용을 메인 브랜치에 통합할 때 사용한다. Git에서는 git merge
명령어를 사용하여 브랜치를 병합할 수 있다.
병합을 실행하기 위해서는 먼저 병합의 대상이 될 브랜치로 이동한 후, 병합할 브랜치를 지정하여 명령어를 실행한다:
$ git switch main # 병합 대상 브랜치로 이동
$ git merge feature # feature 브랜치를 현재 브랜치(main)에 병합
Fast-Forward 병합
Fast-Forward 병합은 가장 단순한 형태의 병합이다. 이는 병합하려는 브랜치가 현재 브랜치의 직접적인 자손
인 경우에 발생한다. 즉, 현재 브랜치 이후에 새로운 커밋들이 추가되지 않은 상태에서 다른 브랜치의 변경 사항을 병합할 때 발생한다.
이 경우 Git은 단순히 현재 브랜치의 포인터를 병합하려는 브랜치의 최신 커밋으로 이동
시킨다. 별도의 병합 커밋을 생성하지 않는다.
그래프로 표현하면 다음과 같다:
# 병합 전
A---B---C (main)
\
D---E (feature)
# 병합 후 (Fast-Forward)
A---B---C---D---E (HEAD -> main, feature)
병합 커밋
Fast-Forward 병합이 불가능한 경우, 즉 병합될 브랜치에 새로운 커밋이 있다면 Git은 병합 커밋(Merge Commit)을 생성한다. 이는 두 브랜치의 변경 사항을 모두 포함하는 새로운 커밋이다.
이러한 병합을 3-way 병합
이라고도 하는데, 이는 공통 조상 커밋과 두 브랜치의 최신 커밋, 총 3개의 커밋을 기반으로 병합이 이루어지기 때문이다.
그래프로 표현하면 다음과 같다:
# 병합 전
A---B---C---F (main)
\
D---E (feature)
# 병합 후 (3-way 병합)
A---B---C---F---G (HEAD -> main)
\ /
D---E (feature)
여기서 G는 병합 커밋으로, F와 E 커밋의 변경 사항을 모두 포함한다.
병합 충돌
병합할 때 충돌(Conflict)이 발생하는 경우가 있다. 이는 Git이 자동으로 해결해주지 못하는 상황이다.
충돌은 주로 두 브랜치에서 같은 파일의 같은 부분을 서로 다르게 수정했을 때 발생한다. 이 경우 Git은 어떤 변경 사항을 적용해야 할지 결정할 수 없어 사용자의 개입을 요구한다.
Git 충돌이 발생하면 다음과 같은 메시지가 표시된다:
$ git merge bye
. 자동 병합: text.txt
. 충돌 (내용): text.txt에 병합 충돌
. 자동 병합이 실패했습니다. 충돌을 바로잡고 결과물을 커밋하십시오.
충돌이 발생한 파일을 열어보면 다음과 같은 형태로 충돌 부분이 표시된다:
Hello
a
<<<<<<< HEAD
b Hi, I am Hi!
=======
b Hi, I am bye!
>>>>>>> bye
c
dddddddddddddddd
ee
ffff
g
이 표시는 다음과 같은 의미를 가진다:
<<<<<<< HEAD
: 현재 브랜치(HEAD)의 내용 시작=======
: 현재 브랜치와 병합하려는 브랜치의 내용 구분선>>>>>>> bye
: 병합하려는 브랜치(여기서는 'bye')의 내용 끝
충돌을 해결하기 위해서는 파일을 열어 충돌 부분을 수정해야 한다. 충돌 마커(<<<<<<<
, =======
, >>>>>>>
)를 제거하고, 최종적으로 유지하고 싶은 내용으로 파일을 수정한다.
예를 들어, 위 충돌을 해결하려면 다음과 같이 수정할 수 있다:
Hello
a
b Hi, I am merged!
c
dddddddddddddddd
ee
ffff
g
충돌을 해결한 후에는 변경된 파일을 스테이징하고 커밋해야 한다:
$ git add text.txt
$ git commit -m "Resolve merge conflict in text.txt"
이렇게 하면 병합 과정이 완료되고 병합 커밋이 생성된다.
병합 전략
Git에서는 다양한 병합 전략을 제공한다. 각 전략은 서로 다른 결과물과 커밋 히스토리를 생성하므로, 프로젝트의 요구사항과 팀의 워크플로우에 맞게 선택하는 것이 중요하다.
1. 기본 병합
기본 병합은 Git의 기본 전략으로, 상황에 따라 Fast-Forward 또는 3-way 병합을 사용한다.
$ git switch main
$ git merge feature
예시:
# 병합 전
A---B---C (main)
\
D---E (feature)
# Fast-Forward 병합 후
A---B---C---D---E (HEAD -> main, feature)
만약 main 브랜치에 추가 커밋이 있다면:
# 병합 전
A---B---C---F (main)
\
D---E (feature)
# 3-way 병합 후
A---B---C---F---G (HEAD -> main)
\ /
D---E (feature)
여기서 G는 병합 커밋으로, 두 브랜치의 변경사항을 모두 포함한다.
2. 리베이스
리베이스는 한 브랜치의 변경사항을 다른 브랜치 위로 재배치하는 방식이다. 이는 선형적인 커밋 히스토리를 만들어 주어 프로젝트 히스토리를 더 깔끔하게 유지할 수 있다.
$ git switch feature
$ git rebase main
$ git switch main
$ git merge feature # 이제 Fast-Forward 병합이 가능
예시:
# 리베이스 전
A---B---C---F (main)
\
D---E (feature)
# feature 브랜치에서 main으로 리베이스 후
A---B---C---F (main)
\
D'---E' (HEAD -> feature)
# 이후 main에서 feature를 병합 (Fast-Forward)
A---B---C---F---D'---E' (HEAD -> main, feature)
여기서 D'와 E'는 원래 D와 E 커밋의 변경사항을 포함하지만, 새로운 커밋 해시를 가진 새 커밋이다. 리베이스는 커밋 히스토리를 재작성하므로, 이미 공유된 브랜치에는 사용하지 않는 것이 좋다.
3. 스쿼시
스쿼시 병합은 브랜치의 모든 커밋을 하나의 커밋으로 압축하여 병합한다. 이는 기능 개발 과정의 중간 커밋들을 숨기고, 완성된 기능만을 메인 브랜치에 추가하고 싶을 때 유용하다.
$ git switch main
$ git merge --squash feature
$ git commit -m "Add feature X"
예시:
# 스쿼시 병합 전
A---B---C---F (main)
\
D---E (feature)
# 스쿼시 병합 후
A---B---C---F---G' (HEAD -> main)
\
D---E (feature)
여기서 G'는 D와 E 커밋의 모든 변경사항을 포함하는 단일 커밋이다. feature 브랜치는 그대로 유지되며, main 브랜치와의 연결 관계는 커밋 히스토리에 표시되지 않는다.
4. No-Fast-Forward
Fast-Forward가 가능한 상황에서도 병합 커밋을 생성하도록 강제하는 전략이다. 이는 기능 브랜치의 존재를 커밋 히스토리에 명시적으로 남기고 싶을 때 유용하다.
$ git switch main
$ git merge --no-ff feature
예시:
# 병합 전 (Fast-Forward 가능한 상황)
A---B---C (main)
\
D---E (feature)
# --no-ff 옵션으로 병합 후
A---B---C-------F (HEAD -> main)
\ /
D---E (feature)
여기서 F는 병합 커밋으로, Fast-Forward가 가능했음에도 생성된다.
5. Ours/Theirs 전략
충돌 발생 시 자동으로 한쪽의 변경사항을 선택하는 전략이다.
# 현재 브랜치(main)의 변경사항을 항상 선택
$ git merge -X ours feature
# 병합하려는 브랜치(feature)의 변경사항을 항상 선택
$ git merge -X theirs feature
이 전략은 충돌이 예상되지만 어떤 쪽의 변경사항을 선택할지 이미 결정된 경우에 유용하다.
각 병합 전략은 상황과 목적에 따라 장단점이 있다. 일반적으로:
- 기본 병합: 간단하고 안전하며, 모든 변경 내역을 보존한다.
- 리베이스: 깔끔한 선형 히스토리를 만들지만, 공유 브랜치에서는 주의해야 한다.
- 스쿼시: 깔끔한 커밋 히스토리를 원하지만 개발 과정의 세부 단계는 중요하지 않을 때 유용하다.
- No-Fast-Forward: 브랜치 구조와 기능 개발 과정을 명확히 보존하고 싶을 때 사용한다.
팀의 워크플로우와 프로젝트의 요구사항에 맞는 전략을 선택하는 것이 중요하다.
결론
Git의 병합 기능은 여러 개발자가 동시에 작업할 수 있게 해주는 핵심 기능이다. Fast-Forward 병합, 3-way 병합, 그리고 충돌 해결 방법을 이해하면 효과적으로 협업할 수 있다.
병합 과정에서 충돌이 발생하는 것은 자연스러운 일이며, 이를 해결하는 과정을 통해 코드의 품질을 유지할 수 있다. 다양한 병합 전략을 상황에 맞게 활용하면 더욱 효율적인 개발 워크플로우를 구축할 수 있다.