🔀
Git·GitHub 심화
branch · switch · merge · conflict
3단원 — 브랜치와 병합
브랜치는 Git의 가장 강력한 기능입니다. 메인 코드를 건드리지 않고 새 기능을 실험하거나 버그를 수정할 수 있으며, 완성되면 병합(merge)으로 합칩니다. 브랜치를 자유롭게 다루면 혼자 개발할 때도 여러 아이디어를 동시에 진행할 수 있습니다.
branchmergeconflict
소요 시간
⏱ 1~2시간
난이도
📊 초급
선수 조건
🎯 2단원
결과물
브랜치 분기와 병합·충돌 해결
이 강의에서 배우는 것
- 1`git branch`·`git switch`로 브랜치를 만들고 전환할 수 있다.
- 2fast-forward merge와 3-way merge의 차이를 설명할 수 있다.
- 3충돌(conflict)이 발생했을 때 직접 수정하고 해결할 수 있다.
- 4`git branch -d`로 병합된 브랜치를 안전하게 삭제할 수 있다.
핵심 개념
1) 브랜치 만들기·전환하기
bash
git branch feature/login # 브랜치 생성
git switch feature/login # 브랜치 전환
# 생성 + 전환 한 번에
git switch -c feature/login
# 목록 확인
git branch # 로컬
git branch -a # 로컬 + 원격2) Fast-forward Merge
text
main: A---B
\
feature: C---D
git merge feature → main: A---B---C---D (직선)- `main`이 앞으로 나아가기만 하면 된다 (분기점 없음).
- 병합 커밋이 생기지 않아 이력이 깔끔하다.
bash
git switch main
git merge feature/login # fast-forward (기본)
git merge --no-ff feature/login # 병합 커밋 강제 생성3) 3-way Merge
text
main: A---B---E
\ \
feature: C---D
git merge feature → main: A---B---E---M (M = 병합 커밋)- `main`과 `feature` 양쪽이 각자 커밋을 진행한 경우.
- 공통 조상(B) + 두 브랜치 끝(E, D)을 비교해 병합한다.
4) 충돌(Conflict) 해결
bash
# 충돌 발생 시 git status 가 conflict 파일을 표시
git status
# both modified: src/app.py
# 파일 열면 충돌 마커가 삽입됨
<<<<<<< HEAD
현재 브랜치의 내용
=======
병합 대상 브랜치의 내용
>>>>>>> feature/login
# 1. 마커 제거 후 원하는 내용 남기기
# 2. git add <파일>
# 3. git commit예제로 보기
예제 1 — `ex01_branch.sh` : 브랜치 생성·전환·목록 확인
bash
#!/usr/bin/env bash
REPO=$(mktemp -d); cd "$REPO"
git init -q && git config user.name "실습용" && git config user.email "demo@example.com"
echo "main" > main.txt && git add . && git commit -q -m "init"
git switch -c feature/hello
echo "hello" > hello.txt && git add . && git commit -q -m "feat: hello"
echo "=== 브랜치 목록 ==="
git branch
git switch main
echo "=== HEAD 위치 ==="
git log --oneline --all --graph**실행 결과**
text
=== 브랜치 목록 ===
* feature/hello
main
=== HEAD 위치 ===
* a1b2c3d (feature/hello) feat: hello
* b2c3d4e (HEAD -> main) init핵심: `*`가 현재 체크아웃된 브랜치를 가리킨다.
예제 2 — `ex02_merge_ff.sh` : Fast-forward Merge
bash
#!/usr/bin/env bash
REPO=$(mktemp -d); cd "$REPO"
git init -q && git config user.name "실습용" && git config user.email "demo@example.com"
echo "init" > README.md && git add . && git commit -q -m "init"
git switch -c feature/ff
echo "FF 기능" > feature.txt && git add . && git commit -q -m "feat: FF feature"
git switch main
git merge feature/ff
echo "=== 병합 후 로그 (직선) ==="
git log --oneline --graph**실행 결과**
text
=== 병합 후 로그 (직선) ===
* a1b2c3d (HEAD -> main, feature/ff) feat: FF feature
* b2c3d4e init핵심: fast-forward는 병합 커밋 없이 포인터만 이동한다.
예제 3 — `ex03_merge_3way.sh` : 3-way Merge
bash
#!/usr/bin/env bash
REPO=$(mktemp -d); cd "$REPO"
git init -q && git config user.name "실습용" && git config user.email "demo@example.com"
echo "init" > README.md && git add . && git commit -q -m "init"
git switch -c feature/3way
echo "기능 A" > feature.txt && git add . && git commit -q -m "feat: feature A"
git switch main
echo "main 추가 작업" > main_extra.txt && git add . && git commit -q -m "chore: main extra"
git merge feature/3way -m "merge: feature/3way into main"
echo "=== 병합 후 로그 (병합 커밋 있음) ==="
git log --oneline --graph**실행 결과**
text
=== 병합 후 로그 (병합 커밋 있음) ===
* c3d4e5f (HEAD -> main) merge: feature/3way into main
|\
| * a1b2c3d (feature/3way) feat: feature A
* | b2c3d4e chore: main extra
|/
* 9a8b7c6 init핵심: 양쪽이 각자 커밋했을 때 Git이 자동으로 병합 커밋을 만든다.
예제 4 — `ex04_conflict.sh` : 충돌 만들기 및 해결
bash
#!/usr/bin/env bash
REPO=$(mktemp -d); cd "$REPO"
git init -q && git config user.name "실습용" && git config user.email "demo@example.com"
echo "공통 내용" > shared.txt && git add . && git commit -q -m "init"
git switch -c feature/conflict
echo "feature 의 수정" > shared.txt && git add . && git commit -q -m "feat: modify shared"
git switch main
echo "main 의 수정" > shared.txt && git add . && git commit -q -m "chore: modify shared on main"
echo "=== 충돌 병합 시도 ==="
if ! git merge feature/conflict -m "merge" 2>/dev/null; then
echo "충돌 발생! shared.txt 내용:"
cat shared.txt
# 충돌 해결: main 버전을 채택
echo "충돌 해결: main 버전 채택" > shared.txt
git add shared.txt
git commit -m "merge: resolve conflict in shared.txt"
echo ""
echo "=== 충돌 해결 후 로그 ==="
git log --oneline --graph
fi
rm -rf "$REPO"**실행 결과**
text
충돌 발생! shared.txt 내용:
<<<<<<< HEAD
main 의 수정
=======
feature 의 수정
>>>>>>> feature/conflict
=== 충돌 해결 후 로그 ===
* d4e5f6a (HEAD -> main) merge: resolve conflict in shared.txt
|\
| * c3d4e5f (feature/conflict) feat: modify shared
* | b2c3d4e chore: modify shared on main
|/
* a1b2c3d init핵심: 충돌 마커를 직접 편집하고 `git add` → `git commit`으로 병합을 완료한다.
다른 시각으로 보기
| Git 개념 | 일상 비유 |
|---|---|
| 브랜치 | 평행 우주 |
| merge | 두 우주를 하나로 합치기 |
| fast-forward | 타임라인을 앞으로 감기 |
| conflict | 두 우주에서 같은 물체를 다르게 수정 |
| 병합 커밋 | 두 우주가 만나는 교차점 |
자주 하는 실수
- **main에서 직접 작업** — 항상 기능 브랜치를 만들어 작업한다.
- **충돌 마커를 파일에 남긴 채 커밋** — `<<<`, `===`, `>>>` 가 남아 있으면 빌드가 깨진다.
- **`-d` 대신 `-D` 로 병합 안 된 브랜치 삭제** — 커밋이 유실된다.
- **병합 전 `git pull` 생략** — 원격 변경을 가져오지 않아 충돌이 더 커진다.
- **fast-forward를 강제(`--no-ff`)해야 할 때 생략** — PR 이력이 사라져 추적이 어려워진다.
정리
- `git switch -c <브랜치>` 로 브랜치를 만들고 전환한다.
- fast-forward는 직선 이력, 3-way는 병합 커밋을 생성한다.
- 충돌은 마커를 편집 → `git add` → `git commit` 순으로 해결한다.
- 병합 완료된 브랜치는 `git branch -d`로 정리한다.
직접 해 보기
bash
cd 03_브랜치와_병합/src
chmod +x *.sh
./ex01_branch.sh
./ex02_merge_ff.sh
./ex03_merge_3way.sh
./ex04_conflict.sh응용:
- `git log --oneline --graph --all` 로 병합 전후 그래프가 어떻게 바뀌는지 비교해 보세요.
- `git merge --abort` 를 사용해 충돌 병합을 중단하는 시나리오를 만들어 보세요.
💻 예제 (examples)
실제로 실행해 결과를 확인할 수 있는 예제입니다.
ex01_branch.sh— 브랜치 생성·전환·목록 확인
CODE
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "실습용" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "main content" > main.txt
git add . && git commit -q -m "init: main"
echo "=== 브랜치 생성 ==="
git branch feature/hello
git branch feature/world
echo ""
echo "=== 브랜치 목록 ==="
git branch
echo ""
echo "=== feature/hello 로 전환 ==="
git switch feature/hello
echo "hello" > hello.txt
git add . && git commit -q -m "feat: add hello"
echo ""
echo "=== 전체 그래프 ==="
git log --oneline --graph --all
echo ""
echo "=== main 으로 복귀 ==="
git switch main
git branch
rm -rf "$REPO"
▶ 실행 결과
=== 브랜치 목록 ===
* feature/hello
main
=== HEAD 위치 ===
* a1b2c3d (feature/hello) feat: hello
* b2c3d4e (HEAD -> main) initex02_merge_ff.sh— Fast-forward Merge
CODE
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "실습용" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "init" > README.md
git add . && git commit -q -m "init"
git switch -c feature/fast-forward
echo "FF 기능 추가" > feature.txt
git add . && git commit -q -m "feat: add feature"
echo "=== 병합 전 로그 ==="
git log --oneline --graph --all
git switch main
git merge feature/fast-forward
echo ""
echo "=== fast-forward 병합 후 로그 (직선) ==="
git log --oneline --graph --all
echo ""
echo "=== --no-ff 로 병합 커밋 강제 생성 ==="
git switch -c feature/no-ff
echo "no-ff 기능" > noff.txt
git add . && git commit -q -m "feat: add no-ff feature"
git switch main
git merge --no-ff feature/no-ff -m "merge: feature/no-ff into main"
echo ""
echo "=== --no-ff 후 로그 (병합 커밋 있음) ==="
git log --oneline --graph --all
rm -rf "$REPO"
▶ 실행 결과
=== 병합 후 로그 (직선) ===
* a1b2c3d (HEAD -> main, feature/ff) feat: FF feature
* b2c3d4e initex03_merge_3way.sh— 3-way Merge
CODE
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "실습용" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "초기 파일" > README.md
git add . && git commit -q -m "init"
# feature 브랜치에서 커밋
git switch -c feature/3way
echo "기능 A" > feature_a.txt
git add . && git commit -q -m "feat: add feature A"
echo "기능 B" > feature_b.txt
git add . && git commit -q -m "feat: add feature B"
# main 에서도 별도 커밋 (분기 상태 만들기)
git switch main
echo "main 에서 버그 수정" > bugfix.txt
git add . && git commit -q -m "fix: bugfix on main"
echo "=== 병합 전 그래프 ==="
git log --oneline --graph --all
git merge feature/3way -m "merge: feature/3way into main"
echo ""
echo "=== 3-way merge 후 그래프 (병합 커밋 있음) ==="
git log --oneline --graph --all
rm -rf "$REPO"
▶ 실행 결과
=== 병합 후 로그 (병합 커밋 있음) ===
* c3d4e5f (HEAD -> main) merge: feature/3way into main
|\
| * a1b2c3d (feature/3way) feat: feature A
* | b2c3d4e chore: main extra
|/
* 9a8b7c6 initex04_conflict.sh— 충돌 만들기 및 해결
CODE
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "실습용" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "공통 내용" > shared.txt
git add . && git commit -q -m "init"
# feature 브랜치: shared.txt 수정
git switch -c feature/conflict
echo "feature 의 내용" > shared.txt
git add . && git commit -q -m "feat: update shared (feature)"
# main 브랜치: 같은 파일 다른 내용으로 수정
git switch main
echo "main 의 내용" > shared.txt
git add . && git commit -q -m "chore: update shared (main)"
echo "=== 충돌 발생 병합 시도 ==="
git merge feature/conflict 2>&1 || true
echo ""
echo "=== 충돌 마커 확인 ==="
cat shared.txt
echo ""
echo "=== 충돌 해결: main 버전 채택 ==="
echo "충돌 해결 완료 — main 버전" > shared.txt
git add shared.txt
git commit -m "merge: resolve conflict in shared.txt"
echo ""
echo "=== 최종 로그 ==="
git log --oneline --graph --all
rm -rf "$REPO"
▶ 실행 결과
충돌 발생! shared.txt 내용:
<<<<<<< HEAD
main 의 수정
=======
feature 의 수정
>>>>>>> feature/conflict
=== 충돌 해결 후 로그 ===
* d4e5f6a (HEAD -> main) merge: resolve conflict in shared.txt
|\
| * c3d4e5f (feature/conflict) feat: modify shared
* | b2c3d4e chore: modify shared on main
|/
* a1b2c3d init📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
과제 1
문제 1 (hw01.sh)
목표: 두 브랜치에서 서로 다른 파일을 수정해 fast-forward merge 가 아닌 3-way merge가 발생하는 시나리오를 구현하세요.
요구사항
- 파일명: hw01.sh
입출력 예시
# 예상 출력 (핵심 부분)
* 병합 커밋 해시 (HEAD -> main) merge: feature/a
|\
| * 해시 (feature/a) ...
* | 해시 ...
|/
* 해시 init▶정답 코드 펼치기 / 접기
SOLUTION
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "실습용" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "base" > base.txt
git add . && git commit -q -m "init: add base.txt"
git switch -c feature/a
echo "기능 A" > a.txt
git add . && git commit -q -m "feat: add a.txt"
git switch main
echo "서포트 파일" > b.txt
git add . && git commit -q -m "chore: add b.txt on main"
git merge feature/a -m "merge: feature/a into main"
echo "=== 3-way merge 결과 ==="
git log --oneline --graph --all
rm -rf "$REPO"
▶ 실행 결과
# 예상 출력 (핵심 부분)
* 병합 커밋 해시 (HEAD -> main) merge: feature/a
|\
| * 해시 (feature/a) ...
* | 해시 ...
|/
* 해시 init과제 2
문제 2 (hw02.sh)
목표: 두 브랜치에서 동일 파일의 동일 줄을 수정해 충돌을 발생시키고, 직접 해결 후 병합 커밋을 남기는 스크립트를 작성하세요.
요구사항
- 파일명: hw02.sh
▶정답 코드 펼치기 / 접기
SOLUTION
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "실습용" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "공통 내용" > conflict.txt
git add . && git commit -q -m "init"
git switch -c feature/b
echo "feature 내용" > conflict.txt
git add . && git commit -q -m "feat: feature version"
git switch main
echo "main 내용" > conflict.txt
git add . && git commit -q -m "chore: main version"
echo "=== 충돌 발생 ==="
git merge feature/b 2>&1 || true
echo "해결된 내용" > conflict.txt
git add conflict.txt
git commit -m "merge: resolve conflict in conflict.txt"
echo ""
echo "=== 최종 그래프 ==="
git log --oneline --graph --all
rm -rf "$REPO"