Linearizing Feature Branches with Mercurial Rebase: A Practical Workflow
Mercurial's rebase extension turns a tangled feature branch into a clean linear history for easier review. Learn when to use it, how to enable it, and a step-by-step example rebasing a local branch onto an updated default—while respecting phases safety.
05 Jul 2025, 21:52 UTC

The Problem: Merge-Heavy History Obscures Review
When a feature branch lives for days while the main line advances, merging it back creates a diamond-shaped DAG. Reviewers must mentally untangle interleaved changes, and bisect tools may land on merge commits that don't represent a single logical step. Mercurial's rebase extension solves this by transplanting a contiguous set of local changesets onto a new parent, producing a straight line of history that reads like a single narrative.
When Rebase Helps—and When It Doesn't
Rebase shines for draft (unpublished) changesets—work that exists only in your local repository. Mercurial's phases mechanism marks commits as draft until they are pushed to a publishing repository, at which point they become public. Rewriting public changesets breaks collaborators who have already based work on them, so the golden rule is: only rebase draft changesets.
Typical candidates:
- A feature branch you haven't pushed yet.
- A stack of local commits you want to clean up before a pull request.
- Backporting a fix onto an older maintenance branch.
Avoid rebase when:
- The changesets are already public (pushed to a shared repo).
- You want to preserve the exact integration points for audit or bisect clarity.
Enabling the Extension
Add one line to your ~/.hgrc (or the repository's .hg/hgrc) under the [extensions] section:
[extensions]
rebase =
Then verify with hg help rebase. No extra packages are needed; the extension ships with Mercurial core. Exact behavior and phases integration vary by release, so confirm against your installed version's help output.
Worked Example: Rebasing a Feature Branch onto Updated Default
Assume a repository where default has moved on while you developed a feature. Run all commands from the repository root; no special permissions are needed beyond normal write access to the repo.
- Start from a clean state (no uncommitted changes):
hg status # should show nothing - Identify the source and destination. Let the feature branch be named
feature-uiand the target bedefault:hg log -G -l 10 # visualize recent DAG - Run the rebase:
hg rebase -s feature-ui -d defaultThis says: take the changesets unique to
feature-ui(source) and replay them on top ofdefault(destination). The-sand-dflags accept any revision specifier (branch name, tag, hash,.for working directory parent). - Handle conflicts if they arise. Mercurial stops, marks files with conflict markers, and prints guidance. For each conflicted file:
If you decide the rebase isn't worth it:# edit the file to resolve conflicts hg resolve -m path/to/file # mark resolved hg rebase --continue # proceedhg rebase --abort # restores the pre-rebase state - Verify the new linear history:
hg log -G -l 15The former
feature-uicommits should now appear directly after the latestdefaulttip, with new hashes but identical diffs. The old line of development remains in the store (Mercurial does not delete the originals) but should no longer be the shared tip.
Trade-offs and Limitations
- Repeated rebases on the same files can force you to resolve similar conflicts multiple times, even when the final content is straightforward.
- Review tools that track comments by changeset hash may lose context when hashes change. Some teams mitigate this by rebasing only once, just before final review.
- Bisect behavior changes: a linear history means
hg bisecttests each logical step, but you lose the explicit merge commit that marked "feature integrated here." - Public history is off-limits: never rebase changesets already pushed to a shared repository unless the team explicitly coordinates history rewriting.
Next Steps
Create a throwaway repository and walk through the example above: commit a base file on default, branch a feature, return to default and commit an independent change, then rebase. Confirm the phases of the involved changesets with hg phase before and after—rewritten work should be draft, never public. Once comfortable, adopt a team convention: rebase local draft work onto the latest upstream tip before pushing. This keeps shared history linear, review-friendly, and bisectable—without rewriting anyone else's foundation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.