Git Merge vs Rebase

브랜치 통합 전략의 이해와 선택 기준

Git에서 브랜치를 통합하는 두 가지 핵심 방법인 Merge와 Rebase의 차이점을 시각적으로 비교하고, 각각의 장단점과 실무에서의 선택 기준을 설명한다. Fast-forward merge, 3-way merge, interactive rebase까지 다룬다.

Engineering
Git
DevOps
저자

Kwangmin Kim

공개

2023년 05월 08일

1 Merge와 Rebase 개요

Git에서 브랜치를 통합하는 방법은 크게 두 가지다:

  • Merge: 두 브랜치의 이력을 합쳐서 새로운 병합 커밋을 만든다
  • Rebase: 한 브랜치의 커밋들을 다른 브랜치 위에 재배치한다

2 Git Merge

2.1 Fast-Forward Merge

main 브랜치에 새로운 커밋이 없고, feature 브랜치만 앞서 있을 때 발생한다.

병합 전:

main:    A --- B
                \
feature:         C --- D

병합 후 (fast-forward):

main:    A --- B --- C --- D
git switch main
git merge feature/login
# Fast-forward 발생 → 병합 커밋 없이 포인터만 이동
Updating b7e2c3a..f1d4a8e
Fast-forward
 src/login.js | 47 ++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

Fast-forward 와 함께 파일 변경 통계만 출력된다. 병합 커밋이 생성되지 않아 이력이 직선으로 유지된다.

2.2 3-Way Merge

main과 feature 브랜치 양쪽에 새로운 커밋이 있을 때 발생한다.

병합 전:

main:    A --- B --- E
                \
feature:         C --- D

병합 후:

main:    A --- B --- E --- M (병합 커밋)
                \         /
feature:         C --- D
git switch main
git merge feature/login
# 3-way merge → 병합 커밋 M이 생성됨
Merge made by the 'ort' strategy.
 src/login.js | 47 ++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

Merge made by the 'ort' strategy. (또는 구버전 Git에서는 recursive)가 나오면 병합 커밋이 생성된 것이다.

--no-ff 옵션: Fast-forward가 가능한 상황에서도 병합 커밋을 강제로 생성한다.

git merge --no-ff feature/login
# feature 브랜치의 존재가 이력에 명확히 남음
# 편집기가 열리며 커밋 메시지를 입력한다 (기본 메시지가 채워져 있음):
# Merge branch 'feature/login'
# 저장 후 종료하면:
Merge made by the 'ort' strategy.
 src/login.js | 47 +++++++++++++++
 1 file changed, 47 insertions(+)

--no-ff 는 Fast-forward가 가능해도 편집기를 열어 병합 커밋 메시지 입력을 요구한다. feature 브랜치의 존재가 이력에 명확히 남는다.

2.3 Merge의 장단점

장점 단점
이력이 있는 그대로 보존됨 병합 커밋이 많아지면 이력이 복잡해짐
안전함 (기존 커밋 변경 없음) 그래프가 복잡해질 수 있음
되돌리기 쉬움

3 Git Rebase

3.1 기본 Rebase

Rebase는 feature 브랜치의 커밋들을 main 브랜치의 최신 커밋 뒤에 재배치한다.

Rebase 전:

main:    A --- B --- E
                \
feature:         C --- D

Rebase 후:

main:    A --- B --- E
                      \
feature:               C' --- D'
# feature 브랜치에서 실행
git switch feature/login
git rebase main
Successfully rebased and updated refs/heads/feature/login.

충돌 없이 완료되면 이 한 줄만 출력된다. 커밋이 여러 개면 각 커밋이 재적용되는 메시지가 나오기도 한다:

Rebasing (1/3)
Rebasing (2/3)
Rebasing (3/3)
Successfully rebased and updated refs/heads/feature/login.
중요

C와 D가 C’와 D’로 바뀐 이유: Rebase는 커밋을 복사하여 새로운 위치에 놓는다. 원본 커밋(C, D)과 rebase된 커밋(C’, D’)은 내용은 같지만 해시값이 다른 별개의 커밋이다.

Rebase 후 main에 Fast-Forward Merge:

git switch main
git merge feature/login  # Fast-forward 발생

최종 결과:

main:    A --- B --- E --- C' --- D'

이력이 일직선으로 깔끔하다.

3.2 Rebase의 장단점

장점 단점
이력이 일직선으로 깔끔함 커밋 해시가 변경됨 (이력 재작성)
불필요한 병합 커밋 없음 공유된 브랜치에서 사용하면 위험
코드 리뷰가 수월함 충돌 해결이 커밋마다 필요할 수 있음

3.3 Rebase 중 충돌 해결

Rebase 도중 충돌이 발생하면 커밋 단위로 충돌을 해결해야 한다.

# 1. Rebase 시작
git rebase main

충돌이 발생하면:

Auto-merging src/login.js
CONFLICT (content): Merge conflict in src/login.js
error: could not apply abc1234... feat: Add login form
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply abc1234... feat: Add login form

could not apply <hash>... 가 나오면 해당 커밋을 재배치하는 과정에서 충돌이 발생한 것이다.

# 2. 충돌 파일 수정 후
git add src/login.js

# 3. Rebase 계속 진행
git rebase --continue
[detached HEAD f9a2b3c] feat: Add login form
 1 file changed, 12 insertions(+)
Successfully rebased and updated refs/heads/feature/login.

모든 커밋에서 충돌이 해결되면 Successfully rebased 메시지가 나타난다.

