Keeping merge commits with --rebase-merges Jump to heading

By default, git rebase linearises history: merge commits in the range are dropped, and the commits from both sides are replayed one after another. For a simple feature branch that is what you want. For an integration branch that deliberately merges several topic branches โ€” a release candidate assembled from features, or a long-lived branch that periodically merges sub-branches โ€” flattening destroys the structure that made it understandable. --rebase-merges keeps the shape: it replays commits along each side, then recreates each merge on top. It also exposes that structure in the todo list, so you can edit it. This page explains the todo listโ€™s new commands and the conflict behaviour that surprises people, within interactive rebase workflows.

When to use this approach Jump to heading

  • The branch you need to rebase contains merge commits that you want to keep.
  • You maintain an integration branch built by merging topic branches, and the base needs to move.
  • You want to reorder or drop whole topic branches inside an integration branch.
  • For a simple feature branch with no merges, a plain rebase is simpler; this page is not needed.

Step 1 โ€” See what a plain rebase would do Jump to heading

A plain rebase of a branch containing merges replays every non-merge commit linearly and discards the merges.

git log --oneline --graph main..integration
# *   e71b0c2 Merge branch 'topic/search' into integration
# |\
# | * 4c2d9a1 Add search ranking
# | * 9a0e3f7 Add search index
# * |   b80f115 Merge branch 'topic/export' into integration
# ...
Plain rebase against --rebase-mergesA plain rebase replays all the topic commits in one line and drops the merge commits, so the branch loses its structure. With --rebase-merges, each topic's commits are replayed on their own line and the merges are recreated, preserving the shape of the integration branch.git rebase maingit rebase --rebase-merges mainmerge commitsdroppedrecreatedtopic structureflattenedkepttodo listpick lines onlylabel, reset, mergeconflict resolutions in mergeslostredonekeeping the shape has a cost: merge resolutions are recomputed, not copied Plain rebase against --rebase-mergesA plain rebase replays all the topic commits in one line and drops the merge commits, so the branch loses its structure. With --rebase-merges, each topic's commits are replayed on their own line and the merges are recreated, preserving the shape of the integration branch.git rebase maingit rebase --rebase-merges mainmerge commitsdroppedrecreatedtopic structureflattenedkepttodo listpick lines onlylabel, reset, mergeconflict resolutions in mergeslostredonekeeping the shape has a cost: merge resolutions are recomputed, not copied

Step 2 โ€” Rebase with --rebase-merges and read the todo list Jump to heading

With -i, the todo list uses three extra commands: label names the current position, reset moves to a named position, and merge recreates a merge commit.

git rebase -i --rebase-merges main
label onto

# Branch topic-export
reset onto
pick 1a7c3e0 Add CSV export
pick 6f20b9d Add export scheduling
label topic-export

# Branch topic-search
reset onto
pick 9a0e3f7 Add search index
pick 4c2d9a1 Add search ranking
label topic-search

reset onto
merge -C b80f115 topic-export   # Merge branch 'topic/export' into integration
merge -C e71b0c2 topic-search   # Merge branch 'topic/search' into integration

Read it as a script: build each topic from onto, label its tip, then go back to onto and merge the topics in order. merge -C <sha> reuses the original mergeโ€™s commit message.

How --rebase-merges rebuilds an integration branchThe rebase labels the new base as onto, replays each topic's commits from onto and labels their tips, returns to onto, and recreates each merge in the original order, so the rebased branch has the same shape on the new base.label ontonew basereset + picktopic-exportreset + picktopic-searchmerge -Crecreate mergesSame shapeon new baseediting the todo list edits the structure: drop a whole topic by deleting its merge line How --rebase-merges rebuilds an integration branchThe rebase labels the new base as onto, replays each topic's commits from onto and labels their tips, returns to onto, and recreates each merge in the original order, so the rebased branch has the same shape on the new base.label ontonew basereset + picktopic-exportreset + picktopic-searchmerge -Crecreate mergesSame shapeon new baseediting the todo list edits the structure: drop a whole topic by deleting its merge line

Step 3 โ€” Expect merge conflicts to be resolved again Jump to heading

This is the part that surprises people. A recreated merge is a new merge: Git merges the rebased topic into the rebased integration branch, which may conflict where the original merge did, and the original resolution is not copied over automatically.

# If a recreated merge conflicts, resolve as for any merge, then continue
git status --short | grep '^UU'
git add -A && git rebase --continue

Enable rerere before rebasing an integration branch. If you resolved the same conflict when making the original merge with rerere on, Git replays your resolution automatically; see automating repeated conflict resolution with rerere.

git config rerere.enabled true
git config rerere.autoUpdate true    # stage reused resolutions automatically (review before continuing)

Step 4 โ€” Edit structure through the todo list Jump to heading

Because the structure is explicit, editing it is editing text. Drop a topic from the integration branch by deleting its merge line (and optionally its block). Reorder merges by reordering the merge lines. Add a topic by inserting a merge of an existing branch.

reset onto
merge -C e71b0c2 topic-search     # merge search first now
# merge -C b80f115 topic-export   <- removed: export is not in this release
merge topic/billing-fixes         # add a branch that was not merged before
Dropping one topic from an integration branchThe integration branch merged the export and search topics. Removing export's merge line from the todo list rebuilds the branch on the new base with only the search topic merged, while the export topic's commits remain on their own branch untouched.edit the todo list, change the release contentsmain (onto)Ntopic-searchNS1S2integrationNmerge Stopic-exportoldE1E2not mergedthe topic branch still exists; only the integration branch's contents changed Dropping one topic from an integration branchThe integration branch merged the export and search topics. Removing export's merge line from the todo list rebuilds the branch on the new base with only the search topic merged, while the export topic's commits remain on their own branch untouched.edit the todo list, change the release contentsmain (onto)Ntopic-searchNS1S2integrationNmerge Stopic-exportoldE1E2not mergedthe topic branch still exists; only the integration branch's contents changed

โš ๏ธ SAFETY WARNING: Rebasing an integration branch rewrites every commit on it, including merges. Anyone who based work on the old integration branch must move it, and the branch must be pushed with git push --force-with-lease. If the rebuilt branch is wrong, git reset --hard ORIG_HEAD restores the previous version immediately after the rebase.

Step 5 โ€” Verify that the rebuilt branch matches intent Jump to heading

Compare the old and new versions of the branch. git range-diff shows each commitโ€™s change before and after, including merges, which is the clearest way to confirm the rebase did not alter content unintentionally.

git range-diff ORIG_HEAD...HEAD
git diff ORIG_HEAD HEAD --stat     # differences here should be only the new base's changes

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Can I make --rebase-merges the default? Jump to heading

Set rebase.rebaseMerges = true. Many teams prefer to leave it off and use it explicitly, because flattening is the right default for ordinary feature branches.

What does rebase-cousins mean? Jump to heading

By default, topics that branched from a commit outside the rebased range keep their original base. --rebase-merges=rebase-cousins moves those onto the new base too, which is usually what you want for an integration branch built from topics of mixed age.

Why not just merge main into the integration branch instead? Jump to heading

Often that is the better choice: it records the update without rewriting anything. Rebasing with merges is for when you need a clean rebuilt branch โ€” typically when preparing a release candidate or when the integration branch is private to one person.