Skip to content

Rebase vs Merge: Choosing an Integration Strategy

Lesson 6 of 7Intermediate11 min readModern Git Workflows · RebasingVerified: Git 2.43.0 on Ubuntu 24.04

Rebasing and merging both integrate one line of work into another. They differ in what they leave behind: merging preserves both histories and records the integration; rebasing rewrites one history so it appears to have been built on the other all along.

Neither is better. They optimise for different things, and the right choice depends on what your team needs from its history.

Start from a branch that diverged after commit B:

Starting point: main and feature have both moved

A trunk lane labelled main with commits A, B and C. A branch lane labelled feature leaves main after commit B with commits D and E.

ABCDEmainfeature

Merging adds a commit joining the two lines:

Merge: both histories intact, integration recorded

A trunk lane labelled main with commits A, B, C and a merge commit M. A branch lane labelled feature leaves main after commit B with commits D and E, merging into M. Commit M has two parents.

ABCMDEmainfeatureD and E keep their original IDs. M records when and by whom the branch was integrated.

Rebasing replays the branch onto main’s tip, then fast-forwards:

Rebase: linear history, new commit objects

A single lane containing commits A, B, C, then D-prime and E-prime, with both main and feature labels at the end. The primed commits are new objects with the same changes as D and E.

ABCD'E'main, featureD' and E' are new commits. No record survives that a branch existed.

The files are identical in both cases. Only the history differs — and history is what you are choosing between.

MergeRebase
Commit IDsUnchangedAll rewritten
History shapeGraph, with branch structureLinear
Integration recordedYes, in the merge commitNo
Extra commitsOne merge commit per branchNone
Conflict resolutionOnce, against final statesPossibly once per replayed commit
Safe on shared branchesYesNo
Force push requiredNoYes
git log readabilityNeeds --first-parentReads directly
git bisectTraverses branch internalsStraight line
git blameFull detailFull detail
Reverting a whole branchOne git revert -m 1One revert per commit
Recovery if wrongRevert the mergeReflog
Learning curveLowModerate

Merging produces a graph that records how work actually happened: this branch existed, these commits were on it, it was integrated at this point. That is an accurate record.

Rebasing produces a straight line that records what the work was, discarding when and how it was integrated. That is a simpler narrative.

Which is better depends on whether you consider “there was a branch” to be information or noise. On a repository receiving thirty branches a week, thirty merge commits saying “these joined here” is mostly noise. On a repository where integration events matter — regulated software, release engineering — it is the point.

A merge commit answers: who integrated this, when, and from which branch. Rebasing answers none of those — after a rebase there is no evidence a branch ever existed.

If your process needs to demonstrate that a change was reviewed and integrated deliberately, merge commits carry that evidence in the repository itself rather than only in the hosting platform’s database.

git bisect works on both. On a linear history it steps through a straight sequence, and every commit was a deliberate state.

On a merged history it also visits commits inside branches, which may be intermediate states that do not build. Mitigations exist — git bisect skip, or git bisect start --first-parent to search only the mainline — but they are extra steps.

The counter-argument: rebased commits were never tested in their final position either. Each replayed commit is a new object whose parent differs from when CI ran. A rebased history can contain commits that do not build just as easily, unless you verify with git rebase --exec.

This is the practical difference people feel most.

Merging resolves each conflicting region once, comparing the final state of both branches against the merge base. One conflict, one decision.

Rebasing replays commits one at a time, so a conflict in an early commit can recur in every later commit touching the same region. On a branch with fifteen commits and a genuine overlap, this is genuinely tedious.

Two things reduce the pain: git rerere records resolutions and replays them automatically, and rebasing frequently keeps the conflicts small.

Merging never rewrites. Anyone can merge a branch anyone else is using, at any time, with no coordination.

Rebasing rewrites, so it is only safe while the commits are private. On a shared branch it orphans everyone else’s copy.

This is the single hardest constraint in the comparison, and it is not a matter of preference.

Undoing an entire branch differs meaningfully between the two.

