포스트

[GitHub 100일 챌린지] Day 48 - Merge Conflict 해결

[GitHub 100일 챌린지] Day 48 - Merge Conflict 해결

100일 챌린지 Day 48 - 병합 충돌(Merge Conflict)을 이해하고 해결하는 방법을 배웁니다.

배울 내용

  1. Merge Conflict란
  2. Merge Conflict 해결 방법
  3. Conflict 예방 전략

Topic1. Merge Conflict란

두 브랜치가 같은 파일의 같은 부분을 수정했을 때 발생합니다.

Merge Conflict의 원인

충돌 발생 시나리오:

gitGraph
    commit id: "C1: config.js 생성"
    commit id: "C2"
    branch feature-a
    checkout feature-a
    commit id: "C3: API_URL 수정"
    checkout main
    branch feature-b
    checkout feature-b
    commit id: "C4: API_URL 다르게 수정"
    checkout main
    merge feature-a
    merge feature-b tag: "❌ CONFLICT!"

왜 충돌이 일어나나요?:

1
2
3
4
5
6
7
8
9
10
11
12
초기 파일 (C1):
const API_URL = "http://localhost:3000"

feature-a (C3):
const API_URL = "http://api.production.com"

feature-b (C4):
const API_URL = "http://api.staging.com"

Git의 고민:
"어떤 버전을 사용해야 할까?"
→ 사용자가 직접 선택해야 함!

Conflict 발생 조건

3가지 필수 조건:

1
2
3
4
5
6
7
8
9
10
11
12
1. 같은 파일
2. 같은 위치 (같은 줄)
3. 다른 내용

예시:
✅ 충돌 O:
  - main: line 10 → "version 1"
  - feature: line 10 → "version 2"

❌ 충돌 X:
  - main: line 10 수정
  - feature: line 50 수정 (다른 위치)

해보기: Conflict 발생시키기

시나리오: 의도적으로 충돌 만들기

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
# 프로젝트 초기화
mkdir conflict-demo
cd conflict-demo
git init

# 초기 파일 생성
cat > config.js << 'EOF'
const config = {
  apiUrl: "http://localhost:3000",
  timeout: 5000,
  debug: false
};
EOF

git add config.js
git commit -m "Initial config"

# === 브랜치 A: production 설정 ===
git checkout -b feature/production-config

cat > config.js << 'EOF'
const config = {
  apiUrl: "https://api.production.com",  // 변경!
  timeout: 5000,
  debug: false
};
EOF

git add config.js
git commit -m "feat: Add production API URL"

# === 브랜치 B: staging 설정 ===
git checkout main
git checkout -b feature/staging-config

cat > config.js << 'EOF'
const config = {
  apiUrl: "https://api.staging.com",  // 다르게 변경!
  timeout: 5000,
  debug: false
};
EOF

git add config.js
git commit -m "feat: Add staging API URL"

# === 충돌 발생시키기 ===
git checkout main
git merge feature/production-config
# → 성공 (Fast-Forward)

git merge feature/staging-config

# 출력:
Auto-merging config.js
CONFLICT (content): Merge conflict in config.js
Automatic merge failed; fix conflicts and then commit the result.

# ❌ 충돌 발생!

결과

충돌 발생 메시지 분석:

1
2
3
4
5
6
7
8
9
10
11
Auto-merging config.js
→ Git이 자동 병합 시도

CONFLICT (content): Merge conflict in config.js
→ config.js에서 내용 충돌 발생

Automatic merge failed
→ 자동 해결 실패

fix conflicts and then commit
→ 수동으로 해결 후 커밋 필요

충돌 상태 확인:

1
2
3
4
5
6
7
8
9
10
11
12
git status

# 출력:
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   config.js

# "both modified" → 양쪽 모두 수정했음

Topic2. Merge Conflict 해결 방법

충돌을 수동으로 해결하는 과정입니다.

Conflict Marker 이해하기

충돌 발생한 파일 확인:

1
2
3
4
5
6
7
8
9
10
11
12
cat config.js

