Understanding Mercurial Changesets and History
Immutable Directed Acyclic Graph (DAG)
Mercurial repositories store changesets in a DAG where each commit points to its parent(s). Unlike Git, Mercurial emphasizes linear history and local state coherence, but it lacks some Git safeguards around conflicting histories during pushes or rebases.
Local Operations vs. Remote Sync
Operations like rebase
, histedit
, or strip
modify local history and can cause divergence if not pushed immediately or used inconsistently across collaborators. Interrupted operations (e.g., via signal kill or crash) can leave the repo in an unstable state.
Symptoms of Divergence or Corruption
- Pushes fail with
abort: push creates new remote heads
- Changesets appear missing when viewing logs or web interfaces
- CI systems report nonexistent revisions
- Rebase or merge errors referencing orphaned or stripped changesets
Root Causes
1. Aborted Rebase or Histedit Sessions
When a rebase
or histedit
operation is interrupted, it may leave dangling revisions or incorrectly marked ancestors, causing future merges or rebases to behave unexpectedly.
2. Inconsistent Strip or MQ Patch Usage
Use of hg strip
or MQ extensions without tracking pushes can remove changesets that exist remotely or in other local clones, creating divergence that’s hard to detect.
3. Force Push or Conflicting Heads
Allowing multiple heads on the same branch can lead to silent divergence. If not properly closed or merged, history forks may become unmergeable over time.
4. File System-Level Corruption
Interrupted writes, faulty disk hardware, or NFS latency during repository operations can corrupt the .hg/store
directory, causing index errors or unreadable changesets.
5. Use of Obsolete Extensions in Modern Mercurial
Some older extensions (e.g., MQ, Convert) may no longer interact safely with modern core behaviors, especially around phases and obsolescence markers.
Diagnostics and Verification
1. Use hg verify
Checks repository integrity by validating changelog, manifests, and filelog. Any inconsistencies here may indicate on-disk corruption.
2. Inspect Heads with hg heads
Reveals all heads in the repository. Multiple heads on the same branch often indicate unmerged or divergent work.
3. Compare with Remote via hg incoming
and hg outgoing
Shows differences between local and remote repos. Divergence here often uncovers missing changesets or rebased work.
4. Analyze History with hg log --graph
Visualize branching history and detect unintended forks or isolated changesets.
5. Check Active Histedit/Rebase State
Look for .hg/rebasestate
or .hg/histedit-state
files which indicate incomplete operations.
Step-by-Step Recovery Strategy
1. Backup the Repository
Before making changes, copy the entire repo to a safe location using tar
or hg clone
locally.
2. Abort Incomplete Operations
hg rebase --abort hg histedit --abort
These commands clean up partial state and restore a safe HEAD.
3. Merge Divergent Heads
Use hg update
to one head, then hg merge
to another. Commit the merge and push to consolidate history.
4. Use hg strip
Carefully
Only strip changesets if you are certain they do not exist in any remote or shared clone. Prefer using hg phase --public
to prevent accidental stripping of published changes.
5. Rebuild Index After Disk Issues
hg recover
Attempts to rebuild corrupted transaction data and restore repository operability after crashes or I/O errors.
Best Practices
- Avoid modifying public history (rebase, histedit) after pushing
- Use bookmarks and named branches to track work, not temporary changesets
- Enable phases and ensure CI/CD treats published commits as immutable
- Run
hg verify
periodically in maintenance scripts - Prefer
hg evolve
extension for modern history rewriting with safety
Conclusion
While Mercurial offers a clean and high-performance alternative to Git, its flexibility in local history editing can lead to hidden divergence and instability when used without discipline. By enforcing clear history rules, using diagnostic tools like hg verify
, and adopting modern practices like phases and evolve, teams can maintain a stable, collaborative codebase that retains the full power of distributed development without the risk of corruption or inconsistency.
FAQs
1. What’s the difference between divergence and corruption in Mercurial?
Divergence refers to conflicting heads in history, while corruption typically involves broken files or indexes. Both can disrupt workflows, but have different causes and solutions.
2. Is it safe to rebase in Mercurial?
Yes, but only on local or draft changesets. Never rebase public commits that have already been pushed or shared.
3. Can I undo a failed strip operation?
Not easily. Stripping is destructive. Always create a backup or use bookmarks and phases to manage changesets instead.
4. What is hg evolve
and should I use it?
The evolve
extension manages changeset evolution safely using obsolescence markers. It's recommended for collaborative rewriting in modern workflows.
5. How do I detect if a rebase or histedit is incomplete?
Check for presence of temporary state files in .hg/
. Commands like hg rebase --abort
will also notify you if cleanup is needed.