Git Rebase vs Merge: Which to Use and When
What each command does to your history, the golden rule of rebasing, and a branching workflow that avoids the arguments entirely.
Table of contents
- What each one does
- The golden rule
- What each is good for
- Squash merge: the pragmatic default
- Recovering from a bad rebase
- A workflow that avoids the argument
- Force-push safely
- Frequently asked questions
- Does rebasing lose commits?
- Why do I get the same conflict repeatedly during a rebase?
- Should I rebase or merge to update a long-running branch?
- What does --no-ff do?
- Related reading
- References
Merge and rebase both integrate one branch into another. They differ in what they do to history, and the right choice depends on whether anyone else has the commits.
What each one does#
Before:
main A---B---C
\
feature D---EMerge creates a commit with two parents. History is preserved exactly, including the fact that the work happened in parallel:
main A---B---C---M
\ /
feature D---ERebase replays your commits on top of the new base. History becomes linear, but D and E are new commits (D' and E') with different hashes:
main A---B---C---D'---E'That last point is the whole basis of the rule below.
The golden rule#
Never rebase commits that other people have based work on.
Rebasing rewrites history. If a colleague has pulled your branch and you rebase it, their copy and yours have diverged with no common ancestor — and resolving that costs everyone time.
Practical version: rebase your own unpushed or unshared feature branch freely. Never rebase main, or any branch someone else is working from.
What each is good for#
Rebase before opening a PR. Pulling main into your branch three times leaves three merge commits that say nothing. Rebasing gives reviewers a clean, linear series of your changes:
git fetch origin
git rebase origin/mainInteractive rebase to clean up. This is where rebase earns its keep — turning "wip", "fix typo", "actually fix it" into one coherent commit:
git rebase -i origin/main
# pick a1b2c3 Add JSON formatter
# squash d4e5f6 wip
# squash g7h8i9 fix typo
# reword j1k2l3 Add testsMerge to integrate a finished branch. A merge commit records that a feature landed as a unit, which is genuinely useful information when you are bisecting or reverting.
Squash merge: the pragmatic default#
Most teams settle here, and for good reason:
git merge --squash feature
# or GitHub's "Squash and merge"The whole branch becomes one commit on main. You get a perfectly linear, readable history where each commit is one reviewed change, and the messy intermediate commits stay in the PR where they are still visible if needed.
The trade-off: you lose the individual commits on main. For a branch of 15 exploratory commits that is a feature, not a loss. For a carefully-curated series of five logical commits, --no-ff merge or rebase preserves more useful information.
Recovering from a bad rebase#
Rebase feels dangerous because it rewrites. It is recoverable, because Git keeps a log of where every ref has been:
git reflog
# a1b2c3 HEAD@{0}: rebase finished
# d4e5f6 HEAD@{1}: rebase start ← the state before
git reset --hard HEAD@{1}Nothing is lost for at least 30 days by default. Knowing git reflog exists is what makes rebasing safe to learn.
Mid-rebase, you also always have:
git rebase --abort # back to where you started
git rebase --continue # after resolving a conflict
git rebase --skip # drop the current commitA workflow that avoids the argument#
- Branch from
main. - Commit freely — messy is fine.
- Before opening the PR:
git rebase -i origin/mainto tidy and to update the base. - Merge with squash (or a curated rebase for a well-structured series).
- Never rewrite
main.
Force-push safely#
After rebasing a pushed branch you must force. Use the safe form:
git push --force-with-lease--force-with-lease refuses if someone else has pushed since you last fetched. Plain --force overwrites their work silently. There is no reason to use plain --force on a shared remote.
Frequently asked questions#
Does rebasing lose commits?#
No — it creates new commits with the same changes. The originals remain in the reflog until it is pruned.
Why do I get the same conflict repeatedly during a rebase?#
Because each commit is replayed in turn and each hits the conflict. git rerere (reuse recorded resolution) remembers your resolutions and replays them automatically — worth enabling globally.
Should I rebase or merge to update a long-running branch?#
Rebase if it is yours alone. Merge if others are working on it. If it is long-running and shared, the real fix is to make it shorter-lived.
What does --no-ff do?#
Forces a merge commit even when a fast-forward is possible, so the branch's existence is recorded in history. Useful when you want the grouping visible.