Using Mercurial’s hg evolve to Safely Rewrite Shared History
Learn how to rewrite pushed commits safely with Mercurial’s hg evolve extension, using obsolete markers and phase management to avoid risky force pushes.
20 Mar 2026, 11:53 UTC

The usual way to fix a mistake in a commit that has already been pushed is to force‑push or create a messy fixup. Those approaches can break the work of teammates and clutter the repository history.
Mercurial’s hg evolve extension changes that story. Instead of deleting or overwriting old changesets, it marks them as obsolete and links them to their replacements. The resulting metadata lets every clone automatically adjust to the new history, making rebasing, amending, and cleaning up safe even after a commit is shared.
The obsolete marker concept
When you run hg amend (or any command that rewrites a changeset), Mercurial does not discard the original changeset. It adds an obsolete marker that records which changeset has been superseded by another. This marker is stored in the repository’s internal graph and is sent to all clones during a normal hg pull. Because the marker is part of the shared history, a collaborator’s client can see that Changeset A is obsolete and automatically move their work to the new Changeset B, avoiding manual merge conflicts.
Phases and safe evolution
Mercurial uses phases — draft, secret, and public — to control when a changeset may be shared. The hg evolve workflow works best while a changeset is still in the draft phase. Once a changeset is marked public, it is considered immutable, and evolving it requires the --force flag, which is discouraged.
To keep history clean, you can place a new commit in the draft phase with hg phase --draft (or simply stay in the default draft phase). After amending, run hg evolve --summary to verify that the obsolete marker has been created, then push the updated history with a regular hg push. The push carries both the new changeset and the obsolete marker, and any remote clone that pulls will see the same marker.
Worked example
# 1. Add the missing file and amend the last commit hg add config.yaml hg amend -m "Add missing config" # 2. Verify the evolution locally (obsolete marker should appear) hg evolve --summary # 3. Ensure we are still in the draft phase (optional safety check) hg phase --draft # if you are not already in draft # 4. Push the evolved history to the remote repository hg push
After the push, run hg pull on a different clone. If the evolve extension is enabled there, the client will automatically treat the old changeset as obsolete and adjust its working tree without any manual intervention.
Trade‑offs and limitations
The main limitation is that collaborators must also have the hg evolve extension installed and enabled. If a teammate does not use evolve, they will see the old changeset as active and may encounter merge conflicts when pulling. In that case, the safety benefit disappears because the obsolete marker is unknown to them.
Another consideration is the phase model: evolving a changeset that is already public requires hg phase --secret or --force, which can be risky. The extension is safest when you keep work in the draft phase until you are ready to share.
Getting started
To use hg evolve, install the extension (for example via a package manager) and enable it in your .hgrc file:
[extensions] evolve =
If you installed the extension manually, replace the empty value with the full filesystem path to the .py file, e.g. evolve = /home/user/.local/lib/python3.11/site-packages/mercurialevolve.py. Once enabled, you can run hg evolve --summary after any amendment to see which changesets are marked obsolete.
Finally, always verify the synchronization by pulling on a secondary clone and confirming that the obsolete markers are present. This simple check ensures that the safe‑rewriting workflow works across your distributed team.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.