포스트

[GitHub 100일 챌린지] Day 47 - 3-Way Merge

[GitHub 100일 챌린지] Day 47 - 3-Way Merge

100일 챌린지 Day 47 - Fast-Forward가 불가능할 때 사용하는 3-Way 병합을 배웁니다.

배울 내용

  1. 3-Way Merge란
  2. 3-Way Merge 실습
  3. Merge Commit 이해하기

Topic1. 3-Way Merge란

Fast-Forward가 불가능할 때 Git이 사용하는 병합 방식입니다.

3-Way Merge의 개념

언제 필요한가?:

gitGraph
    commit id: "C1"
    commit id: "C2"
    branch feature
    checkout feature
    commit id: "C3"
    checkout main
    commit id: "C4"
    checkout feature
    commit id: "C5"
    checkout main
    merge feature tag: "3-Way!"

Fast-Forward 불가능한 상황:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
초기:
C1 ← C2
      ↑
    main

브랜치 생성:
C1 ← C2
      ↑
    main, feature

feature에서 작업:
C1 ← C2 ← C3 ← C5
      ↑         ↑
    main      feature

main에서도 작업! (Fast-Forward 불가!)
      C3 ← C5 (feature)
     ↗
C1 ← C2
     ↘
      C4 (main)

❌ Fast-Forward 불가능!
✅ 3-Way Merge 필요!

3-Way Merge의 원리

3개의 커밋을 비교:

1
2
3
4
5
6
7
1. 공통 조상 (Base): C2
2. main의 최신 커밋: C4
3. feature의 최신 커밋: C5

Git이 자동으로 병합:
C2 → C4의 변경사항
C2 → C5의 변경사항

병합 결과:

gitGraph
    commit id: "C1"
    commit id: "C2"
    branch feature
    checkout feature
    commit id: "C3"
    checkout main
    commit id: "C4"
    checkout feature
    commit id: "C5"
    checkout main
    merge feature tag: "M (Merge Commit)"

Merge Commit 생성:

1
2
3
4
5
      C3 ← C5
     ↗       ↘
C1 ← C2 ← C4 ← M (병합 커밋)
              ↑
            main

해보기: 3-Way Merge 조건 만들기

시나리오: 두 브랜치가 동시에 진행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 프로젝트 초기화
mkdir three-way-demo
cd three-way-demo
git init

# 초기 커밋
echo "# Project" > README.md
git add README.md
git commit -m "C1: Initial commit"

echo "v1.0" > VERSION
git add VERSION
git commit -m "C2: Add version"

# 현재 상태
git log --oneline
# C2 Add version
# C1 Initial commit

# feature 브랜치 생성
git checkout -b feature/docs

# feature에서 작업
echo "# Documentation" > DOCS.md
git add DOCS.md
git commit -m "C3: Add docs"

echo "# Installation" >> DOCS.md
git add DOCS.md
git commit -m "C5: Update docs"

# main으로 돌아가기
git checkout main

# main에서도 작업 (중요!)
echo "# License: MIT" > LICENSE
git add LICENSE
git commit -m "C4: Add license"

# 브랜치 그래프 확인
git log --oneline --all --graph

# 출력:
* C4 (HEAD -> main) Add license
| * C5 (feature/docs) Update docs
| * C3 Add docs
|/
* C2 Add version
* C1 Initial commit

# ✅ 3-Way Merge 조건 충족!

결과

3-Way Merge 필요 조건:

1
2
3
4
✅ main과 feature가 분기됨
✅ 두 브랜치 모두 새 커밋이 있음
✅ Fast-Forward 불가능
✅ 공통 조상 커밋이 존재 (C2)

Fast-Forward vs 3-Way 비교:

1
2
3
4
5
6
7
8
9
Fast-Forward:
main    → C2
feature → C2 → C3 → C4
→ main을 C4로 이동만 하면 됨

3-Way Merge:
main    → C2 → C4
feature → C2 → C3 → C5
→ C4와 C5를 병합해야 함!

Topic2. 3-Way Merge 실습

실제로 3-Way Merge를 수행해봅니다.

기본 3-Way Merge

병합 명령어:

1
2
3
4
5
6
7
8
9
10
11
12
13
# main으로 전환
git checkout main

# feature 브랜치 병합
git merge feature/docs

# 출력:
Merge made by the 'recursive' strategy.
 DOCS.md | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 DOCS.md

# "recursive strategy" → 3-Way Merge!

병합 후 상태:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 히스토리 확인
git log --oneline --graph

# 출력:
*   M (HEAD -> main) Merge branch 'feature/docs'
|\
| * C5 (feature/docs) Update docs
| * C3 Add docs
* | C4 Add license
|/
* C2 Add version
* C1 Initial commit

# 병합 커밋 M이 생성됨!

그래픽 구조:

1
2
3
      C3 ← C5 (feature/docs)
     ↗       ↘
