Choosing Mercurial Commit Phases for Collaborative Workflows
Learn how to use Mercurial’s phases (draft, secret, public) to prevent accidental pushes of unfinished work and choose the right state for each changeset in a collaborative workflow.
14 Mar 2026, 16:27 UTC

Problem and Takeaway
When multiple developers push changes to a shared Mercurial repository, unfinished work can accidentally become public, breaking the expectation that only reviewed code appears in the main line. The decision is whether to rely on manual discipline or to let Mercurial’s phase system automatically block pushes of draft changesets. The useful takeaway is: enable the default phases extension, use draft for work in progress, secret for truly local experiments, and only promote to public when the changeset is ready for sharing.
Decision and Constraints
You must decide how to manage the visibility of changesets before they are integrated into a shared branch. Constraints include:
- The remote repository may enforce a phase policy that rejects pushes of non‑public changesets.
- Local workflows may require temporary rewriting (e.g.,
hg histedit) of changesets that are not yet public. - New collaborators should encounter minimal confusion when they see push rejections.
Comparison of Phase Options
| Phase | Visibility | Push Allowed (remote enforces public) | Survives Rebase/Histedit | Typical Use |
|---|---|---|---|---|
draft |
Visible locally, not yet published | Blocked unless --force is used |
Yes – phase stays draft after rebasing | Work in progress that will be shared later |
secret |
Local only, never transmitted | Blocked (cannot be pushed at all) | Yes – remains secret after local rewrites | Experimental changes, temporary branches, or backup work |
public |
Already shared or intended for sharing | Allowed (subject to normal access controls) | No – changing phase to public makes the changeset immutable; rebasing creates a new public changeset | Ready‑for‑integration code, releases, or any changeset that has been pulled by others |
Trade‑offs
Draft offers a safety net: pushes are rejected by default, reminding you to review before sharing, yet the changeset can be rebased or edited locally without losing its draft status. The downside is that a forgotten --force can still push unfinished work if the remote policy is lax.
Secret guarantees that a changeset never leaves your clone, which is useful for risky experiments. However, because it cannot be pushed at all, you must remember to change the phase (or export via bundle) when you decide to share the work.
Public** is the default for any changeset that has been pulled from another repository. Once public, Mercurial treats it as immutable; rewriting history requires creating a new changeset, which can clutter history if overused. Enforcing a strict public‑only policy on a shared server prevents accidental exposure but can break workflows that rely on frequent history editing (e.g., feature‑branch squashing).
Concrete Implementation and Validation
Assume a central repository at ssh://[contact removed]/project that enforces a public‑only phase policy (the default for many hosting services). The steps below show how to create a draft changeset, verify that it is blocked, promote it to public, and then push successfully.
1. Verify the phases extension is active
Run this command in any clone; it should print help without error.
hg phase --help
If you see "unknown command", enable the extension by adding [extensions]
phase = to your .hgrc or the repository’s .hg/hgrc.
2. Create a draft changeset
Make a change, commit it, and check its phase.
# Edit a file
echo "TODO: implement feature" > src/new_feature.txt
# Commit
hg commit -m "Add placeholder for new feature"
# Show phase
hg log -r . --template "{phase}\n"
Expected output: draft.
3. Attempt to push (should be blocked)
hg push ssh://[contact removed]/project
Look for a message similar to:
pushing to ssh://[contact removed]/project searching for changes remote: abort: push would create new remote heads on branch 'default' remote: abort: cannot push draft changesets (use --force to override)
If the push succeeds, the remote is not enforcing the public phase policy; adjust the server configuration or accept the risk.
4. Promote the changeset to public
When the work is ready, change its phase explicitly.
# Identify the changeset revision (e.g., 1234)
hg phase -d public -r 1234
Verify the change:
hg log -r 1234 --template "{phase}\n"
Expected output: public.
5. Push again (should succeed)
hg push ssh://[contact removed]/project
You should see a normal push summary with no phase‑related errors.
6. Using secret for local experiments
To create a changeset that will never be pushed:
hg commit -m "Secret experiment" --secret
The --secret flag sets the phase to secret immediately. Attempting hg push will fail with "abort: cannot push secret changesets". To later share the work, first change the phase:
hg phase -d draft -r
# then follow steps 3‑5 above
Limitations and Practical Checks
Phases are stored with each changeset, so they survive clones, pulls, and rebases, but they are not transmitted unless the changeset itself is transmitted. If you pull from a repository that does not enforce phase policies, you may receive draft changesets that appear as public locally; always check the phase with hg log -r --template '{phase}\n' before assuming safety.
A quick way to audit the phase state of all outgoing changesets is:
hg outgoing --template '{rev} {phase}\n'
Any line showing draft or secret indicates a changeset that will be blocked by a strict remote.
Remember that changing a changeset’s phase rewrites its metadata; this operation is local and does not create a new changeset, but it cannot be undone without another phase command. If you need to revert a phase change, simply run the opposite command (e.g., hg phase -d draft -r ).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.