# 출력:
const config = {
<<<<<<< HEAD
  apiUrl: "https://api.production.com",
=======
  apiUrl: "https://api.staging.com",
>>>>>>> feature/staging-config
  timeout: 5000,
  debug: false
};

Conflict Marker 의미:

1
2
3
4
5
<<<<<<< HEAD
  현재 브랜치 (main)의 내용
=======
  병합하려는 브랜치 (feature/staging-config)의 내용
>>>>>>> feature/staging-config

각 마커의 역할:

1
2
3
4
5
6
7
8
9
10
11
12
<<<<<<< HEAD
  → 충돌 시작
  → HEAD (현재 브랜치) 내용 시작

=======
  → 구분선
  → 위: 현재 브랜치
  → 아래: 병합 브랜치

>>>>>>> feature/staging-config
  → 충돌 끝
  → 병합 브랜치 이름

충돌 해결 3단계

1단계: 충돌 파일 열기:

1
2
3
4
# 에디터로 충돌 파일 열기
code config.js
# 또는
vim config.js

2단계: 원하는 버전 선택:

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
// 옵션 1: HEAD 버전 선택 (production)
const config = {
  apiUrl: "https://api.production.com",
  timeout: 5000,
  debug: false
};

// 옵션 2: 병합 브랜치 버전 선택 (staging)
const config = {
  apiUrl: "https://api.staging.com",
  timeout: 5000,
  debug: false
};

// 옵션 3: 둘 다 사용 (환경 변수로 분기)
const config = {
  apiUrl: process.env.NODE_ENV === 'production'
    ? "https://api.production.com"
    : "https://api.staging.com",
  timeout: 5000,
  debug: false
};

// ⚠️ Conflict Marker 제거 필수!
// <<<<<<< HEAD
// =======
// >>>>>>> 이런 마커들 모두 삭제

3단계: 해결 완료 표시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 충돌 해결한 파일 stage
git add config.js

# 상태 확인
git status

# 출력:
On branch main
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
	modified:   config.js

# 병합 커밋 생성
git commit

# 기본 메시지 사용 또는:
git commit -m "Merge: Resolve conflict in config.js

- Choose environment-based API URL selection
- Support both production and staging environments"

해보기: 실전 충돌 해결

시나리오: 팀 협업 중 충돌 해결

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# === 초기 설정 ===
mkdir team-conflict
cd team-conflict
git init

# 공통 함수 파일
cat > utils.js << 'EOF'
function formatDate(date) {
  return date.toString();
}

function formatCurrency(amount) {
  return `$${amount}`;
}
EOF

git add utils.js
git commit -m "Add utility functions"

# === 개발자 A: 날짜 포맷 개선 ===
git checkout -b feature/date-format

cat > utils.js << 'EOF'
function formatDate(date) {
  // 개선된 날짜 포맷
  const options = { year: 'numeric', month: 'long', day: 'numeric' };
  return date.toLocaleDateString('ko-KR', options);
}

function formatCurrency(amount) {
  return `$${amount}`;
}
EOF

git add utils.js
git commit -m "feat: Improve date formatting"

# === 개발자 B: 날짜 포맷 다르게 개선 ===
git checkout main
git checkout -b feature/date-iso

cat > utils.js << 'EOF'
function formatDate(date) {
  // ISO 형식으로 변경
  return date.toISOString().split('T')[0];
}

function formatCurrency(amount) {
  return `$${amount}`;
}
EOF

git add utils.js
git commit -m "feat: Use ISO date format"

# === 충돌 발생 ===
git checkout main
git merge feature/date-format
# → 성공

git merge feature/date-iso
# → 충돌!

# === 충돌 확인 ===
cat utils.js

# 출력:
function formatDate(date) {
<<<<<<< HEAD
  // 개선된 날짜 포맷
  const options = { year: 'numeric', month: 'long', day: 'numeric' };
  return date.toLocaleDateString('ko-KR', options);
=======
  // ISO 형식으로 변경
  return date.toISOString().split('T')[0];
>>>>>>> feature/date-iso
}

