Safe Shared History Rewriting in Mercurial with hg-evolve
Use Mercurial's hg-evolve extension and obsolescence markers to amend pushed commits without breaking teammates' checkouts.
23 Sept 2025, 07:39 UTC

You push a feature branch, then discover the last commit contains a secret, a broken test, or a confusing message. In Mercurial, amending that commit creates a new changeset, but anyone who already pulled the old one now has divergent history. The usual fix—a force-push—overwrites the remote and can break every teammate's checkout.
hg-evolve offers a more surgical option. It records an 'obsolescence marker' that links the old changeset to its successor. Teammates who pull receive that marker, and Mercurial can move their local work onto the new commit instead of leaving two parallel heads.
How obsolescence markers work
A changeset is Mercurial's unit of committed work, identified by a changeset ID. An obsolescence marker is metadata stating that one changeset has been replaced by another. When you amend a commit with the evolve extension enabled, Mercurial does not simply hide the old changeset. It writes a marker saying the old changeset is obsolete and points to the new one.
This matters for distributed teams. If a colleague based work on the old changeset, their client can see the marker on pull and rebase their commits onto the successor automatically. Without the marker, they would see an orphaned commit and face a manual merge.
Enable evolve and rewrite a pushed commit
Add the extension to your Mercurial configuration. The extension is not part of the core distribution, so it must be enabled explicitly.
[extensions] evolve =
Run this in your repository root with write access to the working directory:
- Amend the commit:
- Push the new changeset and markers:
- Teammate synchronizes:
hg commit --amend -m "Remove accidental secret"
The previous changeset is now marked obsolete. Confirm it:
hg log --hidden --obsolete
hg push
This requires push permission to the remote. Mercurial sends both the successor changeset and the obsolescence markers.
hg pull --update
If they had commits on top of the old version, their local work moves onto the successor. If their client lacks evolve, they may see the old changeset as active instead.
Comparison: force-push versus evolve
| Behavior | Force-push | hg-evolve |
|---|---|---|
| Remote history | Overwritten | Updated with successor and marker |
| Teammate with local work | Divergent heads; manual merge | Automatic rebase onto successor |
| Old changeset | Discarded | Marked obsolete, retained in store |
| Extension required | No | Yes, on all clients for full benefit |
Limitations and practical checks
Evolve is not a magic wand. Every collaborator should enable the extension; otherwise they see obsolete changesets as normal commits. Also avoid rewriting history on branches already deployed to production, because deployment tooling may still reference the old changeset ID.
After pushing, verify the marker arrived by cloning the repository fresh or asking a teammate to run:
hg incoming
Look for the new changeset and associated obsolescence data in the output. There is no safe remote rollback once markers are pushed, so coordinate with the team before rewriting shared branches.
If your team frequently rewrites feature branches and struggles with divergent heads after rebasing, enable hg-evolve and make obsolescence markers part of your workflow. It turns history rewriting from a destructive overwrite into a tracked transition.