Git

[Git] merge와 rebase, 뭘 언제 쓰나

개발자 Noah 2026. 8. 31. 11:21

merge와 rebase, 뭘 언제 쓰나 - 대표 이미지

개인 앱 프로젝트를 마일스톤 단위로 쪼개서 만들었다. 기능 묶음마다 브랜치를 따고, 끝나면 main 으로 합치는 방식이었다. 세 번째 묶음을 합치고 나서 git log --graph 를 열었는데, 선이 갈라졌다 붙었다를 반복해서 어느 작업이 언제 들어갔는지 읽히지 않았다.

찾아보면 "rebase 를 쓰면 히스토리가 깔끔해진다" 는 말이 흔하다. 그런데 이상했다. 두 방식 모두 결국 같은 파일 상태를 만든다. 최종 코드가 같은데 왜 둘 다 존재하고, 왜 한쪽이 더 깔끔하다는 걸까.

merge와 rebase는 최종 파일 상태가 같은데 왜 둘 다 있고, 언제 뭘 골라야 하나?

환경

항목 버전
Git 2.50.1 (Apple Git-155)
확인한 문서 git-scm.com 공식 레퍼런스 (2026-08-07 기준)

두 명령이 실제로 하는 일

이름부터 다르다. merge 는 합치는 것이고, rebase 는 base 를 다시(re) 잡는 것이다.

main 에서 갈라져 나온 feature 브랜치가 있다고 하자. 갈라진 뒤 양쪽에 각각 커밋이 쌓였다.

merge 는 두 갈래를 하나로 묶는 커밋을 새로 만든다. 이 커밋은 부모가 두 개다. 공식 문서는 이렇게 적는다.

Except in a fast-forward merge (see above), the branches to be merged must be tied together by a merge commit that has both of them as its parents.

fast-forward 머지를 제외하면, 머지되는 브랜치들은 양쪽 모두를 부모로 갖는 merge commit 으로 묶여야 한다.

git-merge

rebase 는 커밋을 옮겨 심는다. 문서의 첫 줄이 "Transplant a series of commits onto a different starting point" 다. 이식한다는 표현을 쓴다. 그런데 실제 동작은 옮기는 게 아니라 다시 만드는 것에 가깝다.

Replay the commits, one by one, in order. This is similar to running git cherry-pick <commit> for each commit.

커밋을 하나씩 순서대로 다시 재생한다. 각 커밋마다 git cherry-pick <commit> 을 실행하는 것과 비슷하다.

git-rebase

여기가 핵심이다. cherry-pick 은 커밋을 복사해서 새 커밋을 만드는 명령이다. 그러니 rebase 를 거친 커밋은 내용이 같아도 해시가 다른 별개의 객체다. 공식 문서 예시도 원래 커밋을 A, 재생된 커밋을 A' 로 구분해서 표기한다.

안에서 무슨 일이 벌어지나

merge는 C와 D를 부모 두 개짜리 커밋 M으로 묶고, rebase는 D를 C 뒤에 새 커밋 D′로 다시 만들어 한 줄로 세운다

merge — 세 지점을 비교한다

merge 라고 하면 두 브랜치를 비교할 것 같지만 아니다. 실제로는 공통 조상까지 포함해 세 지점을 본다. 이걸 3-way merge 라고 한다.

기본 전략은 ort 다. 예전에 쓰던 recursive 는 v2.49.0 까지만 별개 구현이었고, v2.50.0 에서 ort 를 가리키도록 리다이렉트돼 지금은 이름만 남은 동의어다. 내 로컬이 2.50.1 이니 -s recursive 를 적어도 실제로 도는 건 ort 다.

세 지점을 보기 때문에 Git 은 "양쪽이 같은 곳을 고쳤는지" 를 판단할 수 있다. 한쪽만 고쳤으면 그쪽을 따라가고, 양쪽 다 고쳤으면 충돌을 낸다.

그리고 갈라진 적이 없으면 커밋을 만들 필요조차 없다.

In this case, a new commit is not needed to store the combined history; instead, the HEAD (along with the index) is updated to point at the named commit, without creating an extra merge commit.

이 경우 합쳐진 히스토리를 저장할 새 커밋이 필요 없다. 대신 HEAD 가 (인덱스와 함께) 대상 커밋을 가리키도록 갱신되며, 별도의 merge commit 은 만들지 않는다.

git-merge

이게 fast-forward 다. 현재 브랜치가 상대 커밋의 조상이기만 하면 일어난다. 즉 git merge 를 쳤다고 항상 merge commit 이 생기는 게 아니다. 이 사실을 모르면 "왜 어떤 머지는 커밋이 생기고 어떤 건 안 생기지" 에서 막힌다.

rebase — 커밋을 다시 만든다

rebase 는 갈라진 지점부터 내 커밋들을 떼어내, 상대 브랜치 끝에 하나씩 다시 붙인다. 하나씩이라는 게 중요하다. 충돌도 커밋 단위로 난다.

