# fast-export converts a Mercurial history into Git through git-fast-import

> fast-export is a set of Python scripts that convert a Mercurial repository into a Git one by feeding git-fast-import, with incremental imports, branch and tag renaming, author repair, content filtering and two ways to handle Mercurial largefiles. It is MIT licensed apart from hg-reset.py, which is GPLv2, and the last push was on 2026-04-05.

**frej/fast-export** — A mercurial to git converter using git-fast-import

- Repository: https://github.com/frej/fast-export
- Website: http://repo.or.cz/w/fast-export.git
- Stars: 837 · Forks: 275
- Language: Python
- License: not declared
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/frej-fast-export

## The import is four commands and one checkout

The usage section of the README is short enough to memorise. For a Mercurial repository, you create a Git repository, move into it, run the converter against the Mercurial repository, and then check out the result.

```bash
git init repo-git # or whatever
cd repo-git
hg-fast-export.sh -r <local-repo>
git checkout
```

That last command is not optional in practice, and the README is explicit about why. hg-fast-export does not automatically check out the newly imported repository, so you probably want to follow up the import with a git checkout command.

The mechanism underneath is git-fast-import, which is the important part. Rather than creating commits through the Git plumbing one at a time, the script streams a fast-import stream to Git, which is why migrating a repository with a long history is a matter of letting the process run rather than watching thousands of individual commits appear.

Incremental imports are supported as well, so the same command can be run again later to pick up new Mercurial commits. That is the feature that turns the tool from a one-off migration into a way of running the two version control systems side by side for a while.

The scripts come in pairs. hg-fast-export.py and hg-fast-export.sh are the converter, hg2git.py is a wrapper, and hg-reset.py and hg-reset.sh handle the reset operation described below.

## hg-reset is the undo button for a partial import

The reset script exists because incremental imports can go wrong. The README describes it as simple to use within a Git repository that is hg-fast-export'ed from Mercurial.

```bash
hg-reset.sh -R <revision>
```

The script gives hints on which branches need adjustment for starting over again, which is a kinder behaviour than silently rewriting history.

The reason this matters is the interaction with the incremental model. If a conversion picks up a commit that you did not want, or you decide the branch layout is wrong, you need to move the Git branches back to a known revision and let the next import re-create them. hg-reset is that operation.

The licence of this one script is different from the rest, and the README flags it. Most of the hg-* scripts are licensed under the MIT licence, but hg-reset.py is licensed under GPLv2 since it copies some code from the Mercurial sources. That distinction matters if you are assembling this into a distribution of your own, and it is the kind of detail that is easy to miss when the licensing section is a paragraph rather than a file per script.

## Two repair options for damaged metadata

Migrating history means moving whatever was in Mercurial, and in practice that includes broken author strings and non UTF-8 metadata. The tool has a dedicated option for each.

The first is the encoding option. When a Mercurial repository does not use utf-8 for encoding author strings and commit messages, the -e <encoding> command line option can be used to force fast-export to convert incoming meta data from that encoding to utf-8, and this encoding option is also applied to file names.

The second case is more specific. In some locales Mercurial uses different encodings for commit messages and for file names, and in that case the --fe <encoding> option overrides the -e option for file names alone.

The author mapping is the third tool. As the README puts it, Mercurial is much less picky about the syntax of author information than Git is, so an author mapping file can be given to hg-fast-export to fix up malformed author strings. The file is specified with the -A option, and the lines take the form of a quoted key and a quoted value.

```bash
"User <garbage\t<user@example.com>"="User <user@example.com>"
```

Inside the key and value strings, all escape sequences understood by the python unicode_escape encoding are supported, and strings are otherwise assumed to be UTF-8 encoded. Versions of fast-export prior to v171002 had a different syntax, and the old syntax can be enabled with the flag --mappings-are-raw.

That last detail is a good sign about the tool. A script that has been in use long enough to have a legacy syntax and a compatibility flag for it is a script that people have run on real repositories.

## Branch names, and a sanitiser the project does not trust

Git and Mercurial differ in what counts as a valid branch or tag name, and the -B and -T options let you supply mapping files to rename branches and tags respectively, using the same file syntax as the author mapping.

The detail worth knowing is the -n flag. The README says that when the -B and -T flags are used you will probably want to use -n to disable the built-in sanitizing of branch and tag names, and it describes that sanitizing as broken in many cases. The project says -n will become the default in the future, but that the default remains with the old behaviour in order not to break existing incremental conversions.

That is a compatibility decision stated in the open, which is better than the alternative.

Branch naming also has a special case. By default the default Mercurial branch is renamed to the master branch on Git, and if a Mercurial repository contains both default and master branches you will need to override this behaviour, which is what the -M <newName> option is for.

This is the kind of detail that decides whether a migration ends up with a confusing history. A Mercurial repository with a default branch and a feature branch called master would otherwise produce a collision that the tool has to resolve by guessing, and -M lets you say what you want instead.

## Content filtering lets you fix line endings during the export

The --filter-contents option runs an external filter for each exported file. The file's content is piped to the filter's standard input, and the filter's standard output is used in place of the file's original content.

The README gives the prototypical use, converting line endings in text files from CRLF to Git's preferred LF, and shows a shell script that does it.