After a merge, one command undoes everything the branch brought in:

Terminal window
git revert -m 1 <merge-commit>

After a rebase, the branch’s commits are individual commits on main with nothing marking them as a group. Undoing them means reverting each, in reverse order:

Terminal window
git revert --no-commit E' D'
git commit -m "Revert the parser feature"

--no-commit accumulates the reversals in the index so you can record them as one commit rather than several.

This favours merging when whole-branch rollback is a realistic operational need — which it is for teams that deploy on merge and occasionally need to back a feature out quickly.

The counterweight: reverting a merge has a trap of its own. Re-merging the branch afterwards restores nothing, because Git sees no new commits. Reverted rebased commits do not have that problem.

Both preserve the author field, so credit for writing the code survives either way.

Rebasing updates the committer field to whoever ran the rebase, with the current time. Merging leaves branch commits entirely untouched.

This matters if your organisation measures contribution by committer rather than author, or if commit timestamps feed into any reporting. It also interacts with commit signing: rebasing produces new commits, which are unsigned unless the rebase itself signs them. See Signed Commits.

A merge commit gives reviewers a single object to examine, and git show --cc displays exactly the regions that were resolved by hand — the best available audit of a conflict resolution.

A rebase leaves no such object. Conflict resolutions performed during the replay are folded silently into the individual commits, and there is no way afterwards to see which lines were the result of a resolution rather than the original work.

For most branches this does not matter. On a merge with substantial conflicts in sensitive code, it is a real argument for merging: the resolution becomes reviewable.

ScenarioPreferWhy
Updating your private branch from mainRebaseKeeps the branch linear, no merge commits
Updating a branch a colleague is also onMergeRebasing would orphan their copy
Integrating a finished branch into mainEitherTeam policy decides
A long-lived branch with many conflictsMergeResolve once instead of per commit
A short branch with clean commitsRebaseCheap, and gives linear history
A release or maintenance branchMergeNever rewrite shared release history
Open source, external contributionMerge or squashYou do not control the contributor’s clone
Cleaning up your own commits before reviewRebase (interactive)The only tool for the job
Undoing something already on mainNeithergit revertNo rewriting of shared history
Regulated environment needing audit trailMergeIntegration events recorded in-repository
Monorepo with very frequent integrationRebase or squashMerge commits would dominate the log

Most teams that have settled the argument use both, at different moments:

  1. Rebase your own branch during development to stay current with main, while the branch is private.
  2. Stop rebasing once review starts — add commits instead, so reviewers’ comments keep their anchors.
  3. Integrate with whatever the team has standardised on — merge commit, squash or rebase-and-merge.

This gets the benefits of rebasing (linear branch, conflicts resolved early, clean commits) without its risks (nothing shared is ever rewritten). It is a good default for most teams.

If your team integrates through pull requests, the merge button chooses. The three options map onto this comparison directly:

ButtonResultEquivalent to
Create a merge commitMerge commit, branch commits preservedgit merge --no-ff
Squash and mergeOne new commitSquash Merging
Rebase and mergeOne new commit per branch commit, linearRebase and Merge

Note that squashing is a third position not covered by “rebase versus merge”: linear history and no per-commit granularity. For many teams it is the pragmatic answer, because it delivers a readable main without anyone needing to master interactive rebase.

The decision is worth making once, explicitly, rather than per pull request. A short procedure:

  1. Does anything require an in-repository audit trail of integration? Regulated software, contractual obligations, or a need to demonstrate review independent of your hosting platform. If yes → merge commits, and stop here.

  2. Do you rely on git bisect regularly? If yes, you want either a linear history or the discipline that every commit builds. Rebase or squash, with a CI job checking each commit.

  3. How often does main receive changes? Above roughly ten branches a week, merge commits start to dominate the log. Rebase or squash keeps it readable.

  4. Are branch commits worth preserving? If they are mostly working notes, squash — it gives linear history and asks nothing of contributors. If they are deliberately structured, rebase-and-merge keeps that structure.

  5. How comfortable is the team with rebase? Rebase-and-merge assumes contributors can rebase and force push safely. If not, squash gives most of the benefit with none of the failure modes.

  6. Write it down and enforce it in settings — enable only the merge methods you have chosen.

