Engineering
Rebase vs merge
The rebase-versus-merge argument gets treated like a holy war, which is silly, because they answer different questions
title: "Rebase vs merge" date: 2026-06-26 tags: [git, rebase, merge, history, workflow] draft: true
The rebase-versus-merge argument gets treated like a holy war, which is silly, because they answer different questions. Merge asks "how do I combine these two histories while preserving what happened?" Rebase asks "how do I make this history read as a clean straight line?" Neither is correct in the abstract. The right one depends on whether you value an honest record of how work happened or a tidy record that's easy to read. Look at what each does to the same starting point and the choice gets concrete.
Start with the same situation both times: a feature branch that split off while main kept moving.
A---B---C feature
/
D---E---F---G main
You want feature's work combined with main. Two ways to get there.
Merge: preserve the history
git checkout main
git merge feature
Merge creates a new commit, M, with two parents, one reaching back into main and one into feature. Nothing is rewritten. Both lines of development stay exactly as they happened, stitched together by the merge commit:
A---B---C
/ \
D---E---F---G---M main
gitGraph
commit id: "D"
commit id: "E"
branch feature
checkout feature
commit id: "A"
commit id: "B"
commit id: "C"
checkout main
commit id: "F"
commit id: "G"
merge feature id: "M"
The graph forks and rejoins. You can see that feature was developed in parallel and merged in, because that is what happened. The cost is that the graph forks and rejoins, and a project with many merges turns into a lattice that's hard to read commit by commit. The benefit is that it's true, and the original commits keep their hashes, so nobody's references break.
Rebase: rewrite for a straight line
git checkout feature
git rebase main
git checkout main
git merge feature # now a fast-forward
Rebase replays feature's commits on top of G as new commits (A', B', C'), then main fast-forwards to include them. No merge commit, no fork. One straight line:
D---E---F---G---A'--B'--C' main
gitGraph
commit id: "D"
commit id: "E"
commit id: "F"
commit id: "G"
commit id: "A'"
commit id: "B'"
commit id: "C'"
The history reads as if you did your work after everyone else's, cleanly, in sequence. The parallel development is erased from the record. That's the trade: you get a log that's a pleasure to read and a history that's a small fiction. And because the commits were rewritten into new hashes, you can't do this to commits other people have already built on without causing them grief.
Side by side
| Merge | Rebase | |
|---|---|---|
| History shape | Forked graph with a merge commit | Single straight line |
| Original commits | Preserved, same hashes | Rewritten, new hashes |
| Extra commit created | Yes, the merge commit | No |
| Record of parallel work | Kept | Erased |
git log readability |
Noisier, shows the branching | Clean, linear |
| Safe on shared branches | Yes | No, rewrites history |
| Conflict resolution | Once, in the merge commit | Possibly once per replayed commit |
| Best for | Integrating shared/long-lived branches | Tidying a private branch before review |
A couple of those rows deserve a note. Conflicts during a merge are resolved one time, in the single merge commit. During a rebase, because each commit is replayed in turn, you can hit the same conflict repeatedly as each patch lands on the new base. For a branch with a tangled history, merge can be less painful, which cuts against the usual assumption that rebase is always the cleaner path.
How to decide
Forget the dogma and ask what you want the history to say.
Rebase your own private branch, before anyone sees it. Pulling in the latest main, squashing your "fix typo" commits, making your six messy commits into two coherent ones, this is rebase's home turf, and it's where it makes pull requests nicer to review.
Merge when you're integrating work that's shared or long-lived. Pulling a finished feature into main, combining branches several people touched, anything where rewriting commits would break someone else's references or erase a branching story worth keeping. Merge tells the truth about how the work came together.
The popular hybrid is the sane default for most teams: rebase locally to clean up your branch, then merge it into main with a merge commit so the integration point is recorded. You get readable individual commits and an honest integration history at the same time.
The only hard rule sits underneath all of it. Rebase rewrites commits into new objects, so never rebase history that others have already built on. Inside that boundary, this was never a war. It's just two tools, one that preserves and one that polishes, and a choice about which one this particular branch needs.