October 13, 2010
Mercurial has many different ways of handling branches. While there is plenty of information available on the different branching models, much of it is incomplete, and it can be difficult to decide what branching model to use when starting to use Mercurial.
Steve Losh has written an excellent guide to branching in Mercurial, but it mostly deals with how the different branching models work, how the branches are stored and what commands are used to handle them.
This guide intends to give more practical information on when to use what model and what the pros and cons are for each one.
This is the simplest branching model and the one used by the Mercurial
developers. You handle separate branches by simply having separate
repositories. You might have one repository called stable
that contains
released versions of your software, and one called dev
that contains code
under development that will eventually go out in the next release.
Mercurial supports this type of branching very well. You create a branch by simply cloning an existing repository, delete it by deleting the repository and move changes between branches by pulling or pushing from one repository to another.
The main problem with this model is that branches are very heavyweight,
especially if you have lots of source code and large repositories. Cloning
an entire repository can take a while. If you have very long-lived branches
like stable
and dev
in the example above, that is probably ok, but for
shorter lived feature branches it becomes cumbersome.
Another consequence of this model is that there is no permanent record of
where a certain commit originated. If you pull changes from one branch
repository to another you can see the separate lines of development, but
you cannot always easily tell which commits came from stable
and which
came from dev
.
Named branches try to fix the problems with using separate repositories as branches. They work by optionally stamping commits you make with a branch name.
Since named branches are just some extra meta data on commits, switching between branches is fast, the time it takes is proportional to the difference between the two branches. And you can always tell in which branch a commit was made, since the branch name is part of the commit.
However, this also means that you can never really delete branches (since
that would mean altering older commits). And there is no way to rename
branches either, so if you made a commit in a named branch called foo
,
everyone you ever share that commit with will have a named branch called
foo
in their history.
There are ways to manage this somewhat, you can mark branches as closed which means they don’t show up by default in certain places, but they’re still there. Closing branches is mostly an indication to Mercurial that you’re not interested in those particular branches any more.
Since branches are often given very common names like dev
, master
or
1.0
, this can cause problems if you intend to share your code with many
other people in an ad hoc fashion. They might want a different branch
called dev
.
But for long lived branches, where you only care about collaborating with a known group of people, named branches work well. They do not work as well for short lived feature branches (since the branch name will be a part of the commits forever) or if you do not want to impose your branch naming on people you share your code with.
Anonymous branches are just a Mercurial repository with multiple heads and
no named branches. You refer to the branches by revision id or some other
available name like tip
.
This works well for very short lived feature branches (anything that is merged within five minutes up to a day), where you know that the branch very soon will be integrated with the main line of development. For these cases, naming the branch is often unnecessary since you know what you’re working on anyway.
Anonymous branching has excellent support in Mercurial, and is very easy to work with, but it has obvious problems for longer lived branches. It’s hard to keep track of which branch is which and it’s cumbersome to communicate which one you’re talking about if multiple people are involved.
Anonymous branches work very well for very short lived feature branches that never leave your local repository, but poorly for anything else.
Bookmarks aren’t part of core Mercurial but the Bookmarks extension ships with Mercurial and is easy to enable. It is the branching model in Mercurial that is most similar to the one in Git.
Bookmarks are essentially pointers or references to commits, stored outside the commit graph itself. This means that you can add, delete and rename bookmarks without altering history (unlike named branches).
As of Mercurial 1.6, it is also possible to push and pull bookmarks between repositories. If you’re using earlier versions of Mercurial you will have to transfer bookmarks manually or with the help of something like rsync.
Bookmarks enables fast switching between branches in a single repository and don’t place many restrictions on other people that you want to collaborate with. Bookmarks can be deleted and renamed without altering history, since they aren’t a part of the commit graph, they just reference it. But there is still only one namespace for bookmarks, so if you want to share your bookmarks you probably want to ensure their names are unlikely to collide with other peoples bookmarks.
However, the support for bookmarks in older versions of Mercurial isn’t that hot. You need to transfer them manually between repositories and they interact badly with extensions that rewrite history like ‘‘hg strip’’ or ‘‘hg rollback’’. I have tried to use bookmarks in many older versions of Mercurial but always given up because I’d end up with crashes in the bookmarks extension because one of my bookmarks was referring to a commit that no longer existed.
I also missed the ability to have a bookmark refer to upstream development and have it update automatically when I pulled from there. Mercurial bookmarks only update when you make a commit, something which works well for your own line of development (because then you make the commits) but leads to problems if you want to refer to a line of development done by someone else with a bookmark. Because you’re not making the commits, only pulling them into the repository, the bookmark will not move forward as you pull in new commits, forcing you to update the bookmark manually.
If everyone you’re collaborating with is using Mercurial 1.6, and they use bookmarks to keep track of branches, this isn’t a problem because then they will update the bookmark when they make commits, and you will get both the bookmark and the commits when you pull.
If you’re unsure what branching model to pick, use separate repositories. This model has great support in Mercurial, you rarely need to add options or extra parameters to commands, and everything works in a very clear and simple manner. As an example, when repositories are branches, push and pull by default only update the current branch, and you have to give explicit options to move commits between branches. With any other sort of branching, Mercurial updates all branches on push or pull, which is rarely what I want. This forces me to constantly supply the branch name to some commands, which is extra work.
If you frequently create feature branches that live for some time (days at least) and then die, and are annoyed by the time it takes to clone the repository to create a branch, consider using bookmarks. It’s one of the more flexible branching models in Mercurial and has good support in the most recent versions.
If you only want a couple of very long-lived branches, and you only care about collaborating with a known group of people (like say the other programmers in your team), use named branches.