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.
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:
git checkout prod
git merge --squash master
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.
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
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:
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.
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!