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

commit visualization

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.

mkdir shopping_bag
cd shopping_bag
git init

git remote add origin git@our-git-server:repo.git
git remote add github git@github.com:username/repo.git

touch README
git add .
git commit -m "initial commit"
git push origin master

git checkout -b release
git push origin release

git checkout -b github_master
git push origin github_master

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.

vim VERSION
vim CHANGELOG
git add .
git commit -m "updated changelog and bumped version"

We’re now ready to move to the github_master branch.

git checkout github_master

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.

git merge --squash release
git commit -m "1.0.0"
git tag 1.0.0 -m "1.0.0"

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.

git push origin github_master

git checkout release
git merge github_master
git push origin release

git checkout master
git merge release
git push origin master

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).

commit visualization

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.

vim VERSION
vim CHANGELOG
git add .
git commit -m "updated changelog and bumped version"

We then merge these changes into github_master squashed, tag the release and push these changes to github.

git checkout github_master
git merge --squash release

git commit -m "1.0.1"
git tag 1.0.1 -m "1.0.1"

git push github HEAD:master

Finally we merge these changes back into release and master, pushing each branch to origin. These steps are the same as the previous release but are shown below for reference.

git push origin github_master

git checkout release
git merge github_master
git push origin release

git checkout master
git merge release
git push origin master

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.

***
Drew Olson Drew is a Principal Engineer at Braintree. More posts by this author

You Might Also Like