
> git merge vs rebase — decide in 10 seconds▋
Merge or rebase, one question decides: did the commit leave your machine? The decision table, the fast-forward case, and the reflog fix when a rebase goes wrong.
mrsaynothing· 5 October 2026· 7 min read
git log --oneline --graphYou will type one of these two commands thousands of times in a career, and half the advice online makes it a religion — rebase everything, history must be clean on one side, never rebase, it destroys work on the other. Both camps skip the actual rule, which fits in ten seconds. This post is the decision, not the theology, and it belongs to the undo anything in Git cluster — same family as git revert vs reset, the other "two commands, one question" pair. Git itself was written from scratch in a ten-day sprint in April 2005; the merge-vs-rebase argument has been running ever since.
The five-minute checklist
What is the difference between git merge and git rebase?
Both commands land the same code; they disagree about what the history should say afterwards. Merge writes the disagreement down: one new commit with two parents, the divergence preserved for anyone reading the graph later. Rebase pretends you started from today's base: your three commits are replayed as three new commits with new SHAs, and the old ones become unreachable from any branch.
Ran both in a throwaway bench on git 2.47.3, same five commits each time. The merge run kept every original SHA and added one knot; the rebase run changed the feature commits' identities — c3 went in as 5d48940 and came out as 6b2be0a. Same tree, different history:
verify: git log --oneline --graph → a |\ knot means merge, a straight line means rebase won
When does a merge become a fast-forward?
If main has not moved since you branched, there is nothing to merge — git just slides the label forward. That is a fast-forward, and it is why rebase people get their clean line without forcing anything: rebase your feature onto main, then merge becomes a fast-forward, and the graph stays one straight line. From the bench:
* 67c0442 c4 feature 2 # rebased, then merged — fast-forward
* 6b2be0a c3 feature 1 # new SHAs — 5d48940 before the rebase
* 1398836 c5 main moved on
* facd5a4 c2 main work
* f45b000 c1 base
Five commits, zero knots. The trade: those prime SHAs no longer exist anywhere except your machine. Push them and a teammate's clone still holds the originals — two versions of "the same" commit, which is the exact mess section five is about.
verify: git merge --ff-only feature → Fast-forward in the output, no merge commit created
Which commits are still yours alone?
The answer is one command, not a feeling. git log origin/main..HEAD lists the commits origin has never seen — those are rebaseable. The mirror, git log origin/main..origin/main is empty by definition; the command that matters is checking the branch others build on. If the list includes a commit that another clone might already have — a push, an open pull request, a branch you pushed and force-pushed before — it is not yours alone, whatever your local graph claims.
Unpushed work is exactly what rebase was made for: replay it onto the updated base and your pull request reads as one clean sequence. This is also why "pull with rebase" is a sane default for private branches — git pull --rebase replays your local commits on top of whatever came in, instead of weaving a merge knot every time you sync. The stashed-work corner case lives in git stash a single file; the fork-sync variant in sync fork with upstream.
What breaks when you rebase a shared branch?
Every clone that holds the old commits now disagrees with yours about reality. Rebase gives your commits new SHAs; your pushed branch and your rewritten branch share no ancestry, so the next push needs --force — and everyone who pulled the old version gets to merge their copy of commits that "no longer exist". Their duplicates resurface in every future merge. This is not a hypothetical: the duplicate-commit merge is the classic aftermath, and unwinding it costs an afternoon.
The rule is one sentence long, and the official book carries it as a warning, not a suggestion:
Do not rebase commits that exist outside your repository and that people may have based work on.
— Pro Git, 2nd edition, "Git Branching — Rebasing"
Reviewer comments pinned to specific SHAs detach in the same move — rebase an open pull request and the review context evaporates with the old commit IDs.
How do you undo a rebase?
The old commits survive the rebase — reflog is where they hide. Rebase moves branch pointers; it does not delete objects. The bench recovery, after a reset --hard threw two commits away:
$ git reflog | head -4
1398836 HEAD@{0}: reset: moving to HEAD~2
67c0442 HEAD@{1}: merge feature: Fast-forward
1398836 HEAD@{2}: checkout: moving from feature to main
67c0442 HEAD@{3}: rebase (finish): returning to refs/heads/feature
$ git reset --hard HEAD@{1}
HEAD is now at 67c0442 c4 feature 2
One line, branch restored, staged and unstaged work with it. Reflog entries age out after about 90 days by default, so the rescue has a clock on it — same mechanism as in git undo last commit, and the fuller revert/reset decision lives in git revert vs reset.
verify: git reflog | head -3 → the pre-rebase tip sits at HEAD@{1}, one reset away
Which command does your situation need?
The table is the whole post in six rows — filter by your branch's situation.
| Situation | Command | Why it wins |
|---|---|---|
| Your feature branch, never pushed | git rebase main |
Linear history, one clean sequence for review |
| A branch others pull from | git merge |
No SHA rewrite — no duplicate commits in anyone's clone |
| Diverged local main, nothing pushed | git pull --rebase |
Syncs without a knot; your commits stay on top |
| Fork catching up with upstream | git merge upstream/main |
Matches the fork sync flow; PR stays attached |
| Hotfix that must land now | git merge --no-ff |
Visible marker of where the fix entered, even on a fast-forwardable branch |
| Rebase went wrong | git reset --hard HEAD@{1} |
Reflog still holds the pre-rebase tip (~90 days) |
Where does --squash fit in?
git merge --squash takes a branch's changes and stages them as one uncommitted lump — no merge commit, no link to the branch's history. Autocomplete says people search "merge vs rebase vs squash" daily: squash is the third answer, for throwing a branch away after landing its net change. It rewrites nothing; it just refuses to record the branch's internal steps.
Can a team just pick one?
Plenty do: rebase-local-work, merge-into-shared is the most common policy, and GitHub's own merge button keeps merge as the default. A repo-wide "rebase everything" policy works too — as long as no shared branch is ever rewritten, which is the one sentence that survives every workflow debate.
The ten-second version, one last time: did the commit leave your machine? No → rebase. Yes → merge. When the wrong one wins anyway, reflog has the exit — and the rest of the undo map lives in the git undo hub.
faq
— mrsaynothing
$ Related entries
We Deleted 16 Languages. The Traffic Barely Noticed.
Six locales stayed, sixteen got dropped, and Google still sends the deleted ones readers through redirects into English. What a translation has to prove before it ships here.
2026-10-04 · 5 min read

Git Revert vs Reset: Which One Saves Your History?
Git revert vs reset explained: which command undoes commits safely, when reset --hard destroys work, and how each rewrites shared GitHub history.
2026-09-15 · 7 min read

Git Cherry Pick: Multiple Commits, Branches, Conflicts
Git cherry-pick explained: copy a commit from another branch, pick multiple commits or a range, fix conflicts, and know when merge or rebase fits better.
2026-09-12 · 6 min read

Git Remove Untracked Files: Safe git clean Guide
Git remove untracked files safely with git clean: dry-run first, -fd for directories, -x for ignored files — plus why git clean isn't removing anything.
2026-09-09 · 8 min read
