08. 병합(Merge)과 충돌(Conflict)
- 갈라진 브랜치를 다시 하나로 합치는 병합을 이해한다
- Fast-forward 병합과 3-way 병합의 차이를 그림으로 안다
- 충돌이 왜 생기는지 이해하고, 직접 해결할 수 있다
- 병합 후 브랜치를 정리한다
8.1 병합이란?
6장에서 feature 브랜치로 평행 세계를 만들어 작업했습니다. 작업이 끝났으면 이제 그 결과물을 다시 main(줄기)에 합쳐야 합니다. 이것이 병합(Merge)입니다.
(D) ← (E) ← feature (완성된 기능)
↑
(A) ← (B) ← (C) ← main
│
└── git merge ──► main에 D, E를 합친다
🌳 나무 비유로, 가지(feature)에서 키운 열매를 줄기(main)에 다시 붙이는 작업입니다.
병합의 기본 흐름은 항상 같습니다.
# 1. "합쳐서 받을" 브랜치로 이동 (대상 = main) git switch main # 2. "합쳐 넣을" 브랜치를 지정 (소스 = feature) git merge feature
git merge feature는 "현재 브랜치에 feature를 가져와 합친다"는 뜻입니다. 즉 항상 "받을 쪽으로 먼저 이동한 뒤" 병합합니다. main에 feature를 합치려면 main에서 실행해야 합니다.8.2 병합 방식 ① — Fast-forward (빨리 감기)
가장 단순한 병합입니다. main이 갈라진 뒤 아무 변화가 없었다면, Git은 그냥 main 포인터를 앞으로 "빨리 감기"만 하면 됩니다.
병합 전 — feature가 main보다 앞서 있고, main은 갈라진 뒤 변화 없음:
gitGraph
commit id: "A"
commit id: "B"
commit id: "C"
branch feature
checkout feature
commit id: "D"
commit id: "E"
병합 후 — main 포인터가 E까지 "빨리 감기"로 전진 (새 커밋 없음):
gitGraph
commit id: "A"
commit id: "B"
commit id: "C"
commit id: "D"
commit id: "E (main=feature)"
git switch main git merge feature
Updating c3d4e5f..a1b2c3d Fast-forward feature.txt | 1 + 1 file changed, 1 insertion(+)
- 새로운 병합 커밋이 생기지 않습니다. main이 feature 위치로 이동할 뿐입니다.
- 이력이 한 줄로 깔끔하게 유지됩니다.
Fast-forward를 막고 싶다면
기능 단위 이력을 명확히 남기려고, 일부러 병합 커밋을 만들 수도 있습니다.
git merge --no-ff feature
이러면 fast-forward 가능한 상황에서도 병합 커밋을 강제로 만듭니다. "여기서 feature가 합쳐졌다"는 기록이 남습니다.
8.3 병합 방식 ② — 3-way Merge (3방향 병합)
main도 갈라진 뒤 따로 전진했다면, 단순 빨리 감기로는 합칠 수 없습니다. 양쪽이 모두 변했기 때문입니다. 이때 Git은 세 개의 커밋을 비교해 새 "병합 커밋"을 만듭니다.
gitGraph
commit id: "A"
commit id: "B"
commit id: "C"
branch feature
checkout feature
commit id: "D"
commit id: "E"
checkout main
commit id: "F"
commit id: "G"
공통 조상 C에서 갈라진 뒤, feature(D·E)와 main(F·G)이 양쪽 모두 전진했습니다.
비교 대상 세 가지:
- 공통 조상 커밋 (C) — 두 브랜치가 갈라진 지점
- main의 끝 (G)
- feature의 끝 (E)
git switch main git merge feature
Git은 C를 기준으로 양쪽 변경을 합쳐 새로운 병합 커밋(M)을 만듭니다.
gitGraph
commit id: "A"
commit id: "B"
commit id: "C"
branch feature
checkout feature
commit id: "D"
commit id: "E"
checkout main
commit id: "F"
commit id: "G"
merge feature id: "M (병합커밋)"
병합 커밋 M은 부모가 둘(G와 E)인 특별한 커밋입니다.
- 병합 커밋(M)은 부모가 둘인 특별한 커밋입니다.
- 병합 시 에디터가 열려 병합 메시지를 작성하게 됩니다 (기본 메시지 그대로 두어도 됨).
8.4 충돌(Conflict)은 왜 생길까?
3-way 병합에서 양쪽이 같은 파일의 같은 부분을 다르게 고쳤다면, Git은 "둘 중 무엇이 맞는지" 판단할 수 없습니다. 이때 충돌(Conflict)이 발생하고, Git은 사람에게 결정을 넘깁니다.
main에서: greeting.txt 1번 줄 → "Hello World"
feature에서: greeting.txt 1번 줄 → "Hi there"
↓
어느 게 맞아? → Git: "사람이 정해줘!" (충돌)
8.5 충돌 만들고 해결하기 (실습)
직접 충돌을 만들어 해결해봅시다. 가장 좋은 학습법입니다.
1단계: 충돌 상황 만들기
# main에서 파일 생성/커밋 git switch main echo "Hello" > greeting.txt git add greeting.txt git commit -m "Add greeting on main" # feature 브랜치 만들어 같은 줄 수정 git switch -c feature echo "Hi there" > greeting.txt git add greeting.txt git commit -m "Change greeting on feature" # main으로 돌아가 같은 줄을 다르게 수정 git switch main echo "Hello World" > greeting.txt git add greeting.txt git commit -m "Change greeting on main"
2단계: 병합 시도 → 충돌 발생
git merge feature
Auto-merging greeting.txt CONFLICT (content): Merge conflict in greeting.txt Automatic merge failed; fix conflicts and then commit the result.
git status를 보면 충돌 파일을 알려줍니다.
git status
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: greeting.txt
3단계: 충돌 표시 이해하기
greeting.txt를 열면 다음과 같습니다.
<<<<<<< HEAD Hello World ======= Hi there >>>>>>> feature
| 표시 | 의미 |
|---|---|
<<<<<<< HEAD | 현재 브랜치(main)의 내용 시작 |
======= | 경계선 |
>>>>>>> feature | 합쳐 넣는 브랜치(feature)의 내용 끝 |
위쪽(HEAD ~ =======)이 내 브랜치(main), 아래쪽(======= ~ feature)이 상대 브랜치(feature)입니다.
4단계: 수동으로 충돌 해결
원하는 최종 모습으로 직접 편집합니다. 충돌 표시(<<<, ===, >>>)는 반드시 모두 삭제해야 합니다.
# 예시 1: main 것을 선택 Hello World # 예시 2: feature 것을 선택 Hi there # 예시 3: 둘 다 합치기 (직접 작성) Hello World and Hi there
5단계: 해결 완료 표시 후 커밋
git add greeting.txt # "이 충돌 해결했어요" 표시 git commit # 병합 커밋 완성 (메시지는 기본값 OK)
[main 7a8b9c0] Merge branch 'feature'
🎉 충돌 해결 완료! 이력을 보면 병합 커밋이 생긴 것을 확인할 수 있습니다.
git log --oneline --graph
8.6 충돌 해결을 돕는 도구들
어느 쪽을 통째로 선택하기
# 충돌 파일에서 내 브랜치(main) 버전 전체 선택 git checkout --ours greeting.txt # 상대 브랜치(feature) 버전 전체 선택 git checkout --theirs greeting.txt git add greeting.txt
병합 자체를 취소하기
해결이 너무 복잡해 "없던 일로" 하고 싶다면:
git merge --abort
병합 시도 이전의 깨끗한 상태로 완전히 되돌아갑니다. 막막할 때 유용한 탈출 버튼입니다.
머지툴 사용
git mergetool # 설정된 시각적 충돌 해결 도구 실행
소스트리에서는 충돌 파일을 우클릭해 "Resolve Conflicts → Launch External Merge Tool"로 좌/우 비교 화면에서 클릭만으로 해결할 수 있습니다. 입문자에게는 텍스트 편집보다 직관적입니다.
8.7 병합 위치 지정과 기준 브랜치
병합 방향을 명확히 다시 정리합니다.
# 항상: 받을 브랜치로 이동 → 합쳐 넣을 브랜치 지정 git switch main # 받을 쪽 git merge feature # 넣을 쪽
특정 커밋이나 다른 브랜치를 기준으로 합칠 수도 있지만, 입문 단계에서는 "main으로 가서 feature를 merge"라는 기본 패턴만 확실히 익히면 됩니다.
8.8 병합 여부 확인과 브랜치 정리
병합이 끝난 브랜치는 보통 삭제합니다(6장).
# main에 이미 병합된 브랜치 목록 보기 git branch --merged # 아직 병합 안 된 브랜치 목록 git branch --no-merged # 병합 끝난 브랜치 안전 삭제 git branch -d feature
git branch --merged로 "이미 합쳐져서 지워도 안전한 브랜치"를 확인한 뒤 정리하면 실수를 줄일 수 있습니다.8.9 이 장에서 배운 것 (요약)
- 병합 = 갈라진 브랜치를 다시 합치기. 받을 쪽으로 이동 후
git merge 소스 - Fast-forward: main이 그대로일 때 포인터만 전진(병합 커밋 없음)
- 3-way merge: 양쪽 다 전진했을 때 공통 조상 기준으로 병합 커밋 생성
- 충돌은 오류가 아니라 "사람의 판단을 기다리는 정상 상태"
- 충돌 해결: 파일 편집 →
<<< === >>>제거 →git add→git commit - 탈출구:
git merge --abort/ 통째 선택:--ours/--theirs git branch --merged로 확인 후 병합된 브랜치 정리
✍️ 확인 문제
- Fast-forward 병합과 3-way 병합은 각각 언제 일어나나요?
- 충돌 파일의
<<<<<<< HEAD아래 내용은 어느 브랜치 것인가요? - 병합 도중 도저히 안 되겠어서 처음 상태로 돌리려면?
다음 장에서는 병합과 비슷하지만 이력을 더 깔끔하게 만드는 리베이스(Rebase)를 배웁니다. → 09_리베이스.md