Table of Contents
Migrating from Git
A practical guide for Git users moving to Mercurial and Isurus. This covers conceptual differences, command equivalents, repository conversion, and common gotchas.
Why Mercurial?
Mercurial was designed with three priorities:
- Simplicity — Fewer concepts to learn. No staging area, no reflog, no detached HEAD state. The command set is smaller and more consistent.
- Safety — Pushed history is immutable by default (via phases). You cannot accidentally rewrite shared commits.
- Correctness — Branches are permanent metadata, not movable pointers. The history graph always reflects what actually happened.
Mercurial and Git are both distributed version control systems with similar capabilities. If you are productive with Git, you will be productive with Mercurial within a day.
Conceptual Differences
Branches vs Bookmarks
This is the biggest difference and the most common source of confusion.
Git branches are lightweight, movable pointers to commits. You create them, switch between them, and delete them freely. They leave no trace in history once merged and deleted.
Mercurial has two mechanisms:
- Named branches are permanent metadata embedded in each changeset. Once you commit on a named branch, that branch name is part of the changeset forever. Named branches cannot be deleted. They are best used for long-lived lines of development (e.g.,
stable,default). - Bookmarks are lightweight, movable pointers — exactly like Git branches. Create them, move between them, push them, delete them. Bookmarks are what you use for feature work.
Rule of thumb: If you would use a Git branch, use a Mercurial bookmark.
# Git # Mercurial
git branch my-feature hg bookmark my-feature
git checkout my-feature hg update my-feature
git branch -d my-feature hg bookmark -d my-feature
No Staging Area
Git has a two-step process: stage changes with git add, then commit the staged changes. This lets you craft commits from a subset of your modifications.
Mercurial has no staging area. When you run hg commit, it commits all modified tracked files. To commit a subset, list the files explicitly:
# Git: stage specific files, then commit
git add file1.go file2.go
git commit -m "Fix validation"
# Mercurial: commit specific files directly
hg commit -m "Fix validation" file1.go file2.go
This is simpler in practice. You never have to worry about what is staged vs. unstaged.
Phases
Git relies on the reflog and force-push restrictions to protect shared history. Mercurial uses phases — a built-in mechanism that tracks whether changesets are local or shared.
| Phase | Meaning | Mutable? |
|---|---|---|
| draft | Local, not yet pushed | Yes — can amend, rebase, strip |
| public | Pushed to a remote | No — immutable, cannot be rewritten |
When you push changesets, they transition from draft to public. This means you cannot accidentally rebase or amend a commit that others have already pulled. No --force needed — the system enforces it.
Isurus repositories use non-publishing mode, so pushed changesets remain draft until explicitly made public. This gives you more flexibility for collaborative work-in-progress.
Changesets vs Commits
Same concept, different name. Mercurial uses the term "changeset" (often abbreviated "cset"). Each changeset has:
- A local revision number (integer, specific to your clone — e.g.,
42) - A global hash (40-character hex string — e.g.,
a1b2c3d4e5f6...)
Use the hash when communicating with others. Revision numbers differ between clones.
Revsets
Mercurial has a powerful query language called revsets for selecting changesets. This goes far beyond what git log flags can do:
# Find all ancestors of current revision that mention "fix"
hg log -r "ancestors(.) and keyword('fix')"
# Find changesets by a specific author in the last 7 days
hg log -r "author('chris') and date('-7')"
# Find changesets that modified a specific file
hg log -r "file('src/main.go')"
# Find the merge base of two bookmarks
hg log -r "ancestor(feature-a, feature-b)"
# Find all draft changesets (not yet public)
hg log -r "draft()"
See hg help revsets for the full language reference.
Command Mapping
| Task | Git | Mercurial |
|---|---|---|
| Clone a repository | git clone URL |
hg clone URL |
| Initialize a new repo | git init |
hg init |
| Add files to tracking | git add FILE |
hg add FILE |
| Commit all changes | git add -A && git commit -m "msg" |
hg commit -m "msg" |
| Commit specific files | git add f1 f2 && git commit -m "msg" |
hg commit -m "msg" f1 f2 |
| Push changes | git push |
hg push |
| Pull changes | git pull |
hg pull -u |
| Fetch without merging | git fetch |
hg pull (without -u) |
| Check status | git status |
hg status |
| View diff | git diff |
hg diff |
| View log | git log |
hg log |
| View log (graph) | git log --graph |
hg log -G |
| Create branch/bookmark | git branch NAME |
hg bookmark NAME |
| Switch branch/bookmark | git checkout NAME |
hg update NAME |
| Delete branch/bookmark | git branch -d NAME |
hg bookmark -d NAME |
| Merge | git merge NAME |
hg merge NAME |
| Stash changes | git stash |
hg shelve |
| Unstash changes | git stash pop |
hg unshelve |
| Rebase | git rebase TARGET |
hg rebase -d TARGET |
| Cherry-pick | git cherry-pick REV |
hg graft -r REV |
| Tag a revision | git tag NAME |
hg tag NAME |
| Annotate/blame | git blame FILE |
hg annotate FILE |
| Discard uncommitted changes | git checkout -- FILE |
hg revert FILE |
| Discard all uncommitted changes | git checkout . |
hg revert --all |
| Undo a commit (new reverse commit) | git revert REV |
hg backout -r REV |
| Amend last commit | git commit --amend |
hg commit --amend |
| Show a specific commit | git show REV |
hg log -p -r REV |
Note:
hg rebaseandhg shelverequire extensions. Add them to your~/.hgrc:[extensions] rebase = shelve =
Converting a Git Repository
Method 1: hg-git Extension
The hg-git extension lets Mercurial interoperate with Git repositories directly.
Install hg-git
pip install hg-git
Add to your ~/.hgrc:
[extensions]
hggit =
Clone a Git Repo as Mercurial
# Clone from a Git URL (prefix with git+)
hg clone git+https://github.com/user/repo.git repo-hg
# Or clone from a local Git repo
hg clone git+file:///path/to/git-repo repo-hg
This creates a full Mercurial repository with all history converted. Git branches become Mercurial bookmarks.
Push to Isurus
cd repo-hg
# Set the Isurus remote
hg paths -a default ssh://your-isurus-instance/org/repo
# Push all bookmarks
hg push
Method 2: hg convert (Built-in)
Mercurial includes a convert extension that handles Git-to-Mercurial conversion without additional dependencies.
Enable the Extension
Add to your ~/.hgrc:
[extensions]
convert =
Convert a Local Git Repository
# Convert the Git repo to Mercurial
hg convert /path/to/git-repo /path/to/hg-repo
# Enter the new repo
cd /path/to/hg-repo
# Update to tip
hg update tip
Step-by-Step Walkthrough
# 1. Clone the Git repo locally (if needed)
git clone https://github.com/user/repo.git /tmp/repo-git
# 2. Enable the convert extension (if not already in ~/.hgrc)
# Add [extensions] convert = to ~/.hgrc
# 3. Run the conversion
hg convert /tmp/repo-git /tmp/repo-hg
# 4. Enter the converted repo and verify
cd /tmp/repo-hg
hg log -l 5
hg bookmarks
# 5. Set the Isurus remote as default push target
echo "[paths]
default = ssh://your-isurus-instance/org/repo" >> .hg/hgrc
# 6. Push to Isurus
hg push
# 7. Clean up
rm -rf /tmp/repo-git
The convert command preserves:
- Full commit history and messages
- Author information
- Tags (as Mercurial tags)
- Branches (as bookmarks)
Workflow Translation
Feature Branch Workflow
The most common Git workflow translates directly to Mercurial bookmarks:
# Git workflow # Mercurial workflow
git checkout -b my-feature hg bookmark my-feature
# ... make changes ... # ... make changes ...
git add -A # (not needed)
git commit -m "Add feature" hg commit -m "Add feature"
git push -u origin my-feature hg push -B my-feature
# Open PR on GitHub # Open PR on Isurus
Pull Requests
Pull requests in Isurus work the same way conceptually. They are bookmark-based:
- Create a bookmark for your feature.
- Make commits on that bookmark.
- Push the bookmark to Isurus.
- Create a Pull Request from your bookmark to the target branch (usually
default). - After review, merge the PR through the web UI.
The main difference: Git merges feature-branch into main. Isurus merges a bookmark into default (or another named branch).
Code Review
Isurus pull requests support:
- Markdown comments with code highlighting
- Line-by-line discussion
- Changeset diff view
- Issue auto-close via commit messages (e.g.,
closes #42)
Common Gotchas
No git add -p (Partial Staging)
Git's git add -p lets you stage individual hunks within a file. Mercurial has no staging area, so this does not exist.
Workaround: Commit specific files with hg commit file1 file2. If you need hunk-level granularity, use the record extension:
[extensions]
record =
hg record # Interactive hunk selection
hg push Pushes All Bookmarks
Unlike git push which pushes only the current branch by default, hg push pushes all new changesets and active bookmarks.
To push only a specific bookmark:
hg push -B my-feature
Named Branches Are Permanent
If you accidentally create a named branch (hg branch bad-name followed by a commit), that branch name is embedded in the changeset permanently. It cannot be removed.
Avoid this: Always use bookmarks for feature work. Named branches are only for long-lived lines like default or stable.
.hgignore Uses Regexp by Default
Git's .gitignore uses glob patterns. Mercurial's .hgignore uses regular expressions by default.
To use glob patterns (like .gitignore), add this at the top of your .hgignore:
syntax: glob
*.pyc
__pycache__/
node_modules/
.env
You can also mix syntaxes:
syntax: glob
*.pyc
node_modules/
syntax: regexp
^build/output-\d+
No git stash by Default
Mercurial does not have a built-in stash command. Enable the shelve extension:
[extensions]
shelve =
Then use it like git stash:
hg shelve # Save uncommitted changes
hg unshelve # Restore saved changes
hg shelve --list # List shelved changes
hg shelve --delete NAME # Delete a specific shelf
History Rewriting Is Phase-Aware
In Git, you can rebase or amend any commit and force-push. In Mercurial, you can only rewrite draft changesets. Once a changeset becomes public (pushed), it is immutable.
# This works (draft changeset)
hg commit --amend
# This fails if the changeset is public
hg rebase -d tip # abort: can't rebase public changeset
This is a feature, not a limitation. It prevents you from rewriting history that others depend on.
default Instead of main
Mercurial's default branch is called default, not main or master. When you create a repository on Isurus, the initial branch is default.
Recommended .hgrc for Git Users
If you are coming from Git, this ~/.hgrc configuration will make Mercurial feel familiar:
[ui]
username = Your Name <your@email.com>
merge = internal:merge3
[extensions]
rebase =
shelve =
strip =
[alias]
st = status
ci = commit
co = update
lg = log -G --template "{rev}:{short(node)} {bookmarks} {desc|firstline} ({date|age})\n"
where = log -r . --template "{rev}:{short(node)} [{branch}] {bookmarks}\n"
[diff]
git = true
The [diff] git = true setting makes hg diff output Git-compatible diff format, which may feel more familiar and works better with some editors and tools.
What's Next?
- Getting Started — Account setup, SSH keys, and first clone.
- Mercurial Cheat Sheet — Quick command reference.
- Pull Requests — Create and review PRs using bookmark workflows.
- CI/CD Guide — Automate builds and tests.