Note: This is what works for us, you may decide this sounds insane. You are allowed that opinion ;)
We recently made the switch to using Mercurial (Hg) in our office and I dare say productivity has been through the roof compared to what now appears to be an archaic way of source control, known as subversion. I won’t go into why we decided on Mercurial in this post but rather how we are using it today and what rocks about it.
The Workflow
At our company we have many products under a single umbrella with many pieces of shared code and functionality between the products. I imagine many companies have very similar setups. We have around 8 developers divided into teams. Each team has their own set of goals and features for their next release for their product. As you can imagine, having this many developers working on a single codebase that shares a lot of core functionality can be a bit complex. For this example I’ll setup a blank repository and use TortoiseHg’s visual tools to help show the progress as we move along.
As you can see above, we have a brand new repository with a single commit. Now lest assume we have two teams and they both have features to develop simultaneously. In this workflow, we branch per feature. The branch also must be a named branch. This is great for clarity purposes, but we also use this for another reason I’ll explain later.
This is where things diverge from a “normal” mercurial workflow. Mercurial, by default, only allows one head per repository. This is how most projects work and for good reasons. There is only ever one release so there only needs to be one branch head.
TortoiseHg has an option called “Push New Branch” which we will use from both teams repositories. As the name implies, it pushes named branches to the central repo. This is important as we want to be able to see what the other teams are working on without pulling changesets directly from one of the team members. This is also important for Continuous Integration. We want each team to have a continuous build for their feature. This means we want their changes to be able to be pushed to the server’s copy of the repository. The CI is essentially setup to build all named branches.
Here is the updated workflow after a few changes from each team have been pushed to the central repo. *note that a central repo could be a codeplex or bitbucket account but in our case is just a locally hosted mercurial server
As you can clearly see, you have two distinct branches coming from the same origin. You’ll notice we named the branches Bug-1 and Bug-2. We named the branches this way for bug tracking purposes, you can name your branches however you like. Let’s say Team2 is done with their feature. Their next step is to check in their latest changes and close the branch. This is an important step as mercurial stops tracking closed branches. This becomes useful in CI scenarios as well, the CI will no longer monitor a closed branch. You can access the close branch command via the tortoise GUI using the branch button in the commit window.
Once the branch is closed and feature is complete, the team can merge the change back into default. If a team needs to go back to the branch, they can. Any commits will open the named branch automatically.
The other team is left finishing their bug and then merging in their changes in the same manner.
Now your cases are done and your stable branch is ready to be built by your favorite CI and published for the world to see!
Why bother?
There are many ways to deal with branches in mercurial and this is just the one we settled on. A lot of people use “branches as clones” or “branching with bookmarks”. This is a variation on “branching with named branches”. Steve Losh has some great source control posts on his blog and I highly recommend reading them all before making a decision.
There are some of the reasons we chose this workflow
- Organization and Visibility
- It’s easy to look at the list of branches to see what’s being worked on by all contributors.
- If you use bug ids in branch names you can easily search all branches in the future to help find the code your looking for.
- Implicit branches don’t have any identifying markers besides the commit message so it’s hard to find them later when you need them.
- If you use bug ids in branch names you can easily search all branches in the future to help find the code your looking for.
- A single clone of the repository means no folder-shuffling between branch switches.
- This is huge for productivity and sanity.
- It’s easy to look at the list of branches to see what’s being worked on by all contributors.
- Easily get other contributors involved
- Since the unfinished features aren’t just sitting locally on a developer’s box, you can easily update to any branch and contribute without pulling from a developer’s clone.
- Backups
- If you don’t allow force-push and don’t used named branches, backing up the main repo means only getting the main branch code in the backup.
- Developers can constantly push their unfinished branches to the source server and it will get backed up in case of computer failure.
- Continuous Integration
- Products like TeamCity allow you to specify a branch name for the build you are setting up, this means each feature branch can have a build. This would normally be impossible with implicit branches.
- Works well with experimental feature branches.
- Just close the branch if it doesn’t work out, no harm no foul.
- You don’t have to use named branches for everything. Small html fixes or something can easily be just committed directly to default if needed.
- I highly recommend always using named branches though, they are virtually free and are little trouble for developers.
- We all know how a small feature can turn into a month long project.
- Creating a named branch allows for short term or long-term development with no side affects.
- We all know how a small feature can turn into a month long project.
- I highly recommend always using named branches though, they are virtually free and are little trouble for developers.
Enforcing the workflow
The workflow above is great, but mercurial is very flexible and doesn’t enforce such restrictions (rightfully so). It allows developers to push implicit branches and multiple heads to a repo. It doesn’t really “allow” it by default, but you can easily use the force push option and end up with a confusing situation for developers. It even reminds the developer to do so in the error message.
In our case, we want to enforce there there is only a single head per named branch. For simplicity sake, we will consider default a named branch as well, it’s treated as such in most situations. This makes things very clear for developers. You can’t push your changes to the main repo without merging if your teammate has modified the code and beat you to the push.
Here is the full server plugin we use to enforce this rule. As you can see, it’s quite simple. This plugin runs before a push is finished. So any developer that tries to push a changeset that contains more than one head on any branch would be rejected until the plugin passes the changesets. Mercurial plugins are a whole topic themselves so I won’t go into how to go about setting it up expect for the bit of notes in the comments from the plugin. If there is desire for such a tutorial in the future, I’ll write one up on the different ways of setting up mercurial and it’s plugins (client and server).
#!/usr/bin/env python
# Enforces that there is only a single head per named branch allowed
# this hooks section should go in the hgrc of the repository you want the rules applied to
# [hooks]
# pretxnchangegroup.single_head = python://someserver/location/singlehead.py:single_head
from mercurial.i18n import gettext as _
def single_head(ui, repo, hooktype, node, **kwargs):
for b in repo.branchtags():
if len(repo.branchheads(b)) > 1:
ui.warn(_("-----------------------------------------\n"))
ui.warn(_("-----------------------------------------\n"))
ui.warn(_("Two heads detected on branch '%s'\n" % b))
ui.warn(_("Only one head per branch is allowed!\n"))
ui.warn(_("Pull changes, merge and try again\n"))
ui.warn(_("-----------------------------------------\n"))
ui.warn(_("-----------------------------------------\n"))
return 1
return 0
Hi Jesse,
ReplyDeleteThank you for sharing the workflow with us.
I am starting to learn about Hg and this is very useful information to me. I hope you could clarify a few points for me.
1. When you initiate the Central Repo do you do it on a fileserver in a LAN?
2. For the teams to start developing new features do they Clone the from The central Repo and then Push New Branch to the Central Repo?
3. After some time as more branches are created you will have many branches. Some can be discarded and some merged with the main branch. Can products that use code that is from a name branch and not the main branch? So in effect, I can have as many name branches as I have products that use shared code. How would this method manage bug fixes that could affect a multiple branches.
4. Is there a rule that says that all named branches much be merged to to the main branch at some point?
Thank you very much in advance.
/KC