# Rebase 중단하고 원래 상태로 돌아가기 (필요 시)
git rebase --abort
Successfully aborted rebase.

4 Interactive Rebase

여러 커밋을 정리(합치기, 순서 변경, 메시지 수정 등)할 때 사용한다.

# 최근 3개 커밋 정리
git rebase -i HEAD~3

편집기가 열리며 다음과 같은 화면이 나타난다:

pick abc1234 feat: Add login form
pick def5678 fix: Fix login form typo
pick ghi9012 feat: Add login validation

# Commands:
# p, pick = 커밋 사용
# r, reword = 커밋 메시지 변경
# e, edit = 커밋 수정
# s, squash = 이전 커밋과 합치기 (메시지 합침)
# f, fixup = 이전 커밋과 합치기 (이 커밋 메시지 버림)
# d, drop = 커밋 삭제

4.1 실전 예시: 커밋 합치기 (Squash)

작업 중 생긴 자잘한 커밋들을 하나로 합친다:

pick abc1234 feat: Add login form
squash def5678 fix: Fix login form typo
squash ghi9012 feat: Add login validation

저장 후 종료하면 합쳐진 커밋의 메시지를 입력하는 두 번째 편집기가 열린다:

# This is a combination of 3 commits.
# The first commit's message is:
feat: Add login form

# This is the 2nd commit message:
fix: Fix login form typo

# This is the 3rd commit message:
feat: Add login validation

원하는 최종 메시지를 작성하고 저장하면:

[detached HEAD 7d3a1b2] feat: Add login form and validation
 Date: Mon Jan 15 14:30:00 2024 +0900
 3 files changed, 62 insertions(+), 2 deletions(-)
Successfully rebased and updated refs/heads/feature/login.

3개의 커밋이 하나의 커밋으로 합쳐진다.

4.2 실전 예시: 커밋 메시지 수정

pick abc1234 feat: Add login form
reword def5678 오타 수정 → 더 나은 메시지로 변경
pick ghi9012 feat: Add login validation

5 git pull –rebase

git pull은 기본적으로 fetch + merge를 수행한다. --rebase 옵션을 사용하면 fetch + rebase를 수행한다.

# 기본 pull (merge)
git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
Unpacking objects: 100% (3/3), done.
From https://github.com/username/repo
   a123456..b789012  main -> origin/main
Merge made by the 'ort' strategy.
 README.md | 2 ++
 1 file changed, 2 insertions(+)

원격 변경사항과 로컬 변경사항이 Merge made by the 'ort' strategy. 와 함께 병합 커밋으로 합쳐진다.

# Rebase pull
git pull --rebase
Successfully rebased and updated refs/heads/main.

로컬 커밋이 원격의 최신 커밋 뒤에 재배치되고 병합 커밋이 생기지 않는다.

기본 pull 전략을 rebase로 설정:

git config --global pull.rebase true

6 Merge vs Rebase: 선택 기준

6.1 Merge를 사용해야 할 때

  • 공유 브랜치 (main, develop)에 통합할 때
  • 이력 보존이 중요한 프로젝트
  • 팀원이 Git에 익숙하지 않은 경우
  • Pull Request 기반 워크플로우

6.2 Rebase를 사용해야 할 때

  • 개인 feature 브랜치를 최신 상태로 유지할 때
  • PR 전에 커밋 이력을 정리할 때
  • 깔끔한 이력을 선호하는 프로젝트

6.3 핵심 규칙

Golden Rule of Rebase

이미 원격에 push한 커밋은 rebase하지 않는다.

Rebase는 커밋 해시를 변경하므로, 다른 팀원이 이미 해당 커밋을 기반으로 작업하고 있다면 심각한 충돌이 발생한다.

  • 로컬에서만 작업한 커밋 → Rebase OK
  • 이미 push한 커밋 → Merge 사용

6.4 실무에서 많이 쓰는 패턴

# 1. feature 브랜치에서 작업
git switch feature/login

# 2. main의 최신 변경사항을 rebase로 가져오기
git fetch origin
git rebase origin/main

# 3. 충돌 해결 후 push (force 필요 - 개인 브랜치에서만!)
git push --force-with-lease origin feature/login

# 4. PR 생성 → 코드 리뷰 → main에 merge

성공 시:

To https://github.com/username/repo.git
 + a123456...f9a2b3c feature/login -> feature/login (forced update)

forced update 가 나타나면 원격 브랜치가 강제로 업데이트된 것이다. --force-with-lease 를 사용했기 때문에 다른 사람이 먼저 push한 경우에는 아래처럼 거부된다:

error: failed to push some refs to 'https://github.com/username/repo.git'
hint: Updates were rejected because the remote contains work that you do not have locally.

이 경우 git fetch 후 상황을 재확인해야 한다.

힌트

--force-with-lease--force보다 안전하다. 다른 사람이 해당 브랜치에 push한 내용이 있으면 거부되므로 실수로 다른 사람의 작업을 덮어쓰는 것을 방지한다.

7 요약 비교

항목 Merge Rebase
이력 형태 비선형 (분기/합류) 선형 (일직선)
기존 커밋 변경 변경 없음 커밋 해시 변경됨
병합 커밋 생성됨 생성 안 됨
안전성 높음 주의 필요
충돌 해결 한 번에 처리 커밋 단위로 처리
적합한 상황 공유 브랜치 통합 개인 브랜치 정리

Subscribe

Enjoy this blog? Get notified of new posts by email: