Once upon a time, I used Mercurial for development. When I moved to VMware, people there seemed to favor Git and so I spent the past few years learning Git and helping to evangelize its use within VMware. I have written about why I chose Mercurial, as well as my initial reactions upon starting to use Git. Hadapt happens to be using Mercurial today and so I have been re-visiting Git and Mercurial.
In using Mercurial, I find myself oddly hobbled in my ability to do things. At first, I thought that this might simply be because some things are simply done differently in Mercurial but at this point, I actually think that Git’s design and attention to detail result in it actually being more usable than Mercurial.
There are three “philosophical” distinctions that are in Git’s favor:
Git has one branching model. Mercurial has several that have evolved over time; Steve Losh has a comprehensive essay describing ways to branch in Mercurial. The effect of this is that different Mercurial users branch in different ways and the different styles don’t really mix well in one repo. Git users, once they learn how branching works, are unlikely to be confused by branches.
Git has names (refs) that don’t change unexpectedly. Every Git commit you care about has a name that you can choose. Some Mercurial commits that you might care about do not have a name. For example, the
defaultbranch in Mercurial can have multiple heads, so it interprets
-r defaultas the tip-most commit. Unfortunately, that commit will vary depending on who has committed what to which head (and when you see it).
Further, Git exposes relative naming by allowing you to refer to the branches in remote repositories by name, without affecting your own names.
Putting this together, consider what happens after you pull in Mercurial. Your last commit used to be called
defaultbut after the pull,
defaultis something from the upstream. Your commit is a separate head that now has no name. In Git, your
masterdoesn’t move after a fetch and the remote’s branch is called
Git even tracks the changes what commit each name refers to in a reflog. You can easily refer to things that the name used to refer to. In Mercurial, branch names don’t have reliable meanings, and it doesn’t track them.
Git commands operate in a local context by default. Mercurial commands often operate on a repository context. For example,
git grepoperates on the current sub-directory of your work tree,
hg grepoperates on your history. The Git analog of
hg grepis using the log pick-axe; the Mercurial analog of
git grepis to use ack, or if you must, something like
hg grep -r reverse(::.) pattern .(Seriously?)
Another example is the
logcommand. Git’s log command shows you the history of the commit you are on right now. Mercurial’s log command shows you something about the whole repository unless you restrict with some combination of
-f. Combined with Mercurial’s way of resolving branch names to commits, it becomes very difficult to use
hg logto compare two heads or explore what has changed in another head of the same branch.
More often than not, I care about things in their current tree more than how things are in some random other branch that I am not working on and Mercurial makes it hard to do that.
There are other usability issues that I’ve found that are more detail-oriented than philosophical. I’ll note a few here.
hg log doesn’t display the full text of the commit message unless you
hg log --debug. This is an unfortunate disincentive to
writing good commit messages.
hg log -p doesn’t pay as much attention to merge commits as Git does; the
hg log reads:
log -p/–patch may generate unexpected diff output for merge changesets, as it will only compare the merge changeset against its first parent. Also, only files different from BOTH parents will appear in files:.
git log has a variety of options to control how the merge
diff is displayed, including showing diffs to both parents, removing
“uninteresting” changes that did not conflict, or showing the full merge
against either just the first or all parents of the merge commit.
Both Mercurial and Git have lots of configurable options; Git has a thin veneer
over editing a config file in the form of the
git config sub-command.
Mercurial involves editing a file even if just setting up your initial username
or enabling extensions. I often wound up editing Git config files directly,
but having the commands were nice for sharing instructions with others.
Git support for working with patches natively is better. Mercurial supports
e-mailing and applying patches, but oddly the extension for sending out patches
is built in (patchbomb) but the extension for importing from an mbox (mbox) is
not. There’s no direct analog of
git apply; instead you have to use a patch
queue. Patch queues are okay, but branches and well-integrated
rebase/e-mail/apply support are much nicer than patch queues: you don’t need to
manual find some
.hg/patches/series file and edit it to re-order stuff.
I could write more and indeed many people have written about Git and Mercurial—you can explore my bookmarks about git for some of the better ones. Let me close here with three interesting features in Mercurial 2.0:
- the new largefiles extension allows users to not transfer large files down until they are needed;
- subrepos can be Git or Subversion in addition to Mercurial;
- revsets allow you to search your history in very flexible ways.
Overall, I feel that Git is significantly more usable for day-to-day development than Mercurial. I’d be curious to hear if you think the opposite is true.