I remember a Q&A session during the most recent 360|idev where several of us took to discussing our favorite flavors of version control. During that discussion I asked whether anyone knew of a sort of über version control system that would allow me to pull code from each of the three most popular version control systems (git, mercurial, subversion). Given that there is a lot of fantastic code on bitbucket (mercurial), github (git), google code (subversion / mercurial), and elsewhere, we all agreed that it would likely prove very useful to be able to use some overarching version control system to pull up-to-date code from more or less anywhere in a manner similar to the way svn-externals handles pulling subversion-based subprojects into a subversion working copy.

I have a number of projects using Subversion for version control, a few using Mercurial, and I even maintain a private branch of Three20 using git. When an external project is using the same version control system as the project itself, life is generally simple. In the gogoDocs subversion repository, for example, the svn-externals mechanism happily keeps the GData API at a specific revision for me. Almost all of my projects use code from some other project that uses a “foreign” version control system. In gogoDocs, where I keep my copy of the GData API close to the bleeding edge, the version of Three20 I’m using is woefully out of date. The reason for this is pretty simple. It’s difficult to update Three20 because it requires a lot of voodoo to pull the changes from the git repository. In order to keep Three20 up to date, I would have to keep the .git directory with the Three20 code inside of my working copy, making sure to svn-ignore that directory and never check it in to subversion (I did that once accidentally…not pretty). If I were to try to update my copy of Three20 with the canonical version on github, I would also have to remember to commit those changes into my own subversion repository. This always causes some problems on whichever of my other development machines is behind the bleeding edge. In order to update to the latest Three20 on another development machine, I have to remember to git pull before I svn up or things start to get scary; this means that I have to keep the .git directory in all of my working copies up to date. What a pain. Like many programmers, I’m almost inexcusably lazy, so I don’t bother. I need an überVCS.

Here’s what I think such an überVCS should do:

  • It should be distributed, allowing me to branch and commit locally. I have lately taken to making atomic local commits and using local branches to experiment and I really miss that workflow when I’m working in svn.
  • It should be ubiquitous and familiar. I don’t want to learn another version control system, okay? I already have three version control systems competing for the mental space that used to hold one. I really just want one version control system that deals with everything.
  • It should be able to easily pull subprojects from git, mercurial, and subversion repositories in a manner similar to subversion’s svn-externals.
  • It should allow me to work within those subprojects through plugins or natively so as to avoid the use of “foreign” version control command sets. In other words, I should be able to use the überVCS’s command set inside of, for example, a subversion subproject. Ideally, I could work in one version control system and have subprojects from other version control systems just work somehow.

Using a few plugins, Mercurial can act as The überVCS I’ve been looking for. It fulfills the first two requirements out of the box. It’s distributed and familiar. As far as I’m concerned, it acts a lot like a distributed version of Subversion, which is the old(ish) school version control system I’m most familiar with, whereas git feels like it is from someplace like Mars. Your mileage may vary.

The third requirement “to easily pull subprojects from git, mercurial, and subversion repositories” can be fulfilled by installing my fork of hgexternals from bitbucket. It’s just a simple merge of the fork from adri (available here) with the latest code from the original author of hgexternals available (here). Once my little fork of hgexternals is installed, you can place an .hgexternals file in the root of your main project and Mercurial will pull code from pretty much anywhere when you run the hg externals command. If you wanted to pull the gdata objective-c client (google code / Subversion), the hgsubversion project (bit bucket / Mercurial), and mogenerator (github / git) into a contrib subdirectory of your project, you would put the following three lines into your .hgexternals file.

./contrib/gdata http://gdata-objectivec-client.googlecode.com/svn/trunk/ svn
./contrib/hgsubversion https://jonmarimba@bitbucket.org/durin42/hgsubversion hg
./contrib/mogenerator http://github.com/rentzsch/mogenerator.git git

If you then run hg externals in the project. You’ll see that the subversion, mercurial, and git repositories are checked-out much like subversion handles svn-externals. Note: Be aware that the hgexternals python scrip will actually run svn or git binaries in a subprocess, so svn and git will have to be installed for this to work properly. If you wanted to hold the external project to a given revision, which is usually a good idea, you just append the external version control system’s usual revision flag. For example, if you wanted the Gdata Objective-C code pegged at revision 533, the first line in the .hgexternals file would look like this.

  
./contrib/gdata http://gdata-objectivec-client.googlecode.com/svn/trunk/ svn --revision 533

In order to get the hg externals command to act a little more like subversion, you’ll want to add the hgexternals.extstatushook to your .hgrc to get the status of the external projects when you type hg stat. To do this, you add the below to your .hgrc as suggested in the hgexternals installation instructions.

[hooks]
post-status = python:hgexternals.extstatushook

With a couple of extra plugins, mercurial can pull from subversion and git, in addition to the usual mercurial repositories. If you prefer to let hgexternals shell out to git or svn (thus requiring you to use git and svn tools within the subprojects themselves), you can stop here. If you would like to use mercurial commands within all subprojects, regardless of whether they are Mercurial repositories, read on. Either way, we can now pull subprojects from anywhere and we can also use hg stat as we normally would in the working copy of the project and each subproject status will also be shown.

Fulfilling the last requirement is actually pretty simple. Mercurial has third-party plugins that do a fine job of allowing you to use mercurial inside of working copies pulled from git or subversion repositories. For git, hg-git from here does the trick. While you can always use git commands within the mogenerator subproject from the example above, I personally find it easier to avoid switching between different version control systems on the fly. Hg-git lets you to interact with a git repository using mercurial. In order to do that, we install hg-git and then change the third line in our .hgexternals file to the following.

./contrib/mogenerator git://github.com/rentzsch/mogenerator.git hg

What we’re doing here is telling the mercurial hgexternals extension to pull from the git repository using mercurial itself. When hg-git is installed, this works with a git-based subproject just as it would with a Mercurial-based project. Make this edit to .hgexternals, remove the mogenerator directory that was checked-out with git, and rerun hg external command. Mercurial will now pull mogenerator from github and we can now work within the mogenerator subproject using regular hg commands.

A similar plugin called hgsubversion, available here, allows you to use Mercurial to pull from Subversion repositories. Just like we did with hg-git, we can install hgsubversion and then change our .hgexternals file to look like this.

./contrib/gdata svn+http://gdata-objectivec-client.googlecode.com/svn/trunk/ hg -r 533
./contrib/hgsubversion https://jonmarimba@bitbucket.org/durin42/hgsubversion hg
./contrib/mogenerator git://github.com/rentzsch/mogenerator.git hg

If we remove the gdata subdirectory tha wall pulled using Subversion from contrib and rerun hg external so that Mercurial will now pull it using its hgsubversion plugin, we now have three projects in our contrib directory that Mercurial itself pulled from git, svn, and hg repositories. With Mercurial plus the addition of a few plugins, we now have an über distributed version control system that makes it much simpler to work with and to update external code from many different sources.