Our Git Workflow: Private Development, Public Releases

We dig open source here at Braintree. We use github to track issues, accept patches and push new releases of our client libraries. Our release process is a bit different — we prefer our individual commits between releases to be “private”. When we’re ready to release we collapse all commits down to a single “release” commit (eg. our python client library). Although the process is somewhat complex, git is flexible enough to let us tailor the releases to fit our needs.

Why Private Commits?

You may be asking yourself why we’ve chosen to use private commits on master. As a dev team we like to commit early and often. This means we’re committing several times while working on a single feature and we’d like to avoid exposing commits on github that contain half-completed features. Other open source projects usually deal with this by asking for atomic commits or working in topic branches. We find atomic commits too restrictive for our working style and feel that topic branches generate too much clutter.

Our commits also contain information that isn’t for public consumption including the story card number or the name of the developer pair. Private commits on master allow us to remove this unnecessary noise from our public releases.

Updated — The final reason for keeping our commits private is that we’re releasing our client libraries along with changes to our gateway. We push changes to our client libraries on master before the corresponding gateway changes have been released publicly. Having these commits exposed prematurely would be both confusing and non-functional for our users as they would not yet have access to our gateway changes.

General Technique

We maintain 3 branches for each of our client libraries:

master — Active development occurs on this branch.

release — Development for bug fixes happens here. We also bump versions and update the changelog on this branch.

github_master — We squash commits from the release branch into single “release” commits on this branch as well as tagging releases. This branch tracks github/master.

Creating the Repo

Take a look at the visualization of the commit history on the sample project above. First we create a repo with three branches and two remotes. The remotes will be our internal git server (origin) and github.

Releasing

Suppose we create three commits that add milk, eggs and fabric softener to our shopping_bag. After this work, we’re ready to release 1.0.0. First, we checkout the release branch and merge our changes in from master.

git checkout release
git merge master

Next, we bump the version to 1.0.0 and update the changelog. We preform this work on the release branch.

We want to merge the changes from release into the github_master branch but we don’t want to see each individual commit. Git helps us out here with the git merge --squash command. This will merge all the changes from a specific ref, squash them into a single set of changes and leave the changes staged. We commit the staged changes with the message “1.0.0” and tag the commit.

With the commits squashed and tagged, it’s time to push to github. We want to push the current branch’s HEAD to the master branch on the github remote.

git push github HEAD:master

Last but not least, we need to push these changes to the branches on origin and merge the squashed commit back to release and master.

You may suspect that git would be confused merging a squashed commit back into branches containing the non-collapsed commits, but it all works just as expected. Git is smart enough to realize no changes need to be made when merging in the squashed commit, but we should still merge to keep our branches in sync.

Our release is finished. If you look at the image above you’ll notice the nice cascade of commits from github_master to master as the squashed commit is merged.

Bug Fix Releases

Anxious to get back to work, we continue our development on master adding water balloons to our shopping_bag project. Suddenly, we find a bug — we don’t have a cheese pizza in the released code! We want to add a cheese pizza to a new release but ignore the water balloons commit (noted by the arrow below).

First, we checkout the release branch.

git checkout release

Next, we fix the bug on release. When the fix is complete it’s time to release the bug fix. First, we update the version and changelog.

With that, our bugfix release is complete and we can continue development on master.

Wrap Up

This style of development works nicely for us at Braintree and we were happy to find a git workflow to make it possible. It allows us to commit early and often between releases while keeping our public repositories on github clean and noise-free. We think it’s a testament to git’s power and flexibility that it is able to adapt itself to our working style so nicely.