function formatCurrency(amount) {
  return `$${amount}`;
}

# === 해결: 두 가지 모두 지원 ===
cat > utils.js << 'EOF'
function formatDate(date, format = 'localized') {
  if (format === 'iso') {
    // ISO 형식
    return date.toISOString().split('T')[0];
  }
  // 기본: 로컬라이즈된 형식
  const options = { year: 'numeric', month: 'long', day: 'numeric' };
  return date.toLocaleDateString('ko-KR', options);
}

function formatCurrency(amount) {
  return `$${amount}`;
}
EOF

# === 해결 완료 ===
git add utils.js
git commit -m "Merge: Support both date formats

- Add format parameter to formatDate function
- Default to localized format
- Support ISO format via parameter
- Resolves conflict between feature/date-format and feature/date-iso"

# 히스토리 확인
git log --oneline --graph

# 출력:
*   M2 (HEAD -> main) Merge: Support both date formats
|\
| * I1 (feature/date-iso) feat: Use ISO date format
* |   M1 Merge branch 'feature/date-format'
|\ \
| |/
| * D1 (feature/date-format) feat: Improve date formatting
|/
* U1 Add utility functions

충돌 해결 도구

명령줄 도구:

1
2
3
4
5
6
7
8
# Git 내장 mergetool
git mergetool

# 선택한 도구로 충돌 파일 열림
# (vimdiff, meld, kdiff3 등)

# 충돌 해결 후 임시 파일 정리
git clean -f

비주얼 도구:

1
2
3
4
5
6
7
8
9
10
11
VS Code:
- 충돌 파일 자동 감지
- "Accept Current Change" 버튼
- "Accept Incoming Change" 버튼
- "Accept Both Changes" 버튼
- 직접 편집 가능

GitKraken, SourceTree:
- 그래픽 인터페이스
- 양쪽 변경사항 비교
- 클릭으로 선택

결과

충돌 해결 체크리스트:

1
2
3
4
5
6
✅ 충돌 파일 모두 확인
✅ Conflict Marker 모두 제거
✅ 원하는 최종 결과 확인
✅ 코드가 정상 작동하는지 테스트
✅ git add로 해결 표시
✅ git commit으로 병합 완료

Topic3. Conflict 예방 전략

충돌을 최소화하는 개발 전략입니다.

작업 분리 전략

파일 단위 분리:

1
2
3
4
5
6
7
8
9
좋은 예:
개발자 A → auth.js 작업
개발자 B → profile.js 작업
→ 충돌 가능성 ↓

나쁜 예:
개발자 A → utils.js 전체 수정
개발자 B → utils.js 전체 수정
→ 충돌 가능성 ↑

기능 단위 분리:

1
2
3
4
5
6
7
8
좋은 예:
feature/user-login    → 로그인 기능만
feature/user-signup   → 회원가입 기능만
→ 명확한 경계

나쁜 예:
feature/user-system   → 모든 사용자 기능
→ 경계 모호, 충돌 많음

자주 동기화하기

정기적인 Pull:

1
2
3
4
5
6
7
8
9
10
# 매일 아침 main 최신 상태로
git checkout main
git pull origin main

# feature 브랜치에 최신 변경사항 반영
git checkout feature/my-work
git merge main

# 또는 rebase (히스토리 깔끔)
git rebase main

작은 단위로 자주 병합:

1
2
3
4
5
6
7
8
9
나쁜 예:
2주 동안 feature 브랜치에서 작업
→ main과 너무 멀어짐
→ 병합 시 충돌 다수

좋은 예:
2-3일마다 작은 기능 단위로 병합
→ main과 가까운 상태 유지
→ 충돌 최소화

소통과 협업

작업 공유:

1
2
3
4
5
팀 채널에 공유:
"오늘 utils.js의 formatDate 함수 수정 중입니다!"

→ 다른 개발자가 같은 부분 작업 피함
→ 충돌 사전 방지

