← Back to Help

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 rebase and hg shelve require 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:

  1. Create a bookmark for your feature.
  2. Make commits on that bookmark.
  3. Push the bookmark to Isurus.
  4. Create a Pull Request from your bookmark to the target branch (usually default).
  5. 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.

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?