Integrating topic branches in Git

A key feature of Git is how easy it is to create topic branches to separate and organize work. This power leads to a codebase with many more branches than you would typically see in other SCMs, like SVN. However, without an appropriate and consistent branch-and-merge strategy, your team will wind up with a confusing and unhelpful history.

How do we avoid this mess? And what do we actually want our history to look like?

A Tale of Two Timelines

No one sets out to create a messy history. Most of us want our main branches to be a straight line of commits.

This clear, linear history absent of any merge commits is highly readable. Git’s default behavior when merging a branch that has not diverged from the mergee is to perform a fast-forward, resulting in this type of history.

There is a major shortcoming; it doesn’t reflect the use of topic branches! You can’t see the workflow that was used and you can’t rollback the work from a single topic branch without a bit of investigation.

So lately we’ve been aiming to have our histories look like the following:

The main branch (master in this case) consists of nothing (besides the initial commit) but merge commits from topic branches. This is just as clear as the above linear timeline, but now:

  1. the history reflects the fact that we used topic branches for our work
  2. a naming convention for topic branches helps identify the work done
  3. it’s easy to revert the work of a branch; just revert the single merge commit!

So how do we achieve this model?

The Workflow

We follow a few key steps around branching and merging in order to create this style of history.

Branch Around Stories

As part of the agile process, we write stories to describe one feature, bug fix, or chore to be delivered. When we begin work on a story we create a topic branch named after it.

We usually use Pivotal Tracker to manage our stories, but no matter what system we can easily apply our naming convention:

[feature|bug|chore]-[id]-[abbreviated_story_title_separated_by_underscores]

Here are some examples:

Story Branch
Feature #12345: Threaded post comments feature-12345-threaded_post_comments
Bug #23456: Can create 2 groups with the same name bug-23456-prevent_duplicate_groups
Chore #34567: Setup CI environment chore-34567-setup_ci_environment

Assuming you are on the master branch, creating a new branch would look like this:

git checkout -b feature-12345-threaded_post_comments

It also makes sense to push this topic branch to the remote repository for backup or remote access by you and others.

git push origin feature-12345-threaded_post_comments

Rebase When Ready to Deliver

When a feature is complete, we rebase our work on the latest version of our main branch (master in this case):

git rebase master

This step is what gives our history model the appearance of each topic branch being created sequentially.

You may be concerned about rebasing when working in a team environment as your next push would have to be forced, rewriting the history. But remember, we only do this step when we’ve finished the story i.e. we no longer plan any further changes to it.

Merge Without Fast Forwarding

As discussed, Git’s default merge behavior (when the 2 branches have not diverged or after a rebase of one on another) is to perform a fast forward. Instead of accepting the default behavior we use merge’s --no-ff flag:

git checkout master
git merge --no-ff feature-12345-threaded_post_comments

This prevents the default behavior and generates a merge commit, achieving the goal of our model!

A Note on Squashing

Some developers prefer to squash all their work in a topic branch into a single commit before they merge. If you’re doing this, then we don’t see much advantage to not fast-fowarding because every topic branch in your history would be a single commit! Instead accept the default behavior and fast foward but at least make sure the story id is in the commit message for reference.

Be Good, Cleanup after Yourself!

Always remember to delete local topic branches after integrating them into another branch.

git branch -d feature-12345-threaded_post_comments

If you’ve pushed the topic branch to a remote, delete it there as well to avoid confusing other developers about its status.

git push origin :feature-12345-threaded_post_comments

Conclusion

Implementing this non-fast-forward workflow requires a bit of discipline from all of us after using the default behavior for some time. But we do enjoy the results, particularly a history that preserves the existence of topic branches.

We would love to hear your opinions and how you manage branching in your own work.

About Rudy Jahchan

Rudy’s fascination with computer programming began at age 10 when he mistakenly picked up the Micro-Adventure book Space Attack; he thought it was going to be about Star Wars. That happy accident led him to graduate from McGill University in Computer Science and start a 12 year career in software development playing with a wide range of technology; everything from web applications to cryptology to NoSQL.
This entry was posted in Everything Else and tagged . Bookmark the permalink.
  • Pingback: Tweets that mention Integrating topic branches in Git « Carbon Five Community -- Topsy.com

  • http://applesonthetree.com Chris Apolzon

    Great article. The one thing I’d add is running the remote prune command (the step I always forget).

    git remote prune origin

    This removes any stale references you have to remote branches which were deleted by another developer.

  • Pingback: This is how you should use git | Dark red. And yellow.

  • Pingback: This is how you should use git | troessner

  • Pingback: Kit of Parts for Developers « rcode5

  • Rob

    I’m looking to implement changes in to a ‘branch-by-user-story git workflow in order to clean up history. My worry with rebase is it:

    1) it creates a more complex workflow for developers and introduces more opportunities for accidents to happen
    2) the golden rule is you shouldn’t rebase other peoples history, a rule which you break when multiple people are working on one user story

    Just interested to hear how this workflow panned out for you?

    thanks

    • Jonah Williams

      I agree that “rebase” can lead to trouble for people unfamiliar with git. We all use it frequently enough that I think it is reasonable to assume our team can learn to get it right but I don’t see anything wrong with merging master into a feature branch before merging that feature branch back into master. I’ll certainly do that for long running feature branches which either have been shared with other developers or which would be particularly painful to rebase.

      As for rebasing other people’s history I totally agree with that as a goal. In our case I consider any feature branch which has many contributors to be a warning sign that we’re doing something wrong. That suggests to me that the story is far too large and needs to be rewritten as smaller tasks. Even if the user story cannot be decomposed into smaller units we should be able to find small independent developer tasks which can map to small short-lived feature branches.

      After a couple of years using it I’m still happy with the process Rudy describes above. These days our merges to master are often via github pull requests but the workflow is effectively the same.

  • http://dickey.xxx/ Jeff Dickey

    I’m a huge fan of GitHub Pull Requests for this workflow. In addition to tracking discussion on branches, they also won’t fast-forward, so you get this same functionality. You can also delete the orphaned branch right after merging.

    You get to edit the merge commit message as well (something they added very recently), which is nice for integrating with project management tools (as you mention).

    IMHO pull requests are a better process since it gives a nice clean way to look at the code and encourages code review. I have someone review my code every chance I get.

    But that’s not even the best feature, if you use semaphore, circleci or travis, you even get a little notification about whether or not that branch is passing the build. That alone has saved me hours of pulling down branches on projects, running the test suite, grabbing a drink, then coming back to see if there were failures.