Git Merge vs Rebase: What's the Difference (and Which to Use When)
Both bring one branch's changes into another — the real difference is whether your history keeps an honest record of what actually happened, or gets rewritten into a straight line that never existed.
`git merge` and `git rebase` both solve the same basic problem — bringing changes from one branch into another — and this is exactly why they get compared so often. But they take genuinely different approaches to history, and picking the wrong one for a given situation is one of the more common sources of confusing, hard-to-untangle Git problems, especially on a team.
What `git merge` actually does
`git merge` takes the tip of another branch and combines it with your current branch, creating a new merge commit that has two parents — the tip of your branch and the tip of the branch being merged in. Nothing about either branch's existing commit history changes; the merge commit simply records the point where the two histories came together. If you look at the log afterward, you'll see the full, real sequence of events: work happened on two branches in parallel, and then they were joined at a specific point.
Sponsored
What `git rebase` actually does
`git rebase` takes your branch's commits and replays them one by one on top of a new base — typically the current tip of the branch you're rebasing onto — creating entirely new commits with new hashes that contain the same changes but sit at a different point in history. The result is a clean, linear sequence: it looks exactly as if you had started your work from the latest version of the base branch all along, even though that's not literally what happened. No merge commit is created; your branch's history is rewritten to sit "after" the base branch's latest commit instead of branching off an earlier point.
The core trade-off
- Merge preserves true history — every commit that ever existed stays exactly as it was, and the merge commit is an honest record of when and how two lines of work came together. The cost: the commit graph can get genuinely tangled on a busy repository with frequent merges in both directions, making `git log --graph` harder to read.
- Rebase produces clean, linear history — a `git log` on a rebased branch reads like a single, straightforward sequence of changes, which is much easier to scan and reason about. The cost: it rewrites history — every rebased commit gets a new hash, which is where most rebase-related problems come from.
Fast-forward merges — the case where there's no real choice to make
Not every merge creates a merge commit. If the branch you're merging into hasn't moved at all since your feature branch split off from it, Git can perform a fast-forward merge — it simply moves the base branch's pointer forward to your feature branch's latest commit, since there's no divergent history to reconcile. No merge commit, no conflict, just a straight extension of the existing line. This is why a repository with infrequent, sequential work can look perfectly linear even though nobody ever ran `git rebase` — fast-forwards happen automatically whenever there's genuinely nothing to reconcile. `git merge --no-ff` forces a merge commit even when a fast-forward would otherwise be possible, which some teams prefer specifically so every feature's integration point stays visible in the log, rather than disappearing into an indistinguishable straight line.
Squash merge — a third option worth knowing
Beyond a plain merge commit and a rebase, many teams use a squash merge when integrating a finished pull request — this takes every commit on the feature branch and condenses them into a single new commit applied to the base branch, with the feature branch's individual commit history left behind entirely (not incorporated at all, not even rewritten). The main branch ends up with one clean commit per feature, which is the linear-history benefit rebase offers, but achieved at integration time rather than by rewriting the feature branch itself beforehand — and critically, it doesn't touch the base branch's existing commits, so it's just as safe on shared branches as a regular merge. Many hosted Git platforms (GitHub, GitLab, Bitbucket) offer this as a one-click "Squash and merge" option directly in the pull request UI, which is why it's become such a common default for teams that want a clean main-branch history without needing every contributor to personally manage interactive rebases correctly.
The rule that actually matters: never rebase shared history
This is the single most important practical rule, and it's the reason rebase has a reputation for being dangerous when it's actually just commonly misused. Rebasing rewrites commit hashes — if you rebase a branch that other people have already pulled and built work on top of, their local history no longer matches yours, and pushing your rebased version forces everyone else into a confusing reconciliation (Git will refuse a plain push and require a force-push, which can silently discard others' work if done carelessly). The safe, standard guidance: rebase freely on commits that exist only in your own local, unpushed work — cleaning up your own branch before opening a pull request is exactly what rebase is good for. Never rebase a branch other people are already working from — use merge there instead, precisely because merge never rewrites existing commits.
A common, safe workflow that uses both
- Create a feature branch and commit your work locally, in whatever messy, iterative sequence actually happened ("fix typo," "actually fix it," "wip," and so on) — nobody else has this branch yet, so it's entirely yours to rewrite.
- Before opening a pull request, `git rebase` your branch onto the latest version of the main branch, and optionally use an interactive rebase (`git rebase -i`) to squash and reorder your messy local commits into a small number of clean, well-described ones.
- Push your cleaned-up branch and open the pull request — reviewers now see a tidy, linear, easy-to-follow set of commits instead of your actual messy work-in-progress history.
- Once the pull request is approved, the team merges it into the main branch — typically via a regular merge commit (or a "squash merge," which condenses the whole branch into one commit on the main branch), preserving an honest record on the shared branch of when this feature was integrated.
This workflow gets the benefit of both tools: a clean, readable history on your own branch (via rebase, done before anyone else depends on it) and an honest, non-destructive record on the shared branch (via merge, since that's the point where history becomes genuinely shared and must not be rewritten).
Interactive rebase — cleaning up your own commits
`git rebase -i` opens an editable list of your branch's commits, letting you squash several commits into one, reword commit messages, reorder commits, or drop a commit entirely — all before anyone else sees this history. This is the tool most people actually mean when they say "I rebased to clean things up" — it's less about the base branch moving and more about turning ten scrappy, real-time commits into two or three commits that actually make sense to a reviewer six months from now.
Conflict resolution: similar experience, different context
Both merge and rebase can hit conflicts when the same lines of code were changed differently on both sides — the resolution mechanics (edit the conflicted file, mark it resolved, continue) are nearly identical either way. The practical difference: a merge conflict is resolved once, in the single merge commit. A rebase conflict can recur multiple times — once for every commit being replayed that touches the conflicting lines — since each commit is applied individually onto the new base. For a feature branch with many small, incremental commits touching the same file repeatedly, this can mean resolving conceptually the same conflict several times over during one rebase, which is a genuine practical annoyance worth knowing about going in (interactive rebase's squash option, applied before the rebase, can reduce this by collapsing multiple small commits into fewer, larger ones first).
`git pull --rebase` — the other place this choice shows up
Beyond feature-branch cleanup, this same merge-vs-rebase choice appears every time you run `git pull` on a branch that has both local and remote commits diverging — by default, `git pull` performs a merge (creating a merge commit to reconcile local and remote history), but `git pull --rebase` replays your local commits on top of the freshly fetched remote ones instead, avoiding an extra merge commit for what's often just "I made one small local commit while a teammate pushed something else." Many teams configure `git config pull.rebase true` globally specifically to keep this very common, low-stakes case from cluttering history with routine merge commits — this is a different, generally lower-risk use of rebase than rewriting a whole feature branch, since it typically only affects your own most recent local commit(s).
Which one should you actually default to
- Default to rebase for: cleaning up your own local, unpushed commits before opening a pull request; keeping a personal feature branch up to date with a fast-moving main branch while you're the only one working on it; routine `git pull --rebase` for your own small local commits.
- Default to merge for: integrating a completed, reviewed feature branch into the shared main branch; any branch other people have already pulled and are actively working from; situations where preserving an exact, honest record of history matters more than a clean linear log (some teams and audits specifically want to see when and how branches were actually integrated).
The short version: merge and rebase both integrate changes between branches, but merge does it by recording an honest join point in history, while rebase does it by rewriting commits onto a new base for a cleaner, linear log. Neither is objectively correct — the right choice depends entirely on whether the history you're changing is still exclusively yours (rebase is fine, even preferable) or already shared with others (merge is the safe, non-destructive choice). Most teams end up using both, just in different, well-understood situations rather than picking one exclusively.
Recovering from a rebase that went wrong
A rebase that hits an unexpected conflict storm, or one you started before realising the branch was already shared, doesn't have to be a crisis. `git rebase --abort` cleanly cancels an in-progress rebase and returns the branch to exactly the state it was in before you started, with nothing lost — a good first move the moment a rebase feels like it's going sideways rather than pushing through conflicts you're unsure about. Even after a rebase completes, Git's `reflog` (`git reflog`) keeps a record of where your branch pointed before the rebase, for a limited but usually generous window, meaning an already-finished rebase you regret can typically still be undone by resetting back to the pre-rebase commit shown in the reflog. This safety net is worth knowing about specifically because it removes a lot of the fear that otherwise makes people avoid rebase entirely, even in the cases (cleaning up your own unpushed work) where it's genuinely the right, low-risk tool.
Treat that safety net as a backstop, not a substitute for the core rule, though — reflog entries do eventually expire, and recovering a complex rebase after the fact is more stressful than simply not rebasing shared commits in the first place. The habit worth building is checking, before every rebase, whether the branch is still exclusively local; that one moment of caution prevents almost every situation where the reflog would actually need to be used as a rescue.
A note on terminology that trips up newcomers
"Rebasing onto main" and "merging main into my branch" sound like they should produce similar results — both bring the base branch's newer commits into your feature branch — and functionally, for the files themselves, they often do. The difference is entirely in what happens to your branch's own commit history in the process: merging main into your feature branch adds a merge commit and leaves your existing commits untouched; rebasing onto main removes your existing commits and creates new ones that sit after main's latest commit instead. If a teammate says "just rebase before you push" versus "merge in the latest main first," they're asking for genuinely different operations with different consequences for anyone else who might already have your branch — worth clarifying rather than assuming, especially on a team still building shared conventions around this.
Tools used in this article
Sponsored
Frequently asked questions
No — rebase is only risky when applied to commits other people already have. Rebasing your own local, unpushed commits is safe and a common way to clean up history before opening a pull request.
TechToolsCenter Team
Product & Tools
The team behind TechToolsCenter — building fast, private, browser-based tools and writing practical guides on how to get the most out of them.
Related articles
SSR vs SSG vs CSR: Server-Side Rendering, Static Site Generation and Client-Side Rendering Explained
The same three letters keep showing up in every modern framework's docs — the real question each one answers is simply: at what point does the HTML a browser receives actually get built?
What Is a CDN, and How Does It Actually Work?
A CDN doesn't make your server faster — it puts copies of your content physically closer to the people requesting it, so the distance data has to travel shrinks instead of the server itself changing.
SQL vs NoSQL Databases: What's Actually Different
The real divide isn't "SQL is old, NoSQL is new" — it's whether your data fits neatly into consistent, related tables, or is naturally varied, nested, and easier to reason about as flexible documents.