C1 ← C2 ← C4 ← M (main, HEAD)

Merge Commit의 특징

두 개의 부모 커밋:

1
2
3
4
5
6
7
8
9
10
11
12
# 병합 커밋 상세 정보
git show M

# 출력:
commit M (부모1: C4, 부모2: C5)
Merge: C4 C5
Author: Your Name
Date: ...

    Merge branch 'feature/docs'

# 두 부모를 가진 특별한 커밋!

병합 커밋 메시지 커스터마이징:

1
2
3
4
5
6
# 병합 시 메시지 직접 입력
git merge feature/docs -m "Merge: Add documentation to main"

# 에디터로 작성
git merge feature/docs
# (에디터가 열림)

해보기: 완전한 3-Way Merge 워크플로우

실무 시나리오: 팀 협업 상황

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# === 프로젝트 설정 ===
mkdir team-project
cd team-project
git init

echo "# Team App v1.0" > README.md
git add README.md
git commit -m "Initial release"

# === 개발자 A: 검색 기능 ===
git checkout -b feature/search

cat > search.js << 'EOF'
function search(query) {
  const results = database.filter(item =>
    item.name.includes(query)
  );
  return results;
}
EOF

git add search.js
git commit -m "feat: Add search functionality"

cat > search.test.js << 'EOF'
describe('Search', () => {
  test('finds matching items', () => {
    expect(search('test')).toBeDefined();
  });
});
EOF

git add search.test.js
git commit -m "test: Add search tests"

# === 개발자 B: 알림 기능 (동시 개발!) ===
git checkout main

git checkout -b feature/notification

cat > notify.js << 'EOF'
function notify(message) {
  const notification = new Notification({
    title: 'Team App',
    body: message
  });
  notification.show();
}
EOF

git add notify.js
git commit -m "feat: Add notification system"

# === main에 핫픽스 ===
git checkout main

echo "v1.0.1" > VERSION
git add VERSION
git commit -m "chore: Bump version to 1.0.1"

# === 현재 상태 확인 ===
git log --oneline --all --graph

# 출력:
* H1 (HEAD -> main) chore: Bump version to 1.0.1
| * N1 (feature/notification) feat: Add notification system
|/
| * S2 (feature/search) test: Add search tests
| * S1 feat: Add search functionality
|/
* I1 Initial release

# === 검색 기능 병합 (3-Way!) ===
git checkout main
git merge feature/search

# 출력:
Merge made by the 'recursive' strategy.
 search.js      | 5 +++++
 search.test.js | 5 +++++
 2 files changed, 10 insertions(+)

# === 알림 기능 병합 (3-Way!) ===
git merge feature/notification

# 출력:
Merge made by the 'recursive' strategy.
 notify.js | 7 +++++++
 1 file changed, 7 insertions(+)

# === 최종 히스토리 ===
git log --oneline --graph

# 출력:
*   M2 (HEAD -> main) Merge branch 'feature/notification'
|\
| * N1 (feature/notification) feat: Add notification system
* |   M1 Merge branch 'feature/search'
|\ \
| * | S2 (feature/search) test: Add search tests
| * | S1 feat: Add search functionality
| |/
* | H1 chore: Bump version to 1.0.1
|/
* I1 Initial release

# 두 개의 병합 커밋 M1, M2 생성!

결과

3-Way Merge의 특징:

gitGraph
    commit id: "I1: Initial"
    branch feature/search
    checkout feature/search
    commit id: "S1: Search"
    commit id: "S2: Tests"
    checkout main
    commit id: "H1: Hotfix"
    merge feature/search tag: "M1"
    branch feature/notification
    checkout feature/notification
    commit id: "N1: Notify"
    checkout main
    merge feature/notification tag: "M2"

병합 커밋의 역할:

1
2
3
4
✅ 두 브랜치 히스토리 보존
✅ 누가 언제 무엇을 병합했는지 명확
✅ 되돌리기 쉬움 (병합 커밋만 revert)
✅ 브랜치 작업 이력 추적 가능

Topic3. Merge Commit 이해하기

Merge Commit은 특별한 커밋입니다.

Merge Commit의 구조

일반 커밋 vs 병합 커밋:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 일반 커밋
git show C4

# 출력:
commit C4
Author: ...
Date: ...

    Add license

diff --git a/LICENSE b/LICENSE
+# License: MIT

# 병합 커밋
git show M

# 출력:
commit M
Merge: C4 C5    ← 두 개의 부모!
Author: ...
Date: ...

    Merge branch 'feature/docs'

부모 커밋 확인:

1
2
3
4
5
6
7
8
# 첫 번째 부모 (main의 커밋)
git show M^1

# 두 번째 부모 (feature의 커밋)
git show M^2

# 모든 부모
git log M^1..M^2

Merge Commit 메시지 작성

기본 메시지:

1
2
3
4
git merge feature/login

