Why migrate
Mercurial and Git are sibling systems - both distributed, both content-addressed, both supporting branches and merges. Migrating is mostly a question of tooling: most hosting providers, CI services, and IDEs now invest more in Git than in Mercurial. For teams choosing tooling alignment, the move is straightforward.
The standard tool: fast-export
The hg-fast-export tool emits Mercurial history in Git's fast-import stream format. It preserves authors, dates, branches, tags, and merges.
# Clone hg-fast-export
git clone https://github.com/frej/fast-export.git
cd ~/path-to-hg-repo
git init ../git-repo
cd ../git-repo
~/fast-export/hg-fast-export.sh -r ../path-to-hg-repo
git checkout HEAD
Authors mapping
Mercurial commit authors are free-form strings; Git expects "Name <email>". Build a mapping:
# authors.txt
"Jane Doe" = "Jane Doe <[email protected]>"
"bob" = "Bob Smith <[email protected]>"
~/fast-export/hg-fast-export.sh -r ../hg-repo -A authors.txt
Branch handling
Mercurial named branches are not the same as Git branches. By default, hg-fast-export maps named branches to Git branches, but the semantics differ:
- Mercurial named branches are permanent labels on commits.
- Git branches are mutable pointers.
If your team uses Mercurial bookmarks, those map more cleanly. Pass -B to migrate bookmarks as Git branches.
Closed branches
Mercurial allows "closing" branches without deletion. By default, closed branches become Git branches; you may want to delete them after migration:
git for-each-ref refs/heads/closed-* --format='%(refname:short)' \
| xargs -n 1 git branch -D
Tags
Mercurial tags are themselves commits to a .hgtags file. fast-export converts these to Git tags automatically. Verify:
hg tags
git tag
Counts should match (minus Mercurial's "tip" pseudo-tag).
Verifying integrity
hg log --template '{node}\n' | wc -l
git log --all --pretty=format:'%H' | wc -l
# Counts should match
Spot-check by comparing file content at known commits:
hg cat -r <hg-rev> path/to/file > /tmp/hg-file
git show <git-sha>:path/to/file > /tmp/git-file
diff /tmp/hg-file /tmp/git-file
Pushing to a Git host
git remote add origin https://github.com/team/repo.git
git push -u origin --all
git push origin --tags
Workflow translation
Mercurial users are often shocked by Git's index. Bookmarks behave like branches; named branches do not. hg pull equals git fetch, not git pull. hg commit commits everything tracked; git commit commits the index. A short workshop bridges the gap.
Extensions and hooks
Mercurial's extension system has no direct Git counterpart. mq (Mercurial Queues) → git rebase -i. histedit → also git rebase -i. Mercurial hooks rewrite as Git hooks; consult .hg/hgrc for what to migrate.
Aftercare
Keep the Mercurial repo read-only for at least a release cycle. Document the SHA mapping in case anyone needs to find an old changeset.