==== Git and Mercurial

(((Interoperation with other VCSs, Mercurial)))
(((Mercurial)))
The DVCS universe is larger than just Git.
In fact, there are many other systems in this space, each with their own angle on how to do distributed version control correctly.
Apart from Git, the most popular is Mercurial, and the two are very similar in many respects.

The good news, if you prefer Git's client-side behavior but are working with a project whose source code is controlled with Mercurial, is that there's a way to use Git as a client for a Mercurial-hosted repository.
Since the way Git talks to server repositories is through remotes, it should come as no surprise that this bridge is implemented as a remote helper.
The project's name is git-remote-hg, and it can be found at https://github.com/felipec/git-remote-hg[^].

===== git-remote-hg

First, you need to install git-remote-hg.
This basically entails dropping its file somewhere in your path, like so:

[source,console]
----
$ curl -o ~/bin/git-remote-hg \
  https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg
----

…assuming `~/bin` is in your `$PATH`.
Git-remote-hg has one other dependency: the `mercurial` library for Python.
If you have Python installed, this is as simple as:

[source,console]
----
$ pip install mercurial
----

If you don't have Python installed, visit https://www.python.org/[^] and get it first.

The last thing you'll need is the Mercurial client.
Go to https://www.mercurial-scm.org/[^] and install it if you haven't already.

Now you're ready to rock.
All you need is a Mercurial repository you can push to.
Fortunately, every Mercurial repository can act this way, so we'll just use the "hello world" repository everyone uses to learn Mercurial:

[source,console]
----
$ hg clone http://selenic.com/repo/hello /tmp/hello
----

===== Getting Started

Now that we have a suitable "`server-side`" repository, we can go through a typical workflow.
As you'll see, these two systems are similar enough that there isn't much friction.

As always with Git, first we clone:

[source,console]
----
$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile
* 65bb417 Create a standard 'hello, world' program
----

You'll notice that working with a Mercurial repository uses the standard `git clone` command.
That's because git-remote-hg is working at a fairly low level, using a similar mechanism to how Git's HTTP/S protocol is implemented (remote helpers).
Since Git and Mercurial are both designed for every client to have a full copy of the repository history, this command makes a full clone, including all the project's history, and does it fairly quickly.

The `log` command shows two commits, the latest of which is pointed to by a whole slew of refs.
It turns out some of these aren't actually there.
Let's take a look at what's actually in the `.git` directory:

[source,console]
----
$ tree .git/refs
.git/refs
├── heads
│   └── master
├── hg
│   └── origin
│       ├── bookmarks
│       │   └── master
│       └── branches
│           └── default
├── notes
│   └── hg
├── remotes
│   └── origin
│       └── HEAD
└── tags

9 directories, 5 files
----

Git-remote-hg is trying to make things more idiomatically Git-esque, but under the hood it's managing the conceptual mapping between two slightly different systems.
The `refs/hg` directory is where the actual remote refs are stored.
For example, the `refs/hg/origin/branches/default` is a Git ref file that contains the SHA-1 starting with "`ac7955c`", which is the commit that `master` points to.
So the `refs/hg` directory is kind of like a fake `refs/remotes/origin`, but it has the added distinction between bookmarks and branches.

The `notes/hg` file is the starting point for how git-remote-hg maps Git commit hashes to Mercurial changeset IDs.
Let's explore a bit:

[source,console]
----
$ cat notes/hg
d4c10386...

$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800

Notes for master

$ git ls-tree 1781c96...
100644 blob ac9117f...	65bb417...
100644 blob 485e178...	ac7955c...

$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9
----

So `refs/notes/hg` points to a tree, which in the Git object database is a list of other objects with names.
`git ls-tree` outputs the mode, type, object hash, and filename for items inside a tree.
Once we dig down to one of the tree items, we find that inside it is a blob named "`ac9117f`" (the SHA-1 hash of the commit pointed to by `master`), with contents "`0a04b98`" (which is the ID of the Mercurial changeset at the tip of the `default` branch).

The good news is that we mostly don't have to worry about all of this.
The typical workflow won't be very different from working with a Git remote.

There's one more thing we should attend to before we continue: ignores.
Mercurial and Git use a very similar mechanism for this, but it's likely you don't want to actually commit a `.gitignore` file into a Mercurial repository.
Fortunately, Git has a way to ignore files that's local to an on-disk repository, and the Mercurial format is compatible with Git, so you just have to copy it over:

[source,console]
----
$ cp .hgignore .git/info/exclude
----

The `.git/info/exclude` file acts just like a `.gitignore`, but isn't included in commits.

===== Workflow

Let's assume we've done some work and made some commits on the `master` branch, and you're ready to push it to the remote repository.
Here's what our repository looks like right now:

[source,console]
----
$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard 'hello, world' program
----

Our `master` branch is two commits ahead of `origin/master`, but those two commits exist only on our local machine.
Let's see if anyone else has been doing important work at the same time:

[source,console]
----
$ git fetch
From hg::/tmp/hello
   ac7955c..df85e87  master     -> origin/master
   ac7955c..df85e87  branches/default -> origin/branches/default
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard 'hello, world' program
----

Since we used the `--all` flag, we see the "`notes`" refs that are used internally by git-remote-hg, but we can ignore them.
The rest is what we expected; `origin/master` has advanced by one commit, and our history has now diverged.
Unlike the other systems we work with in this chapter, Mercurial is capable of handling merges, so we're not going to do anything fancy.