```bash
#!/bin/sh
# $1 = pathname of exported file relative to the root of the repo
# $2 = Mercurial's hash of the file
# $3 = "1" if Mercurial reports the file as binary, otherwise "0"

if [ "$3" == "1" ]; then cat; else dos2unix -q; fi
```

The third argument is the interesting one. The filter is told whether Mercurial considers the file binary, so the same script can pass binaries through untouched and convert only text. The comment on the dos2unix -q option explains that it avoids returning an error code when handling non ASCII based text files, such as UTF-16 encoded ones.

Doing this during the export is much cheaper than doing it afterwards. A separate pass would mean rewriting history to change line endings, which is exactly the kind of operation the git_lfs_importer section warns about taking days on a large repository.

## Largefiles: a plugin that converts as it goes, or a second history rewrite

When migrating from Mercurial to Git, largefiles are exported as ordinary files by default, and the README gives the pre-export requirements before the export starts. You must have all largefiles from all Mercurial commits available locally, using hg clone --all-largefiles <repo-url> for a new clone or hg lfpull --rev "all()" for an existing repository.

Then there are two strategies, and the difference between them is the interesting part of this project.

The first is the git_lfs_importer plugin, which runs during the conversion. It performs Just-In-Time conversion by identifying large files during the export and writing LFS pointers immediately, which skips the need for a second pass, and it supports incremental conversion.

The second is the standard git lfs migrate import, a two step process where you export the full history from Mercurial to Git and then run a separate full history rewrite to move the files into LFS.

The README's explanation of when to prefer the plugin is quantitative. For monorepos or very large repositories, 100GiB and above, the traditional two step process can take days, because the history rewrite has to touch every commit again. Integrating the conversion into the export removes that overhead and allows incremental progress.

The plugin lives in the plugins directory, and the repository also carries a pluginloader directory, a tests directory and a t/ directory, which is the shape of a Perl style test layout for a Python project.

## A maintained-in-2023-2026 tool with a slow release rhythm

The release tags are dated by day rather than by version number, which is the convention of this project: v260405 from 2026-04-05, v250330 from 2025-03-30 and v231118 from 2023-11-18. The last push to the repository was on 2026-04-05, so the code is six months old as of this writing, and there is no newer tag to point at.

That matters for a migration tool, and the good news is that fast-export is not a library with an API you build against. It is a script you run, and a script that has not changed for six months is a script whose behaviour you can learn and rely on.

The support policy is stated in the README. Problems and bugs should go to the GitHub issue tracker, and the README asks you to check closed issues first, because the point of the tracker is that the next person with the same problem benefits. It also asks you not to contact the maintainer directly unless you are reporting a security bug. Frej Drejhammar is the current maintainer, and the original scripts were written by Rocco Rutte.

The homepage is a repository on repo.or.cz, which is a useful detail, because a mirrored or forked copy of a migration tool is easy to come by and the canonical source is where the issue tracker lives.

## Conclusion

fast-export fits a team that needs to move a long Mercurial history into Git without rewriting it by hand, since it drives git-fast-import directly and can be run again to pick up new commits. It is the wrong tool if you have no Mercurial history to migrate, if your repository depends on Mercurial specific concepts that Git has no equivalent for, or if you need a maintained upstream, since the last push was on 2026-04-05 and the most recent tag is v260405. Before you run it, make sure all largefiles are present locally with hg clone --all-largefiles or hg lfpull, decide whether to use the git_lfs_importer plugin or git lfs migrate import, and read the licence note, because hg-reset.py is GPLv2 while the rest of the scripts are MIT.

## FAQ

### How do I convert a Mercurial repository to Git with fast-export?

Run git init on a new directory, change into it, run hg-fast-export.sh -r with the path to your local Mercurial repository, and then run git checkout, since the converter does not check out the imported repository by itself.

### What are the requirements for running hg-fast-export?

Python 3.7 or newer and the Mercurial package 5.2 or newer, which can be installed with pip install mercurial. On Windows, the bash that comes with Git for Windows is known to work well.

### How do I fix broken author names when converting?

Give the converter an author mapping file with the -A option. The file contains lines of the form "<key>"="<value>", with python unicode_escape sequences supported inside the strings, and versions before v171002 can use the older syntax with the --mappings-are-raw flag.

### How do I convert Mercurial largefiles to Git LFS?

First make sure all largefiles are available locally with hg clone --all-largefiles or hg lfpull --rev "all()". Then either use the git_lfs_importer plugin, which converts during the export and supports incremental conversion, or export first and run git lfs migrate import as a second full history rewrite.

### What license is fast-export released under?

Most of the hg-* scripts are MIT licensed and were written by Rocco Rutte, while hg-reset.py is GPLv2 because it copies code from the Mercurial sources. The current maintainer is Frej Drejhammar, and support requests go to the GitHub issue tracker.

## Sources

- [frej/fast-export on GitHub](https://github.com/frej/fast-export)
- [Issues](https://github.com/frej/fast-export/issues)
- [Project website](http://repo.or.cz/w/fast-export.git)
- [README](https://github.com/frej/fast-export/blob/master/README.md)
- [Releases](https://github.com/frej/fast-export/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/frej-fast-export
