git rebase vs git merge — when to actually use each
Two commands, one job — get changes from one branch into another — and somehow this is the topic that turns otherwise calm engineers into superstitious campfire storytellers. "Don't rebase, ever." "Always rebase, merge commits are pollution." Both camps are right, and both are wrong, because they're answering a question that was never asked.
The real question isn't "which is better." It's "whose history am I about to touch?" Answer that, and the choice between merge and rebase stops being a debate and becomes obvious. Here's the whole picture.
- What merge actually does — and what it costs you
- What rebase actually does — and why it rewrites history
- The one golden rule that keeps you out of trouble
- A concrete workflow for when to reach for each
- Interactive rebase — the feature that makes it all worth it
The problem both commands solve
You're working on a feature branch. Meanwhile, main has moved
ahead — bug fixes landed, other features merged. Now you need to reconcile
the two: either pull main's changes into your branch so you're
not building on stale code, or fold your finished work back into
main.
Both merge and rebase accomplish that integration.
The difference is entirely about what the history looks like
afterward — and whether the commits you had before are the same
commits you have after.
How merge works
git merge ties two histories together with a new
merge commit — a commit with two parents, one from each
branch. Nothing that already exists is changed.
# on the feature branch, pull in main's latest
git checkout feature
git merge main
# before: after:
# main: A─B─C─D─E main: A─B─C─D─E
# \ \ \
# feature: F─G feature: F─G───M ← merge commit M
The merge commit M has two parents — E from
main and G from your branch. Every original commit
is still there, untouched. The history is complete and
honest: you can see exactly when the branches diverged and when
they came back together.
The cost? With lots of branches and frequent syncing, your history turns
into a subway map — merge commit after merge commit, and
git log starts reading like a group chat.
How rebase works
git rebase takes a different approach. Instead of tying the
histories together, it replays your commits on top of the target
branch. It sets your commits aside, moves your branch to the tip of
main, then re-applies each of your changes as a brand-new
commit.
# on the feature branch, replay onto main's latest
git checkout feature
git rebase main
# before: after:
# main: A─B─C─D─E main: A─B─C─D─E
# \ \
# feature: F─G feature: F'─G' ← new commits, new SHAs
Notice the primes: F and G are gone from the
branch, replaced by F' and G'. They're
new commits with new SHAs, even though the changes are
identical. The old commits still exist (you can find them in the reflog),
but nothing points to them anymore.
The result is a clean, linear history — it looks as if you
had simply started your work from the latest main. Beautiful
for review, beautiful for git log. And that "rewrites history"
part is both the superpower and the danger.
The golden rule
Everything you need to stay safe fits in one sentence:
Rebase rewrites history. Rewriting history that only you have is tidying up. Rewriting history that your teammates have already pulled is vandalism — their local commits no longer match yours, and everyone ends up in a detached, conflicting mess. If a branch has been pushed and shared, it's merge-only.
The corollary is a clean division of labor:
- Shared branches (
main,develop, anything a teammate has pulled) — merge only. - Your own branches (local, or pushed only for backup/CI) — rebase freely.
That's it. Most of the horror stories you've heard are someone rebasing a shared branch and force-pushing over other people's work. Follow the rule and rebase is harmless; break it and you'll understand why the campfire stories exist.
When to use each
With the golden rule in place, the day-to-day guidance is simple:
- Use rebase to sync your feature branch with the latest
mainwhile you're still working on it. Keeps your branch current and your eventual diff clean. - Use rebase (interactive) to tidy your local commits before opening a PR — squash the "wip" and "fix typo" noise into coherent units.
- Use merge to integrate finished work back into
main— usually via a pull request, where the merge (or squash-merge) happens on the shared branch. - Use merge when the branch was collaborative. If two people committed to it, its history belongs to both of you — don't rewrite it.
Rebase is for your drafts, merge is for the published record. You rewrite your own rough notes; you don't rewrite the chapter that's already in print.
The workflow most teams converge on looks like this:
# 1 · branch off main, commit freely (messy is fine, it's yours)
git checkout -b feature/login main
# 2 · before opening the PR, tidy + sync
git rebase -i main # squash/reword your rough commits
git fetch origin
git rebase origin/main # replay onto the latest main
# 3 · push (force-with-lease if the branch was already pushed)
git push --force-with-lease
# 4 · open the PR; the merge into main happens on the shared branch
Interactive rebase — the killer feature
Plain rebase replays commits; interactive rebase lets you edit them
as they're replayed. Run git rebase -i HEAD~3 and your
editor opens with your last three commits, oldest first, each prefixed with a
command:
pick a1b2c3d add login form
squash e4f5g6h wip
squash i7j8k9l fix typo
reword m0n1o2p update readme
# pick keep the commit as-is
# squash fold it into the previous commit
# reword keep it, but change the message
# drop delete it entirely
# (you can also reorder the lines to reorder the commits)
This is how a messy afternoon of wip, wip,
fix typo, actually fix it becomes one clean,
well-described commit before anyone else ever sees it. Paired with a decent
commit-message convention — see the
Conventional Commits cheat
sheet — your PR history reads like a table of contents instead of a
stream of consciousness.
When it goes wrong — getting out alive
Rebase has a reputation for eating work, but it almost never actually loses anything. You just need to know the exits:
- Mid-rebase, conflicts everywhere, you regret everything:
git rebase --abortreturns you to exactly where you were before you started. No harm done. - Mid-rebase and you want to keep going: resolve the conflicts,
git addthe files, thengit rebase --continue. - Rebase already finished and it's wrong:
git refloglists every positionHEADhas held. Find where you were andgit reset --hard HEAD@{n}to go back. The reflog is your undo history — commits are almost never truly gone.
If you must force-push a rebased branch, use
--force-with-lease. It refuses to push if someone else has
pushed to the branch since you last fetched — a seatbelt against
overwriting a teammate's work. Plain --force has no seatbelt.
Wrap-up
Merge preserves history; rebase rewrites it. Merge for shared branches and
finished work; rebase for your own drafts — to sync with
main and to tidy your commits before anyone sees them. The
golden rule ("never rebase what others have pulled") is the whole safety
story, and git reflog is your parachute when you still manage to
slip.
Once your history is clean, make the messages worth keeping — the commit message generator drafts a conventional commit in seconds, and the cheat sheet covers the types and the 50-character rule. And if your clean, rebased branch still won't deploy because the browser is yelling about origins, that's a different post entirely.