Question 1 is the one that overrides the others. Everything after it is a readability and tooling preference, and reasonable teams land in different places.

The arguments above are mostly about the moment of integration. The more consequential question is what someone reading the repository in a year can recover.

On a merged history, git log --first-parent main reads as a list of integrations — one entry per branch, with the branch name in the merge message. Drilling into any of them shows the commits that made it up. The record answers “what landed, when, and what was in it”.

On a rebased history, git log main reads as a flat sequence of every commit, in the order they were integrated. It is easier to scan and harder to group: nothing marks where one unit of work ended and the next began, unless commit messages happen to make it obvious.

On a squashed history, git log main is one entry per unit of work. The clearest to read at a glance, and the detail underneath is gone.

Two practical consequences:

Generating release notes. Squashed and merged histories both give you natural units — one commit or one merge per change. A rebased history requires grouping by something else, usually a ticket reference in the message.

git blame archaeology. Rebased and merged histories both keep fine-grained commits, so a line traces to the commit that introduced it. A squashed history traces every line of a branch to one commit, and the reasoning is only recoverable from the linked pull request — assuming that link still resolves.

If your team frequently asks “why is this line like this?”, that favours preserving commits. If it frequently asks “what shipped in March?”, that favours merge commits or squashing.

Treating it as a matter of principle. Both work. The question is which trade-offs suit your team.

Rebasing a shared branch to get linear history. The linearity is not worth orphaning colleagues’ work.

Merging main into a feature branch daily out of habit. Produces a branch whose history is mostly merge commits and makes review harder. Sync when there is a reason.

Assuming rebasing is always cleaner. On a long branch with genuine conflicts, rebasing means resolving repeatedly. A merge resolves once.

Assuming merging is always safer. It is safer regarding rewriting. It is not safer regarding whether the result works — a clean merge can still be semantically broken.

Mixing methods without a rule. A main where some changes are merges, some squashed and some rebased is harder to read than any consistent choice.

Forgetting git revert exists. For undoing something already shared, neither rebase nor merge is the tool.

Merging records what happened. Rebasing records what you wish had happened.

A merge says: these two lines of work existed separately and were joined here. That is true, and it is sometimes more detail than anyone wants.

A rebase says: this work was built on top of that work. That is a simplification — it was not, originally — and it is often the more useful story.

Choose the one whose story your team needs. Only rebase a story nobody else has read.

  • Merging preserves both histories and adds a commit with two parents; rebasing rewrites one history.
  • The resulting files are identical; only the history differs.
  • Merging is always safe on shared branches; rebasing is not.
  • Merging resolves each conflict once; rebasing may present the same conflict per commit.
  • Merge commits record integration in the repository, not just in the hosting platform.
  • Bisecting is simpler on a linear history, but rebased commits are equally untested in their new positions.
  • The common hybrid: rebase privately, merge publicly, integrate by team policy.
  • Squash merging is a third option that gives linear history without per-commit granularity.

Build the same integration both ways and compare the results directly.

  1. Create a repository with commits A and B on main.
  2. Create feature and add two commits. Note their IDs.
  3. Back on main, add one commit so the branches diverge.
  4. Merge path: git switch -c try-merge main && git merge feature. Record the shape with git log --oneline --graph.
  5. Rebase path: git switch feature && git branch backup && git rebase main, then git switch -c try-rebase main && git merge --ff-only feature.
  6. Compare the two histories side by side.
  7. Confirm the files are identical: git diff try-merge try-rebase should print nothing.
  8. Compare the commit IDs on feature against your note from step 2, and against backup.
  9. On each, run git log --oneline --first-parent and note how differently they read.

Step 7 is the point: the code is the same either way. Everything you are choosing between is history.

The comparison assumes rebasing is available. The final lesson in this cluster covers the situations where it is not — and the nuance behind “never rebase public history”.