DaleSchool

브랜치 병합

입문25분

학습 목표

  • git merge로 브랜치를 병합할 수 있다
  • fast-forward와 merge commit의 차이를 이해할 수 있다
  • git log --graph로 병합 전후 히스토리를 비교할 수 있다
  • 충돌의 개념을 이해하고 다음 레슨에 대비할 수 있다

동작하는 코드

예제 1: Fast-forward 머지

브랜치를 만들고 작업 후 병합합니다:

git init
echo "# 프로젝트" > README.md
git add README.md
git commit -m "feat: 초기 커밋"

git checkout -b feature-about
echo "<h1>소개</h1>" > about.html
git add about.html
git commit -m "feat: about 페이지 추가"

병합 전 상태 확인:

git log --oneline --graph --all

출력:

* a1b2c3d (HEAD -> feature-about) feat: about 페이지 추가
* e4f5g6h (main) feat: 초기 커밋

main으로 이동해서 병합:

git checkout main
git merge feature-about

출력:

Updating e4f5g6h..a1b2c3d
Fast-forward
 about.html | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 about.html

병합 후 히스토리:

git log --oneline --graph --all

출력:

* a1b2c3d (HEAD -> main, feature-about) feat: about 페이지 추가
* e4f5g6h feat: 초기 커밋

mainfeature-about과 같은 커밋을 가리킵니다. 히스토리가 선형입니다.

예제 2: --no-ff 머지 (히스토리 보존)

git checkout -b feature-contact
echo "<h1>연락처</h1>" > contact.html
git add contact.html
git commit -m "feat: contact 페이지 추가"

git checkout main
git merge --no-ff feature-contact -m "merge: contact 페이지 병합"

출력:

Merge made by the 'ort' strategy.
 contact.html | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 contact.html

병합 후 히스토리:

git log --oneline --graph --all

출력:

*   b2c3d4e (HEAD -> main) merge: contact 페이지 병합
|\
| * f7g8h9i (feature-contact) feat: contact 페이지 추가
|/
* a1b2c3d feat: about 페이지 추가
* e4f5g6h feat: 초기 커밋

--no-ff는 분기했다가 합친 구조를 히스토리에 보존합니다.

직접 수정하기

두 브랜치가 동시에 진행된 경우

main과 feature 모두에 새 커밋이 있을 때:

# main에 새 커밋 추가
git checkout main
echo "// 메인 기능" > main-feature.js
git add main-feature.js
git commit -m "feat: 메인 기능 추가"

# feature 브랜치도 앞서 있음
git checkout -b feature-sidebar
echo "<nav>사이드바</nav>" > sidebar.html
git add sidebar.html
git commit -m "feat: 사이드바 추가"

히스토리 확인:

git log --oneline --graph --all

출력:

* j0k1l2m (HEAD -> feature-sidebar) feat: 사이드바 추가
* b2c3d4e (main) merge: contact 페이지 병합
...

main이 앞서 있으므로 fast-forward 불가. 병합하면 merge commit이 자동 생성됩니다:

git checkout main
git merge feature-sidebar

출력:

Merge made by the 'ort' strategy.
 sidebar.html | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 sidebar.html
git log --oneline --graph --all

출력:

*   m3n4o5p (HEAD -> main) Merge branch 'feature-sidebar'
|\
| * j0k1l2m (feature-sidebar) feat: 사이드바 추가
* | b2c3d4e merge: contact 페이지 병합
...

병합 후 정리

# 병합된 브랜치 삭제
git branch -d feature-contact
git branch -d feature-sidebar

# 모든 브랜치 확인
git branch

"왜?" — Fast-forward vs Merge Commit

Fast-forward 머지

main에 새 커밋이 없고, feature 브랜치만 앞서 있으면 단순히 포인터를 앞으로 이동합니다:

Before:
main → A → B
              ↑ main
               C → D
                     ↑ feature

After:
main → A → B → C → D
                     ↑ main (포인터 이동)