# feature 브랜치에서, main 의 최신 위에 내 커밋들을 다시 얹는다
git switch feature
git rebase main

충돌이 나면 전체가 멈추는 게 아니라 문제가 된 커밋에서 멈춘다. 거기서 셋 중 하나를 고른다.

명령 하는 일
git rebase --continue 충돌을 해결한 뒤 이어서 진행
git rebase --skip 그 커밋을 버리고 다음으로
git rebase --abort 전부 되돌리고 원래 브랜치로

커밋이 10개면 충돌을 10번 만날 수도 있다. merge 는 한 번에 몰아서 끝나는데 rebase 는 나눠서 겪는다. 총량이 줄어드는 게 아니라 분산된다.

나는 이렇게 나눠 쓴다

기준은 하나로 정리된다. 그 커밋을 나 말고 다른 사람이 가지고 있는가.

# 아직 안 올린 내 로컬 브랜치 → rebase 로 최신을 따라잡는다
git switch feature
git rebase main

# 공유 브랜치로 합칠 때 → merge, 그리고 fast-forward 를 막는다
git switch main
git merge --no-ff feature

--no-ff 를 붙이는 이유는 문서 그대로다.

With --no-ff, create a merge commit in all cases, even when the merge could instead be resolved as a fast-forward.

--no-ff 를 쓰면 fast-forward 로 해결할 수 있는 경우에도 항상 merge commit 을 만든다.

git-merge

fast-forward 로 붙어 버리면 커밋들이 main 히스토리에 그냥 섞인다. 어디부터 어디까지가 한 기능이었는지 경계가 사라진다. merge commit 하나를 남겨 두면 나중에 그 단위로 되돌릴 수 있다.

git pull 도 손봐 뒀다. 기본값이 merge 라 원격을 받을 때마다 의미 없는 merge commit 이 쌓인다.

# pull 할 때 merge 대신 rebase 를 쓴다
git config --global pull.rebase true

정리하면 이렇게 나뉜다.

상황 고르는 것 이유
내 로컬 브랜치를 최신으로 rebase 아직 아무도 안 가진 커밋이라 다시 만들어도 안전
원격에서 받아오기 pull --rebase 의미 없는 merge commit 방지
기능 브랜치를 공유 브랜치로 merge --no-ff 작업 단위 경계를 남긴다
이미 올린 브랜치 merge rebase 는 금지 (아래)

대가

rebase 가 깔끔해 보이는 건 대가를 치르고 얻은 것이다.

첫째, 히스토리가 사실이 아니게 된다. rebase 를 거치면 내 커밋들은 마치 처음부터 최신 main 위에서 작업한 것처럼 보인다. 실제로는 그 시점에 존재하지도 않은 코드 위에서 작업한 게 된다. "언제 갈라져서 언제 합쳐졌나" 라는 정보는 사라진다. merge 히스토리가 지저분한 건 실제로 지저분하게 일어났기 때문이다.

둘째, 공유된 브랜치에서는 쓰면 안 된다. 문서의 경고가 분명하다.

Rebasing (or any other form of rewriting) a branch that others have based work on is a bad idea: anyone downstream of it is forced to manually fix their history.

다른 사람이 그 위에서 작업을 쌓아 둔 브랜치를 rebase 하는 것은(혹은 다른 어떤 형태로든 다시 쓰는 것은) 나쁜 생각이다. 그 아래에 있는 모든 사람이 자기 히스토리를 손으로 고쳐야만 한다.

git-rebase

해시가 바뀌기 때문이다. 원격에는 옛 해시로 된 커밋이 있는데 내 로컬에는 새 해시가 있으니, Git 은 이걸 서로 다른 커밋으로 본다. 이 상태에서 억지로 올리려면 --force 가 필요하고, 그 순간 남의 커밋을 지울 수 있다.

셋째, 충돌을 여러 번 겪는다. 앞에서 본 그대로다. 커밋이 많을수록 나눠서 만난다.

반대로 merge 의 대가는 히스토리 복잡도 하나다. 브랜치가 많은 팀에서 git log --graph 는 읽기 어려워진다. 다만 이건 정보가 많아서 어려운 것이지 틀린 게 아니다.

정리

질문으로 돌아가면, 두 명령은 결과가 같은 게 아니었다. 최종 파일 상태만 같고 히스토리는 다르다. merge 는 일어난 일을 그대로 기록하고, rebase 는 일어나지 않은 일을 기록한다.

그래서 기준도 하나로 좁혀진다. 그 커밋을 남이 가지고 있으면 merge, 나만 가지고 있으면 rebase. 깔끔함이 기준이 아니다. 히스토리를 다시 써도 아무도 안 다치는 구간인지가 기준이다.

처음의 그 갈라진 그래프는 사실 정직한 기록이었다. 다만 마일스톤 브랜치를 --no-ff 없이 합친 구간이 섞여 있어서, 어디가 한 묶음이었는지 경계가 지워져 있었다. 지금은 합칠 때 --no-ff 를 붙인다.