Using hg-git to work in git and push to hg

update: hg-git init’s a bare repo, for now create your .git with `git init` (assuming intree, see below) before `hg gexport`.

hg-git is a fantastic project by GitHub’s Scott Chacon that allows bidirectional communication between Mercurial and Git.

Most of the documentation is angled towards hg users contributing to git projects but being more in the git camp I have the reverse use for it.

Here are the basic steps I follow to get set up to work with git on an hg project.

First, we need a mercurial checkout. I’m going to use pyglet – an awesome little python windowing and graphics library. Some little play code I have uses it and the cocoa backend needs some love (diving head first into PyObjC has been.. interesting).

I’ll assume you have hg-git installed and enabled in your ~/.hgrc
$ hg clone https://pyglet.googlecode.com/hg/ pyglet

The basic operation to generate a git repository and convert hg commits to git commits is gexport. This will create refs in the git repo for each hg bookmark that exists. Hg bookmarks are essentially equivalent to git branches — named pointers to commits that move when new child commits are created. See http://mercurial.selenic.com/wiki/BookmarksExtension for more info.

Before we create the git repo let’s create bookmarks for the hg branches we would like to interact with in git (this can be done after the initial gexport as well).
$ hg bookmark hg/default -r default
$ hg bookmark hg/cocoa-port -r cocoa-port

I’m prefixing the refs with hg so we have slight namespace separation between our git branches and the refs that are updated with gexport

Ok. Let’s go ahead and create the git repo:
$ hg gexport

This can take some time with a large repo (and pyglet’s repo isn’t tiny) but almost all of the cost is one-time. This takes a little under 10 minutes on my machine.

By default gexport will create the git repo at .hg/git. I prefer my .git and .hg directories to live side-by-side. Just simply symlink the .git repo into the right place:

$ ln -s .hg/git .git

(You can make this the default behavior, see [1] below.)

From here let’s create a branch corresponding to the “hg” branch:
$ git branch cocoa-port hg/cocoa-port

And make our master the same as hg/default:
$ git reset hg/default

From here you can make commits on your git branches and pull them into your hg repo with:
$ hg gimport

Subsequent pulls/fetches and gexport calls will push new commits to the hg/ refs in git.

There you go! A push from there would get your git commits to your remote hg repo. You’re set up to work with git and publish to hg!


One annoyance you’ll likely notice is the issue of the .hg and .git directories showing up in git status and hg status repectively.

To hide .hg from git simply:
$ echo ".hg" >> .git/info/exclude

Unfortunately Mercurial doesn’t have quite as flexible of a local ignore file but we can get part of the way there with:
$ echo "[ui]
ignore = `pwd`/.hg/hgignore" >> .hg/hgrc

and then:
$ echo ".git" >> .hg/hgignore
The absolute path is required here for the ignore to work throughout the repository, unfortunately.


Here’s that again with less jabber:

$ hg clone https://pyglet.googlecode.com/hg/ pyglet
$ hg bookmark hg/default -r default
$ hg bookmark hg/cocoa-port -r cocoa-port
$ hg gexport
(wait)
$ ln -s .hg/git .git
$ git branch cocoa-port hg/cocoa-port
$ git reset hg/default
(hack, commit)
$ hg gimport
$ hg push

To set up ignores:
$ echo ".hg" >> .git/info/exclude
$ echo "[ui]
ignore = `pwd`/.hg/hgignore" >> .hg/hgrc

$ echo ".git" >> .hg/hgignore


Addendum

  1. You can change hg-git’s default behavior by adding this section to your ~/.hgrc:
    [git]
    intree=1
  • Pingback: uberVU - social comments

  • P

    FYI:

    `pwd`/.hg/hgignore”

    ends with a “smart quote” for some reason. The others are fine.

  • masklinn

    Unfortunately Mercurial doesn’t have quite as flexible of a local ignore file but we can get part of the way there with:

    You could just create a .hgignore file ignoring itself at the root of the repository you know… Or create a global hgignore file ignoring (among other things) .git, so you don’t have to repeat this maneuver all the time.

    Also, I’m pretty sure your hg ignorefile is broken: by default, mercurial’s ignore file uses regular expressions, not globbing, so you’re not ignoring “.git” you’re ignoring any file or directory whose name starts with an arbitrary character and is followed by “git”. Repository-wide.

  • http://www.mynext.co.uk steve

    Thank you for bringing the app to my attention. Having a test now.

  • Pingback: traviscline.com » Using hg-git to work in git and push to hg « Netcrema – creme de la social news via digg + delicious + stumpleupon + reddit

  • Pingback: Destillat #49 – Git, Mercurial, Subversion und Co. | Open Source und Wetware

  • Paul Bolle

    0) On the hg side I’m playing with this tweak in the .hg/hgrc file to make this even easier:

    [hooks]
    post-pull.bookmark = hg bookmark -f hg/default -r default
    post-pull.export = hg gexport

    On the git side I now have this configured:

    [alias]
    hpull = !hg pull

    1) So I only have to do a “git hpull” every now and then. After that I manually merge (remote branch) “hg/default” into (local branch) “default”.

    (I guess a shell script might make this setup more reliable and more transparent, ie allowing one to forget that you are actually using a clone of a hg repository.)

    2) By the way, I don’t push to hg repositories.