코드 리뷰 활용:

1
2
3
4
5
6
Pull Request 리뷰 시:
1. 변경 범위 확인
2. 다른 PR과 충돌 여부 체크
3. 순서 조정 가능

→ 병합 전 충돌 예측

해보기: 충돌 예방 워크플로우

시나리오: 팀 프로젝트 충돌 예방

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
# === 월요일 오전: 작업 시작 ===
# 1. main 최신화
git checkout main
git pull origin main

# 2. 새 feature 브랜치 생성
git checkout -b feature/payment-gateway

# 3. 팀에 공유
# Slack: "payment.js 작업 시작합니다!"

# === 월요일 저녁: 중간 커밋 ===
git add payment.js
git commit -m "WIP: Add payment gateway skeleton"
git push origin feature/payment-gateway

# === 화요일 오전: main 동기화 ===
git checkout main
git pull origin main

# main에 새 변경사항 반영
git checkout feature/payment-gateway
git merge main

# 충돌 있으면 여기서 해결 (작은 충돌)
# 충돌 없으면 계속 작업

# === 화요일 저녁: 기능 완성 ===
git add .
git commit -m "feat: Complete payment gateway integration"
git push origin feature/payment-gateway

# === 수요일: Pull Request & 병합 ===
gh pr create --title "Add payment gateway" --body "..."

# 리뷰 후 병합
gh pr merge --squash

# 다른 브랜치로 전환 전 정리
git checkout main
git pull origin main
git branch -d feature/payment-gateway

충돌 최소화 규칙

팀 규칙 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 브랜치 수명 규칙
   - Feature 브랜치: 최대 3일
   - 3일 넘으면 중간 병합 고려

2. 파일 작업 공지
   - 공통 파일 수정 시 팀에 공지
   - 동시 작업 조율

3. 정기 동기화
   - 매일 오전 main pull
   - 매일 오후 main merge

4. 작은 PR 원칙
   - 한 PR에 너무 많은 변경 X
   - 100~300줄 정도가 적당

5. 충돌 해결 담당
   - 나중에 병합하는 사람이 해결
   - 먼저 병합한 사람에게 질문 가능

결과

충돌 예방 효과:

1
2
3
4
5
6
7
8
9
Before (충돌 예방 전):
- 주 평균 10건 충돌
- 충돌 해결에 하루 2시간 소요
- 스트레스 높음

After (충돌 예방 후):
- 주 평균 2건 충돌
- 충돌 해결에 하루 30분 소요
- 원활한 협업

핵심 원칙:

1
2
3
4
5
✅ 작업 분리: 파일/기능 단위
✅ 자주 동기화: 매일 pull & merge
✅ 빠른 병합: 2-3일 내 PR
✅ 적극 소통: 작업 공유
✅ 작은 커밋: 리뷰 가능한 크기

정리

오늘 배운 내용:

1. Merge Conflict:

  • 같은 파일, 같은 위치, 다른 내용
  • Git이 자동 해결 불가
  • 수동 해결 필요

2. 충돌 해결 방법:

1
2
3
4
5
6
7
8
9
10
11
# 1. 충돌 확인
git status

# 2. 파일 편집 (Conflict Marker 제거)
vim file.js

# 3. 해결 표시
git add file.js

# 4. 병합 완료
git commit

3. Conflict Marker:

1
2
3
4
5
<<<<<<< HEAD
현재 브랜치 내용
=======
병합 브랜치 내용
>>>>>>> branch-name

4. 충돌 예방:

  • 작업 분리 (파일/기능)
  • 자주 동기화 (매일 pull)
  • 빠른 병합 (2-3일 내)
  • 팀 소통 (작업 공유)

다음 단계: Day 49에서 Rebase를 배웁니다.

완료 체크:

  • Merge Conflict의 원인을 이해했다
  • 충돌을 해결할 수 있다
  • Conflict Marker를 읽을 수 있다
  • 충돌 예방 전략을 알고 있다

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