히스토리가 선형이 됩니다. 브랜치가 있었다는 흔적이 없습니다.

Merge Commit

두 브랜치 모두에 새 커밋이 있거나, --no-ff를 사용하면 새 merge commit을 만듭니다:

Before:
main → A → B → E
              ↘
               C → D ← feature

After:
main → A → B → E → M (merge commit)
              ↘     ↗
               C → D

히스토리에 브랜치 구조가 보존됩니다.

| | Fast-forward | Merge Commit | | -------- | ------------- | ------------------- | | 히스토리 | 선형 (깔끔) | 분기 구조 보존 | | 언제 | 브랜치 최신화 | PR 병합, 협업 | | 옵션 | 자동 (기본) | --no-ff 또는 자동 |

충돌 맛보기 (다음 레슨 예고)

같은 파일의 같은 줄을 두 브랜치에서 다르게 수정하면 충돌이 발생합니다:

# main에서 수정
echo "main의 내용" > shared.txt
git add shared.txt && git commit -m "main 수정"

# feature에서 같은 파일 다르게 수정
git checkout -b feature-conflict
echo "feature의 내용" > shared.txt
git add shared.txt && git commit -m "feature 수정"

git checkout main
git merge feature-conflict

출력:

CONFLICT (content): Merge conflict in shared.txt
Automatic merge failed; fix conflicts and then commit the result.

이 충돌을 해결하는 방법은 레슨 7에서 자세히 다룹니다.

흔한 실수

실수 1: 잘못된 브랜치에서 merge

# 실수: feature 브랜치에서 main을 merge
git checkout feature-login
git merge main   # feature에 main이 병합됨 (원하지 않는 방향)

# 올바름: main에서 feature를 merge
git checkout main
git merge feature-login

merge의 방향: "현재 브랜치에 대상 브랜치를 합친다"는 것을 기억하세요.

실수 2: 충돌 상태에서 다른 작업

# 충돌 해결 없이 다른 명령어 사용
git merge feature-conflict   # 충돌 발생
git checkout main   # 에러: 충돌 해결 먼저!

# 올바른 순서
# 1. 충돌 해결
# 2. git add 파일명
# 3. git commit

실수 3: merge 취소를 모름

# 충돌 해결이 어렵거나 잘못된 merge면 취소 가능
git merge --abort   # merge 전 상태로 돌아감

심화 학습

merge 전략 선택
# fast-forward만 허용 (불가능하면 에러)
git merge --ff-only feature-branch

# 항상 merge commit 생성
git merge --no-ff feature-branch

# squash: feature의 모든 커밋을 하나로 합쳐서 staging에 넣기
git merge --squash feature-branch
git commit -m "feat: feature 기능 추가 (squash)"

팀에서 일관된 머지 방식을 정해두면 히스토리 관리가 쉬워집니다.

git log --all --graph --decorate 활용
# 가장 상세한 그래프 보기
git log --all --graph --oneline --decorate

# 날짜도 포함
git log --all --graph --format="%h %ai %s"

# 특정 브랜치 비교
git log main..feature-login   # feature-login에만 있는 커밋
git log feature-login..main   # main에만 있는 커밋
  1. 저장소를 만들고 main에 커밋 2개를 만드세요.
  2. feature-page 브랜치를 만들고 커밋 2개를 추가하세요.
  3. main으로 돌아와 git merge feature-page로 fast-forward 병합하세요.
  4. git log --oneline --graph --all로 히스토리를 확인하세요.
  5. feature-nav 브랜치를 만들고 main에도 새 커밋을 추가한 후, git merge --no-ff feature-nav로 merge commit을 만들고 히스토리를 비교하세요.

Q1. git merge --no-ff 옵션의 역할은?

  • A) 충돌을 무시하고 강제로 병합
  • B) fast-forward가 가능해도 merge commit을 만든다
  • C) 브랜치를 삭제하면서 병합
  • D) 병합 전에 확인을 요청