[source,console]
----
$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
 hello.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --decorate
*   0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard 'hello, world' program
----

Perfect.
We run the tests and everything passes, so we're ready to share our work with the rest of the team:

[source,console]
----
$ git push
To hg::/tmp/hello
   df85e87..0c64627  master -> master
----

That's it!
If you take a look at the Mercurial repository, you'll see that this did what we'd expect:

[source,console]
----
$ hg log -G --style compact
o    5[tip]:4,2   dc8fa4f932b8   2014-08-14 19:33 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   64f27bcefc35   2014-08-14 19:27 -0700   ben
| |    Update makefile
| |
| o  3:1   4256fc29598f   2014-08-14 19:27 -0700   ben
| |    Goodbye
| |
@ |  2   7db0b4848b3c   2014-08-14 19:30 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard 'hello, world' program
----

The changeset numbered _2_ was made by Mercurial, and the changesets numbered _3_ and _4_ were made by git-remote-hg, by pushing commits made with Git.

===== Branches and Bookmarks

Git has only one kind of branch: a reference that moves when commits are made.
In Mercurial, this kind of a reference is called a "`bookmark,`" and it behaves in much the same way as a Git branch.

Mercurial's concept of a "`branch`" is more heavyweight.
The branch that a changeset is made on is recorded _with the changeset_, which means it will always be in the repository history.
Here's an example of a commit that was made on the `develop` branch:

[source,console]
----
$ hg log -l 1
changeset:   6:8f65e5e02793
branch:      develop
tag:         tip
user:        Ben Straub <ben@straub.cc>
date:        Thu Aug 14 20:06:38 2014 -0700
summary:     More documentation
----

Note the line that begins with "`branch`".
Git can't really replicate this (and doesn't need to; both types of branch can be represented as a Git ref), but git-remote-hg needs to understand the difference, because Mercurial cares.

Creating Mercurial bookmarks is as easy as creating Git branches.
On the Git side:

[source,console]
----
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
 * [new branch]      featureA -> featureA
----

That's all there is to it.
On the Mercurial side, it looks like this:

[source,console]
----
$ hg bookmarks
   featureA                  5:bd5ac26f11f9
$ hg log --style compact -G
@  6[tip]   8f65e5e02793   2014-08-14 20:06 -0700   ben
|    More documentation
|
o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| |    update makefile
| |
| o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |    goodbye
| |
o |  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard 'hello, world' program
----

Note the new `[featureA]` tag on revision 5.
These act exactly like Git branches on the Git side, with one exception: you can't delete a bookmark from the Git side (this is a limitation of remote helpers).

You can work on a "`heavyweight`" Mercurial branch also: just put a branch in the `branches` namespace:

[source,console]
----
$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'
$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
 * [new branch]      branches/permanent -> branches/permanent
----

Here's what that looks like on the Mercurial side:

[source,console]
----
$ hg branches
permanent                      7:a4529d07aad4
develop                        6:8f65e5e02793
default                        5:bd5ac26f11f9 (inactive)
$ hg log -G
o  changeset:   7:a4529d07aad4
|  branch:      permanent
|  tag:         tip
|  parent:      5:bd5ac26f11f9
|  user:        Ben Straub <ben@straub.cc>
|  date:        Thu Aug 14 20:21:09 2014 -0700
|  summary:     A permanent change
|
| @  changeset:   6:8f65e5e02793
|/   branch:      develop
|    user:        Ben Straub <ben@straub.cc>
|    date:        Thu Aug 14 20:06:38 2014 -0700
|    summary:     More documentation
|
o    changeset:   5:bd5ac26f11f9
|\   bookmark:    featureA
| |  parent:      4:0434aaa6b91f
| |  parent:      2:f098c7f45c4f
| |  user:        Ben Straub <ben@straub.cc>
| |  date:        Thu Aug 14 20:02:21 2014 -0700
| |  summary:     Merge remote-tracking branch 'origin/master'
[...]
----

The branch name "`permanent`" was recorded with the changeset marked _7_.

From the Git side, working with either of these branch styles is the same: just checkout, commit, fetch, merge, pull, and push as you normally would.
One thing you should know is that Mercurial doesn't support rewriting history, only adding to it.
Here's what our Mercurial repository looks like after an interactive rebase and a force-push:

[source,console]
----
$ hg log --style compact -G
o  10[tip]   99611176cbc9   2014-08-14 20:21 -0700   ben
|    A permanent change
|
o  9   f23e12f939c3   2014-08-14 20:01 -0700   ben
|    Add some documentation
|
o  8:1   c16971d33922   2014-08-14 20:00 -0700   ben
|    goodbye
|
| o  7:5   a4529d07aad4   2014-08-14 20:21 -0700   ben
| |    A permanent change
| |
| | @  6   8f65e5e02793   2014-08-14 20:06 -0700   ben
| |/     More documentation
| |
| o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
| |\     Merge remote-tracking branch 'origin/master'
| | |
| | o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| | |    update makefile
| | |
+---o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |      goodbye
| |
| o  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program
----

Changesets _8_, _9_, and _10_ have been created and belong to the `permanent` branch, but the old changesets are still there.
This can be *very* confusing for your teammates who are using Mercurial, so try to avoid it.

===== Mercurial Summary

Git and Mercurial are similar enough that working across the boundary is fairly painless.
If you avoid changing history that's left your machine (as is generally recommended), you may not even be aware that the other end is Mercurial.
