Posts Tagged ‘feature branch’

Articles

git bugfix branches: choose the root wisely

In source code management on January 17, 2012 by gcbenison Tagged: , ,

(The impatient may jump directly to the summary or a case study.)

You’re hacking along on your project, and you discover a bug (not in whatever feature you’re working on; somewhere else in the code). You’re using git for revision control, so branching and merging are very cheap, and it makes sense to create a bugfix branch. That way you need not worry about creating a pristine patch immediately – you can fearlessly pepper your code with assertions, extra tests, and confessions, and generally tear your code apart in whatever way helps hunt down the bug, without fear of disrupting development on the main branch. Then when you’re happy with the bugfix branch, you can merge it back into the main branch (usually called ‘master’ when using git), even if more development has happened (possibly by other developers) in the meantime.

repository dependency graph, before bugfix

Figure 1. Repository, before bugfix

But what if the project has more than one branch under active development? Consider the typical scenario in Figure 1, where in addition to the master branch, there is a stable release branch  and a feature devlopment branch. Development on the feature branch is proceeding independently of the master branch. The release branch has diverged a bit, too. (Why? Perhaps bugfixes no longer relevant to the main branch; compatibility patches; etc. It happens.)  Should the bugfix branch be merged into these other active branches, as well? Certainly, if the bug affects those branches.  But in the process of merging, we should aim to pull in only our bugfix changes – the bugfix should not force us to merge our branches in a more general way at this time.  Therefore a bugfix branch emanating from the current master branch (as in Figure 2) presents a problem, because when merged with any other branch, it will pull in all of the current master branch with it.  (Basing the bugfix on the tip of any other current branch will suffer the same concerns.)

Figure 2 – bugfix branch from current masterbugfix branch from head of master

One solution is to use git cherry-pick to apply the bugfix selectively to the other branches.   This is less straightforward than simple git merge and has the disadvantage that git cherry-pick must rename each commit (since the resulting commits have new parents, they must have different names).  This makes it less clear that the same bugfix was applied to both branches.  Constraining bugfixes to single commits - possibly using git rebase to squash a branch down – can make git cherry-pick more straightforward; however, there is still the renaming problem, and I dislike the requirement of one commit per bug; it is reasonable to split up a complex bugfix into steps and to want to retain that information.

The solution I like best involves first finding the commit that introduced the bug, and then branching from there.  A command that is invaluable for this is git blame.  It is so useful, I would recommend learning it right after commit, checkout, and merge.  The resulting repository now looks like Figure 3, where we have found the source of the bug deep inside the development history.

This approach has several advantages.   In a git log of the bugfix branch, the patch now appears adjacent to the problem it attempts to solve, rather than randomly interspersed with unrelated commits.  More importantly, the bugfix branch can now be merged with any appropriate branch using a simple git merge without dragging in any unrelated commits.  The bugfix branch should be merged with any branch that contains the source of the bug (i.e. where we rooted the bugfix branch.)  An extremely useful command for this – which I learned here – is git branch --contains.  In our example, the bugfix branch should be merged with master and with the feature branch, but not with the stable release branch, which diverged from master before the bug was introduced.

After the merge (Figure 4), the head commits of the ‘master’ and ‘feature’ branches now have two parents – the previous heads of those branches, and the head of the bugfix branch.  Some may recoil at this type of complex ancestry in the master branch, preferring a linear commit history.  But why?  Handling this sort of complexity is what git is great at.

bugfix branch, merged

Figure 4 - bugfix branch, merged

bugfix branch rooted at the source of the bug

Figure 3 - bugfix branch rooted at source of bug


A brief summary of the approach:

  1. Discover bug.
  2. Identify commit that introduced bug (git blame is invaluable here.) Can use git tag to keep track of the commit, if desired.
  3. Create a bugfix branch rooted at the commit identified in step (2): git checkout -b my-bugfix-branch <source-of-bug>
  4. hack hack hack…bug fixed
  5. Identify branches affected by the bug: git branch --contains <source-of-bug>
  6. For each branch identified in (5), merge in the bugfix branch

This approach scales to more complicated repositories having many branches with which you may not be familiar. Steps 5 and 6 can be put into a script that automatically finds all the appropriate branches:

#!/bin/sh
#
# GCB 1jan12
# Automatically merge $bugfix_branch into all branches containing $bug_root.

for b in `git branch --contains $bug_root | sed s/*//`;
do
  echo $b;
  git checkout $b;
  git merge $bugfix_branch;
done


A case study

This real-world example comes from the development of burrow-owl, a package I maintain that draws and manipulates magnetic resonance spectra.

1) Discovery of the bug:

When the documentation-generating program ‘doxygen’ was missing on the system,
running make in the burrow-owl doc/ directory would fail (exit with non-zero status),
which is a bug because such problems should be diagnosed during the configuration stage.

2) Uncovering the source of the bug:

Poking around, we find this line in Makefile.am:


doxygen-docs:
$(DOXYGEN) Doxyfile

and this in configure.ac:


AC_PATH_PROG(DOXYGEN, doxygen, no)

When doxygen is installed, this works as expected.  When it is absent, the ‘DOXYGEN’ variable
is set to ‘no’, which ‘make’ then tries to execute – since there is no program called ‘no’,
make fails.

3) Creating a bugfix branch

We can find the commit responsible for the lines in question using git-blame:

$ git blame configure.ac
8771b190 (Greg Benison 2008-01-09 11:52:51 -0800  16) AC_CHECK_PROGS(INDENT, indent, [# ** indent not
available **])
8771b190 (Greg Benison 2008-01-09 11:52:51 -0800  17)
2a258920 (Greg Benison 2008-11-23 23:49:24 -0800  18) AC_PATH_PROG(DOXYGEN, doxygen, no)
2a258920 (Greg Benison 2008-11-23 23:49:24 -0800  19)
1288c02a (greg         2009-12-21 07:35:38 -0800  20) PKG_CHECK_MODULES(GLIB,      [glib-2.0 gobject-2.0])

It’s apparent that the line we want to change was introduced by commit 2a258,
quite far back in the history of ‘master’.  Now we look at that commit in more detail:

$ git log 2a258 -n1

commit 2a2589204454fb5f340a47ba40859f5c3357560b
Author: Greg Benison <gbenison@gmail.com>
Date:   Sun Nov 23 23:49:24 2008 -0800

added doxygen config file and some inline comments

Earlier commits do not use ‘doxygen’ and do not have the bug, so we create a tag at this commit
which will serve as the root of our bugfix branch:

$ git tag 2a258 bug-doxygen-missing
$ git checkout -b bug-doxygen-missing tags/bug-doxygen-missing

4) Creating the patch

Now on our bugfix branch, we make our changes, adding an option to ‘configure’ to either disable
doxygen or specify an alternate program, and if disabled to have ‘make’ skip building the documentation
rather than fail.

$ git log heads/bug-doxygen-missing --oneline

b276d6e Check during configuration for missing doxygen
f7cf4d8 Updated README.branch to contain description of the problem.
2a25892 added doxygen config file and some inline comments

Observations: Our bugfix branch consists of two commits, then the next parent is
the commit that introduced the bug rather than some other random piece of commit
history.

5) Merging the bugfix branch

The repo contains a ‘master’ branch, where development is occuring, and two
release branches:


$ git branch
1.4
1.5
* master

So to which branches should we apply our bugfix branch?  Only to those that
actually contain the bug, i.e. that include the commit that began our bugfix
branch.  We can find these with  git branch --contains:

$ git branch --contains tags/bug-doxygen-missing
1.5
* bug-doxygen-missing
master

Apparently, the bug was introduced before release 1.5, but after release 1.4.  So it is
appropriate to merge the bugfix branch into master and 1.5 only.

On master, we can verify that our bugfix branch has not yet been merged, and then merge it:

$ git branch
1.4
1.5
bug-doxygen-missing
* master

$ git branch --no-merged
1.4
1.5
bug-doxygen-missing

$ git merge heads/bug-doxygen-missing

Similarly for branch ‘1.5’; then it would be safe but not necessary
to delete the bug-doxygen-missing branch because it has been merged
everywhere it needs to be.

Follow

Get every new post delivered to your Inbox.