# 자동 생성 메시지:
Merge branch 'feature/login'

권장 메시지 형식:

1
2
3
4
5
6
7
8
9
git merge feature/login -m "Merge: Add user login feature

- Implement login form with validation
- Add authentication API integration
- Include unit and integration tests
- Update documentation

Reviewed-by: Tech Lead
Tested-by: QA Team"

에디터로 작성:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# --no-ff로 에디터 열기
git merge --no-ff feature/payment

# 에디터에서:
Merge: Integrate payment system

## Changes
- Stripe payment integration
- Payment history tracking
- Receipt generation

## Testing
- [x] Unit tests (95% coverage)
- [x] Integration tests
- [x] Manual testing in staging

## Deployment Notes
- Requires STRIPE_API_KEY environment variable
- Database migration needed

Closes #123

해보기: Merge Commit 분석

시나리오: 병합 커밋 상세 분석

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 병합 상황 만들기
mkdir merge-analysis
cd merge-analysis
git init

echo "base" > file.txt
git add file.txt
git commit -m "Base commit"

# 브랜치 A
git checkout -b branch-a
echo "feature A" >> file.txt
git add file.txt
git commit -m "Add feature A"

# 브랜치 B
git checkout main
git checkout -b branch-b
echo "feature B" >> file.txt
git add file.txt
git commit -m "Add feature B"

# 병합
git checkout main
git merge branch-a
git merge branch-b

# === 병합 커밋 분석 ===
# 1. 병합 커밋 찾기
git log --merges --oneline

# 출력:
M2 Merge branch 'branch-b'
M1 Merge branch 'branch-a'

# 2. 병합 커밋 상세 정보
git show M1

# 출력:
commit M1
Merge: base-commit feature-a-commit
Author: ...
Date: ...

    Merge branch 'branch-a'

# 3. 부모 커밋 확인
git log M1^1..M1^2 --oneline

# 출력: branch-a의 커밋들

# 4. 병합으로 들어온 변경사항
git diff M1^1 M1

# 출력: feature A 변경사항

# 5. 병합 커밋만 보기
git log --first-parent --oneline

# 출력:
M2 Merge branch 'branch-b'
M1 Merge branch 'branch-a'
base Base commit

Merge Commit 되돌리기

병합 커밋 revert:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 병합 커밋 되돌리기
git revert -m 1 M

# -m 1: 첫 번째 부모 (main)를 유지
# -m 2: 두 번째 부모 (feature)를 유지

# 예시
git checkout main
git merge feature/experimental

# 문제 발생! 병합 취소
git revert -m 1 HEAD

# feature의 모든 변경사항이 제거됨

병합 전으로 완전히 되돌리기:

1
2
3
4
5
6
7
# 병합 전 커밋으로 reset
git reset --hard HEAD^

# 또는 병합 전 커밋 SHA로
git reset --hard C4

# ⚠️ 주의: push한 경우 사용 금지!

결과

Merge Commit의 특징 정리:

1
2
3
4
5
6
7
8
9
10
11
12
13
구조:
✅ 두 개의 부모 커밋을 가짐
✅ 브랜치 히스토리 보존
✅ 병합 시점과 이유 기록

장점:
✅ 전체 히스토리 추적 가능
✅ 기능 단위 되돌리기 쉬움
✅ 누가 언제 병합했는지 명확

단점:
❌ 히스토리가 복잡해질 수 있음
❌ 그래프가 지저분해질 수 있음

병합 커밋 활용 팁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 의미있는 병합 메시지 작성
git merge feature/X -m "Merge: Add feature X

- 주요 변경사항 설명
- 리뷰 정보
- 테스트 결과"

# 2. 병합 전 리뷰 확인
git diff main...feature/X

# 3. 병합 후 검증
git log --first-parent
git show HEAD

# 4. 필요시 되돌리기
git revert -m 1 HEAD

정리

오늘 배운 내용:

1. 3-Way Merge:

  • Fast-Forward 불가능할 때 사용
  • 공통 조상, main, feature 3개 커밋 비교
  • 자동으로 병합 커밋 생성

2. 3-Way Merge 실습:

1
2
3
4
5
git checkout main
git merge feature/branch

# 출력: "recursive strategy"
# → 3-Way Merge 수행됨

3. Merge Commit:

  • 두 개의 부모 커밋
  • 브랜치 히스토리 보존
  • 의미있는 메시지 작성 중요

4. Merge Commit 관리:

1
2
3
4
5
6
7
8
9
# 병합 커밋 보기
git log --merges

# 부모 커밋 확인
git show M^1
git show M^2

# 되돌리기
git revert -m 1 M

다음 단계: Day 48에서 Merge Conflict를 배웁니다.

완료 체크:

  • 3-Way Merge의 개념을 이해했다
  • 3-Way Merge를 수행할 수 있다
  • Merge Commit의 구조를 안다
  • Merge Commit을 관리할 수 있다

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.