Git 실무 워크플로우 #2 rebase vs merge — 결정 기준과 금기
갈라진 두 히스토리를 합치는 방법으로 Git 기초 #3 브랜치와 머지에서 merge를 다뤘습니다. fast-forward가 안 되는 상황이면 머지 커밋이 생기고, 갈라졌다 합쳐진 흔적이 히스토리에 그대로 남는 방식이었습니다. 그런데 실무 저장소의 히스토리를 보면 머지 커밋 없이 일직선으로 깔끔하게 정리된 경우를 자주 만납니다. 그 뒤에 있는 도구가 rebase입니다. rebase는 강력하지만, 잘못 쓰면 팀 전체의 히스토리를 꼬이게 만드는 유일한 기초 명령이기도 합니다. 이번 글에서 동작 원리와 결정 기준, 그리고 금기를 한 번에 정리합니다.
총 7편으로 구성됩니다.
- #1 브랜치 전략 — GitHub Flow와 trunk-based
- #2 rebase vs merge — 결정 기준과 금기 ← 이번 글
- #3 interactive rebase — squash·fixup으로 커밋 정리
- #4 충돌 해결 — 충돌의 구조와 mergetool·rerere
- #5 PR 운영 — 리뷰 단위·커밋 메시지·draft PR
- #6 stash·cherry-pick·bisect — 일상 도구 셋
- #7 모노레포와 Git — sparse-checkout·서브모듈·LFS
rebase의 동작 원리 — 옮기는 것이 아니라 다시 만든다 #
merge와 같은 출발 상황에서 시작하겠습니다. 브랜치가 갈라진 뒤 양쪽 모두 전진한 상태입니다.
A --- B --- C --- F ← main
\
D --- E ← feature (HEAD)feature에서 git rebase main을 실행하면 히스토리가 이렇게 바뀝니다.
git switch feature
git rebase main
# Successfully rebased and updated refs/heads/feature.A --- B --- C --- F ← main
\
D' --- E' ← feature (HEAD)겉보기에는 D와 E를 main 끝으로 옮긴 것 같지만, Git이 실제로 한 일은 다릅니다. D와 E의 변경 내용을 F 위에 하나씩 다시 적용해 새 커밋 D’와 E’를 만들었습니다. 커밋에는 부모 포인터가 들어 있으므로, 부모가 달라진 커밋은 같은 커밋일 수 없습니다. D’는 D와 변경 내용이 같아도 해시가 다른 완전히 새로운 커밋입니다. 원래의 D와 E는 어떤 브랜치도 가리키지 않는 상태로 남았다가 나중에 정리됩니다.
이 상태에서 main으로 이동해 merge하면 fast-forward가 가능하므로, 머지 커밋 없이 포인터만 전진합니다. 일직선 히스토리는 이렇게 만들어집니다.
merge와 rebase — 무엇을 남기는가의 차이 #
두 방식의 차이는 기능이 아니라 히스토리 철학의 차이입니다.
| 구분 | merge | rebase |
|---|---|---|
| 히스토리 | 갈라짐과 합류를 그대로 보존 | 일직선으로 다시 씀 |
| 커밋 해시 | 기존 커밋 유지 | 재적용된 커밋은 전부 새 해시 |
| 머지 커밋 | 생김(3-way의 경우) | 없음 |
| 그래프 가독성 | 브랜치가 많으면 복잡 | 단순 |
| 사실 기록 | 실제 작업 순서 그대로 | 정리된 순서로 재구성 |
merge는 “무슨 일이 실제로 있었는가"를 남기고, rebase는 “이렇게 작업한 것처럼 정리하면 읽기 좋다"를 남깁니다. 어느 쪽이 항상 옳다고 말할 수 없고, 팀이 히스토리를 어떻게 읽고 싶은지에 따라 선택이 갈립니다.
황금률 — 공유 브랜치는 rebase하지 않는다 #
rebase에는 예외 없이 지켜야 할 규칙이 하나 있습니다. 이미 push되어 다른 사람이 base로 쓰고 있는 브랜치는 rebase하지 않습니다. 이유를 구조로 확인하겠습니다.
[rebase 전 — 동료가 C까지 받아 그 위에서 작업 중]
A --- B --- C ← origin/feature
A --- B --- C --- G ← 동료의 로컬 feature
[내가 C를 C'로 rebase하고 force push한 뒤]
A --- B --- C' ← origin/feature (히스토리가 재작성됨)
A --- B --- C --- G ← 동료의 로컬 (origin과 갈라짐)동료의 로컬에 있는 C는 원격에서 사라진 커밋이 됐습니다. 동료가 pull하는 순간 C와 C’가 같은 내용의 서로 다른 커밋으로 충돌하고, 이를 풀려고 merge하면 중복 커밋이 생기며, 다시 push하면 또 다른 사람의 히스토리가 꼬입니다. 한 번의 rebase가 팀 전체의 강제 푸시 연쇄로 번지는 구조입니다.
반대로 아직 push하지 않은 로컬 커밋, 또는 나만 쓰는 브랜치라면 rebase는 아무 문제가 없습니다. 재작성되는 커밋을 base로 쓰는 사람이 없기 때문입니다. 황금률은 이 한 문장으로 요약됩니다. 커밋을 다시 쓰는 것은 자유지만, 남이 딛고 선 커밋은 다시 쓰지 않습니다.
안전하게 쓰는 세 가지 상황 #
실무에서 rebase가 안전하고 유용한 사용처는 뚜렷합니다.
첫째, 내 기능 브랜치를 최신 main 위로 올릴 때입니다. PR을 열기 전에 git rebase main으로 브랜치를 최신 상태 위에 다시 세우면, 리뷰어는 최신 코드 기준의 diff를 보게 되고 머지 시점의 충돌도 미리 해소됩니다. 브랜치를 나만 쓰고 있다면 push 여부와 관계없이 안전합니다. 이미 push한 브랜치라면 rebase 후 push가 거부되는데, 이때 필요한 force push와 안전장치 --force-with-lease는 다음 글에서 자세히 다룹니다.
둘째, pull에서 생기는 불필요한 머지 커밋을 없앨 때입니다. 로컬에 커밋이 있는 상태에서 git pull을 실행하면 기본 동작은 merge라서 “Merge branch ‘main’ of …” 같은 머지 커밋이 생깁니다. 내 커밋을 원격 커밋 위로 재적용하는 git pull --rebase를 쓰면 이 잡음이 사라집니다.
# 이번 한 번만
git pull --rebase
# 기본값으로 설정
git config --global pull.rebase true
# rebase 전 작업 중인 변경을 자동으로 stash했다가 복원
git config --global rebase.autoStash truerebase.autoStash까지 켜 두면 작업 도중에도 pull이 매끄럽게 지나갑니다. 이때 재적용되는 것은 아직 push하지 않은 내 로컬 커밋뿐이므로 황금률에도 어긋나지 않습니다.
셋째, push 전에 로컬 커밋을 정리할 때입니다. 오타 수정, 디버그 출력 제거 같은 자잘한 커밋을 의미 단위로 합치고 다듬는 interactive rebase가 이 용도인데, 다음 글의 주제이므로 여기서는 존재만 짚어 둡니다.
git add한 뒤 git rebase --continue로 재적용을 이어 가고, 전체를 중단하려면 git rebase --abort로 rebase 이전 상태로 돌아갑니다. 커밋을 하나씩 재적용하는 구조라서 충돌도 커밋 단위로 쪼개져 나온다는 점은 merge보다 오히려 다루기 쉬운 면입니다.결정 기준 정리 #
지금까지의 내용을 판단 규칙으로 압축합니다.
- 로컬 전용, 나만 쓰는 브랜치 — rebase 자유. 히스토리를 얼마든지 다듬어도 됩니다.
- 공유 브랜치, 보호 브랜치(main 등) — rebase 금지. 합류는 merge나 PR의 머지 버튼으로 합니다.
- PR로 합류하는 기능 브랜치 — 열기 전
git rebase main으로 정돈하고, 머지 방식은 팀의 PR 정책(merge commit이든 squash든)을 따릅니다.
마지막 항목이 말해 주듯, 개인의 rebase 취향보다 팀의 히스토리 정책이 우선입니다. 일직선 히스토리를 원하는 팀이라면 PR의 Squash and merge나 Rebase and merge가 같은 결과를 더 안전하게 만들어 주기도 합니다.
마무리 #
이번 글의 핵심은 세 가지입니다.
- rebase는 커밋을 옮기는 것이 아니라 새 커밋으로 다시 만들어 재적용하는 명령이고, 그래서 해시가 전부 바뀝니다.
- merge는 역사를 보존하고 rebase는 역사를 다시 씁니다. 팀이 히스토리를 어떻게 읽고 싶은지가 선택 기준입니다.
- 황금률은 하나입니다. 남이 base로 쓰는 공유 브랜치는 rebase하지 않습니다. 내 로컬 커밋과 개인 브랜치는 자유입니다.
다음 글인 “Git 실무 워크플로우 #3 interactive rebase — squash·fixup으로 커밋 정리"에서는 rebase의 가장 생산적인 사용처를 다룹니다. 지저분하게 쌓인 로컬 커밋을 리뷰하기 좋은 단위로 다듬는 법, 그리고 재작성한 브랜치를 안전하게 올리는 --force-with-lease까지 확인하겠습니다.