7 minutes
Git merge vs git rebase
Sooner or later every Git user faces the same question: should I merge or rebase? Both commands integrate changes from one branch into another, and the end result — your branch contains the other branch’s commits — looks similar at first glance. The difference lies in how the history is shaped, and that has real consequences for your team.
Let’s look at what each command actually does, what it costs, and when to reach for one over the other.
The starting point
Both commands start from the same situation: two branches that have diverged. main moved forward after you created your feature branch, and now you want to bring your work up to date (or bring it back into main).
A --- B --- E (main)
\
C --- D (feature)
A and B are the common ancestors, E is a new commit on main, and C and D are your feature commits.
git merge
merge creates a new commit with two parents that ties the two histories together. Nothing that already exists is rewritten.
git checkout main
git merge feature
Or, if you want to update your feature branch with the latest main:
git checkout feature
git merge main
The resulting history keeps both lines visible and adds a merge commit M:
gitGraph
commit id: "A"
commit id: "B"
branch feature
checkout feature
commit id: "C"
commit id: "D"
checkout main
commit id: "E"
merge feature id: "M"
What merge buys you
- It is non-destructive. Existing commits keep their hashes and identity. This makes merge safe on branches that other people already have.
- It records context. The merge commit documents when and why two lines of work were joined. Tools like
git log --graphshow the true shape of the project’s history. - Conflicts are resolved once. If
CandEtouch the same lines, you resolve the conflict a single time, in the merge commit.
What merge costs you
- A noisy history. On an active repository, merging back and forth produces a forest of merge commits that makes
git loghard to read. - Bisecting gets harder. A merge commit can bundle several unrelated changes, so
git bisectmay point at a merge instead of the commit that actually broke something. - Fast-forward merges blur boundaries. If the target branch has not moved,
git mergesimply slides the pointer forward and creates no commit at all — the fact that a feature was merged disappears.
If you want a merge commit even when a fast-forward is possible, force it:
git merge --no-ff feature
This is a common convention: it preserves the “this was a feature branch” information in the graph.
git rebase
rebase takes the commits unique to your branch, reapplies them one by one on top of another base, and moves the branch pointer to the new copies. The originals are discarded.
git checkout feature
git rebase main
The result is a straight line — as if you had branched off the latest main in the first place:
gitGraph
commit id: "A"
commit id: "B"
branch feature
checkout feature
commit id: "C"
commit id: "D"
checkout main
commit id: "E"
branch feature-rebased
checkout feature-rebased
commit id: "C2"
commit id: "D2"
Notice that C and D became C2 and D2. They are new commits with new hashes, even though their content may be identical.
What rebase buys you
- A linear, readable history.
git logreads like a changelog: every commit sits on one line, in order. - Cleaner pull requests. Rebasing onto
mainbefore opening a PR means the reviewer sees only your changes, without unrelated merge commits. - Easier bisecting. Each change is isolated in its own commit.
- Powerful history editing. Interactive rebase lets you rewrite your own commits before sharing them:
git rebase -i main
In the editor you can reword messages, squash fixups into the previous commit, drop noise, or edit a commit to split it.
What rebase costs you
- It rewrites history. Every rebased commit gets a new hash. Anyone who already pulled the old commits now has a diverged history.
- Conflicts can repeat. If
CandDboth touch the same lines that changed inE, you may resolve the same conflict more than once, once per replayed commit. - It is a bigger mental model. Losing track of
HEADduring a rebase can be intimidating, thoughgit reflogandgit rebase --abortare reliable escape hatches.
The golden rule
Never rebase commits that exist outside your local repository.
A rebase rewrites hashes. If someone else has based work on the old commits, their branch and yours no longer share history, and Git cannot reconcile them cleanly. The usual formulation is: rebase your own private work, merge shared work.
A reasonable policy for most teams:
- Local, unpushed commits — rebase freely.
- Your own feature branch that nobody else uses — rebase freely, but coordinate before force-pushing.
main, release branches, anything others pull from — merge only.
What happens on conflicts
This is where the two commands feel most different in practice.
Merge stops once, shows you all conflicting hunks, and you resolve them together:
git merge main
# ... resolve files ...
git add .
git commit
Rebase stops at the first commit it cannot replay, then continues commit by commit:
git rebase main
# ... resolve files ...
git add .
git rebase --continue
# may stop again for the next commit
If it goes wrong, you can always bail out:
git rebase --abort
And if you finish and regret it, the original branch tip is still in the reflog:
git reflog
git reset --hard HEAD@{1}
Force-pushing after a rebase
If you already pushed the feature branch, rebasing makes your local branch diverge from the remote. A normal push is rejected, so you need a force push — but never a bare --force:
git push --force-with-lease
--force-with-lease refuses to overwrite the remote if it has commits you have not seen. It is the difference between “I meant to rewrite my branch” and “I just erased a colleague’s work.”
Comparison at a glance
git merge |
git rebase |
|
|---|---|---|
| History shape | Branching, with merge commits | Linear |
| Existing commits | Preserved, same hashes | Rewritten, new hashes |
| Safe on shared branches | Yes | No |
| Conflict resolution | Once, in the merge commit | Possibly once per replayed commit |
| Traceability of feature boundaries | Explicit merge commits (with --no-ff) |
Lost — commits appear inline |
git bisect friendliness |
Lower (merge commits bundle changes) | Higher (one change per commit) |
| Typical use | Integrating finished work into main |
Updating a private branch, cleaning up commits |
When to use which
Reach for rebase when:
- you want to bring a feature branch up to date with
mainbefore review; - you are cleaning up local commits — squashing fixups, fixing messages, reordering;
- the branch is yours alone and has not been shared;
- you want the project history to read as a linear sequence of changes.
A common flow:
git checkout feature
git fetch origin
git rebase origin/main
# resolve conflicts, then:
git push --force-with-lease
Reach for merge when:
- you are integrating a finished feature into a shared branch;
- the branch has already been pushed and others may have based work on it;
- you want to preserve the real topology of the project — who branched from where, and when it was joined;
- you are pulling changes into a long-lived branch and want a record of the integration.
A common flow:
git checkout main
git pull --ff-only
git merge --no-ff feature
git push
Many teams combine both: rebase locally to keep the branch tidy, merge to integrate it. That gives you a clean feature history and a shared history nobody has to force-push.
Merge, squash, or rebase?
A third option worth mentioning: git merge --squash collapses all commits from a branch into a single set of staged changes, which you then commit yourself. It produces a linear history like rebase, but without replaying commits one by one — and without preserving the individual commits. Platforms like GitHub and GitLab offer the same thing as a “Squash and merge” button.
Rough guidance:
- Merge commit — you care about the branch’s internal history and its boundaries.
- Squash — you care only about the net change, and the branch’s intermediate commits are noise.
- Rebase — you care about a linear history and the individual commits are meaningful.
Summary
merge and rebase are not rivals; they are two tools for two different jobs. Merge preserves history and is safe to use on anything shared. Rebase rewrites history to produce a clean, linear story and is safe only on work that has not left your machine.
The rule that keeps teams out of trouble is simple: rebase your private work, merge shared work — and if you must rewrite something that was already pushed, use --force-with-lease and make sure nobody else is standing on that branch.