A Spotless Branch of Releases

05 Aug 2011

When setting up our new production environment for SaveUp, we decided that we wanted to override a lot of capistrano's core functionality and use git to handle a lot of things. In particular we wanted to to roll back our code by instructing capistrano to go back one commit in git (git reset --hard HEAD^). Many people use tags for this and it works great that way, but it's harder to automate a rollback action unless the tags have a very rigid set of naming rules (like version numbers). We instead decided that having a release branch with one commit per release would enable us to use git for rolling back without making any naming assumptions apart from what branch we're on.

Squash It

The first thing we thought to do was use git merge --squash to gather everything together in one commit. Assuming master and a prod branch, we'd do the following:

  1. git checkout prod
  2. git merge --squash master
  3. git commit -m "release X" # for some value of X

Doing this every time we did a release would give us a branch with only releases on it so our intended method of rolling back code would work just fine.

This system was fine until the merge conflicts started happening. After the first few releases, we ran into merge conflicts every time we tried the squash merge. What happened was the two branches diverged and our prod branch now had its own history separate from master. Since the branches diverged starting at the creation of the prod branch, every commit on that branch appeared different from master, so git treated it as two different branches of development in need of merging, as well it should. Our workaround for this was to instruct git to always choose master's changes using git merge -Xtheirs master. This didn't seem quite right though, so we searched for a better solution. In addition, in tools like GitX, the production branch didn't appear in line with master and was instead further down the tree.

Merge It

The solution was to merge the prod branch back into master after each release commit (thanks go to Braintree for this one). This allows the branches to remain in sync so that git doesn't go along thinking there are conflicts when there are none. All the commits on the production branch end up in master, so when doing the next squash merge into prod, there are no commits on prod for master to conflict with. The result of all this is a spotless branch with nothing but commits on it, while still being in sync with master. Here's what an example repo looks like after a few of these releases, as rendered in GitX:

merging diagram

You'll notice that there's no indication that the release commits are merged from master. This is because when you use --squash, git doesn't actually create a merge. If it did, you'd have all the commits in master appearing in prod's history, which is not what we want here.

Other Branches

Like most companies, we develop on multiple branches, and most often don't release off master. We use release branches for each release and hotfix branches after the release. We use the same method as above for releasing off these branches, and merge back into master after doing the merge back into the hotfix or release branch. The individual releases on prod make for great places to branch off for hotfixes, in the same way they would if they were tags.

I hope someone finds this useful and uses it in their setup. Git is very flexible, so there are other ways one could go about building their release system in git. This one may not be the best one but I think we've done well with it and it works for us. I'd like to learn more about what other people do to solve this problem, so if you've got some ideas, let me know!