Rebase vs Merge
Merge and rebase both bring changes from one branch into another, but they do it differently — and the difference shows up in your project’s history. Most Git arguments boil down to which one a team should use. The honest answer is that both are useful; the question is which trade-offs you prefer.
What each one does
Starting point
A───B───C───F ◀── main
\
D───E ◀── featureAfter 'git merge feature' on main
A───B───C───F───M ◀── main
\ /
D───E───┘ ◀── feature
↑ M is a merge commit (two parents)After 'git rebase main' on feature
A───B───C───F───D'───E' ◀── feature
D' and E' are NEW commits (same content, different hashes)
feature now appears to have been branched from FSide-by-side comparison
Aspect | Merge | Rebase |
|---|---|---|
Creates new commits? | One (the merge commit) | Re-creates all your commits with new hashes |
History shape | Diamond / graph | Linear / straight line |
Preserves true timeline | Yes | No — pretends work happened sequentially |
Safe to push to shared branches | Yes | Only if you’re the sole user of that branch |
| Shows branches explicitly | Shows nothing about the branches |
Easy to revert | One commit (the merge) | N commits (one per rebased commit) |
Conflict handling | Resolve once | May need to resolve per replayed commit |
Required after the fact | Just push | Force-push (--force-with-lease) |
When merging is the better choice
Shared branches — main, develop, release. Rebasing these and force-pushing breaks everyone’s local copies.
Wanting an honest record — “these two branches existed and met here” is a useful historical fact.
Auditing requirements — when compliance demands a verifiable timeline of who did what when.
Long-lived branches — release branches that hotfixes flow into.
You merged with
--no-ffas a team convention.
When rebasing is the better choice
Updating your local feature branch with the latest main, before pushing.
Polishing commits before review — squashing WIP commits, fixing typos in messages.
Linear history is a team value —
git logis easier to scan.Bisecting — linear history makes binary search for bugs much more reliable.
Personal experimental branches — nobody else has them, no risk.
Common workflows that combine both
Hybrid: rebase locally, merge to share
# While developing your feature branch: git pull --rebase origin main # bring main's changes onto your branch # Right before opening a PR: git rebase -i main # tidy up your commits # When merging on GitHub: use "Merge commit" or "Squash and merge" # The PR itself is a merge — but your branch is clean and linear.
The “three styles” a team typically picks
Always merge (with
--no-ff). Detailed history, every feature visible as a diamond. Used by Gitflow.Rebase locally, merge to share. Personal branches are linear; main shows one merge per feature.
Always squash. Each PR collapses into one commit on main. Cleanest possible log.
Decision flowchart
Are these commits already pushed AND used by others?
├── YES → MERGE (don't rebase shared history)
└── NO →
Are you trying to combine work or update a local branch?
├── Updating local branch → REBASE
└── Bringing two diverged branches together
├── Want a clear "branch was merged here" marker → MERGE --no-ff
├── Want a clean linear log → REBASE then merge
└── Don't care about commits, want one entry → SQUASHThe golden rule (worth repeating)
— Local commits not yet pushed.
— Branches that are explicitly “mine alone” (most feature branches on solo work).
Both are equally valid
There are healthy teams running every possible strategy. Linux uses merge. Many startups use squash. Plenty of mid- size teams rebase. The mistake is mixing styles inconsistently within the same repo — that produces confusing histories. Pick one, document it in CONTRIBUTING.md, and trust it.