Git Merge vs Rebase: When Should You Use Each
Photo by Unsplash on Unsplash
Table of Contents
The Short Version
I got burned by this early on. I rebased a branch a teammate had already checked out, force-pushed it, and then spent an afternoon untangling duplicated commits and a very confused Git state. So I care about getting the distinction right. Both commands answer the same question — "how do I bring these two lines of work together?" — with completely different philosophies about history.
This post walks through what each command does under the hood, the trade-offs, the one rule that keeps you out of trouble, and the workflow I actually use every day.
What Merge Actually Does
There's a special case worth knowing: the fast-forward merge. If `main` hasn't moved since you branched off it, Git doesn't need a merge commit at all — it just slides the `main` pointer forward to your feature tip. Clean and simple. You only get a real merge commit when both branches have new work, meaning the history genuinely diverged. If you'd rather force a merge commit even when a fast-forward is possible (some teams like the explicit marker that a feature landed), you pass `--no-ff`.
The big win with merge is honesty. The graph shows what really happened: you branched here, worked in parallel, and joined back there. Nothing gets rewritten, so it's non-destructive and it's genuinely hard to lose work. That's why it's the default for pulling feature branches into a shared `main`, and why it's the calmer choice if you're still building Git muscle memory.
The downside? On a busy repo, merge commits pile up. If ten people merge feature branches every day, your `git log` turns into a braided mess of merge commits and criss-crossing lines. It's accurate, but reading it can be a chore. Some of that noise is just the price of an honest record — and most days I'd rather have the truth than a pretty graph. Clear release conventions help keep things legible even when the graph is busy; I wrote about that in semantic versioning.
What Rebase Actually Does
Photo by Unsplash on Unsplash
Here's the crucial part: replaying a commit creates a *new* commit with a *new* hash. The originals are effectively discarded and rewritten. The content is usually identical, but the identity changes. That's what people mean when they say rebase "rewrites history," and it's exactly why the command is both powerful and dangerous.
The payoff is a clean, readable log. No merge commits, no tangled graph — just a straight line you can follow from the newest commit all the way back to the start of the project. And when you rebase your feature onto `main` before merging, the eventual merge can fast-forward, leaving `main` perfectly linear.
Then there's interactive rebase (`git rebase -i`), which is the feature I'd miss most if it vanished. It lets you rewrite your own commits before you share them: squash five "wip" commits into one clean commit, reword a bad message, reorder things, or drop a commit you never meant to keep. I use it constantly to turn the messy reality of how I actually wrote a feature into a tidy story that's easy to review. A reviewer who sees three logical commits instead of twenty "fix typo" commits will quietly thank you. The Atlassian merging-vs-rebasing guide has good diagrams if you want to watch the graph transform step by step.
The Golden Rule and How I Actually Work
So the workflow I settle on for most projects looks like this:
- While building a feature, I periodically run `git rebase main` (or `git pull --rebase`) on my *local* branch to keep it current with `main`. That keeps a pile of "merge main into feature" commits from cluttering my branch. - Right before I open the pull request, I do a quick interactive rebase to squash and reorder my commits into a sensible sequence. - Once the feature is reviewed and ready, it goes into `main` with a merge — often `--no-ff`, so there's a clear record that a whole feature landed at that point.
That combination gives me clean feature branches *and* an honest record on `main`. Plenty of teams pick one side and standardize instead — some ban rebase entirely for safety, some squash-merge everything — and that's completely fine. Consistency across the team matters more than which camp you land in. If your team already runs a specific package-manager and release flow, fold this into it; I covered some of those tooling trade-offs in npm vs Yarn vs pnpm.
One last practical note. If a rebase goes sideways mid-replay because of a conflict, don't panic. You fix the conflicting files, `git add` them, and run `git rebase --continue`. If you'd rather bail out completely, `git rebase --abort` puts everything back exactly how it was before you started. Knowing that abort is always there is what makes rebase feel safe enough to experiment with on a local branch in the first place.
It's also worth saying that the merge-versus-rebase debate gets more heated than it deserves. Both produce the same *code* in the end — the files on disk are identical whichever route you take. The only thing that differs is the shape of the history, and that's a readability preference, not a correctness one. So when a teammate is dogmatic about it, I try to remember the stakes are low: pick a convention, write it down, and move on to the actual work. The commands are tools, not a moral position, and the best choice is the one your whole team applies the same way every time.
Frequently Asked Questions
Is git rebase safe to use?
On a local, unshared branch, yes — it's safe and genuinely useful. The danger only shows up when you rebase a branch other people have already pulled, because rewriting those commits creates diverged history and duplicate commits for everyone else. Stick to the golden rule (never rebase shared branches) and rebase becomes a low-risk way to keep your work tidy. And if a rebase goes wrong, git rebase --abort restores the exact state you started from.
Does rebase delete my commits?
Not in the sense of losing your work — it replaces each commit with a new one that has the same changes but a different hash. The old commits are still recoverable through git reflog for a while, so a botched rebase is almost always fixable. What actually changes is the commit identity and the branch's base point, which is why the history looks linear afterward.
What does git merge --no-ff do?
It forces Git to create a merge commit even when a fast-forward would have been possible. Without it, if main hasn't moved, Git just slides the pointer forward and you get no record that a feature branch ever existed. Teams use --no-ff when they want every feature to leave a visible marker in the history showing exactly when it landed.
Should beginners use merge or rebase?
Start with merge. It's non-destructive, hard to get badly wrong, and it keeps an honest record of what happened, which is exactly what you want while you're still learning how Git thinks. You can add rebase to your toolkit later, once fast-forwards and commit hashes feel natural. The other [developer explainers](/blog) on the site build up the fundamentals that make rebase click faster.
How do I undo a rebase that went wrong?
If you're still mid-rebase, git rebase --abort cancels it and restores your branch to its pre-rebase state. If the rebase already finished and you want the old version back, run git reflog to find the commit hash your branch pointed at before, then git reset --hard to that hash. The reflog is your safety net — Git remembers where your branches used to point even after history is rewritten.
Try ToolsFuel
23+ free online tools for developers, designers, and everyone. No signup required.
Browse All Tools