Alex headshot

AlBlue’s Blog

Macs, Modularity and More

Git Tip of the Week: Git Bisect

Gtotw 2011 Tip Git

This week's Git Tip of the Week is about bisecting to find where a failure was introduced. You can subscribe to the feed if you want to receive new instalments automatically.


When a problem is discovered in an existing system, it's not always clear what change caused the regression. Sometimes it's obvious, but in some cases the only way to find out is to go back and test each stage of the history to find out when the code went from 'good' to 'bad'.

Since this can take a significant amount of time, git bisect can be used to help automate the discovery of the problem. It relies on the ability to determine whether a problem is 'good' or 'bad', and uses a binary sort to discover through the history when the error was introduced. To do so, you use the git bisect start, followed by the git bisect good and git bisect bad commands. Let's use it to find out when "Line two" was added:


# Sample file, adding one line per commit
(master) $ git blame test
^b06000a (Alex Blewitt 2011-07-15 08:44:49 +0100 1) Line one
3c30996f (Alex Blewitt 2011-07-15 08:45:04 +0100 2) Line two
4b2a81b8 (Alex Blewitt 2011-07-15 08:45:24 +0100 3) Line three
67664b1e (Alex Blewitt 2011-07-15 08:45:32 +0100 4) Line four
50cc6273 (Alex Blewitt 2011-07-15 08:45:40 +0100 5) Line five
(master) $ git bisect start
(master|BISECTING) $ git bisect good b06000a
(master|BISECTING) $ grep two test
Line two
(master|BISECTING) $ git bisect bad
Bisecting: 1 revision left to test after this (roughly 1 step)
[4b2a81b86fcdb0e59981dc5d0ecdef5e6567c71b] Third
((4b2a81b...)|BISECTING) $ grep two test
Line two
((4b2a81b...)|BISECTING) $ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[3c30996f285905b16f1d1d8fb293372615272ce4] Second
((3c30996...)|BISECTING) $ git diff HEAD^
diff --git a/test b/test
index 67c8eb8..b8b933b 100644
--- a/test
+++ b/test
@@ -1,2 +1,2 @@
 Line one
-
+Line two

So we specified a good revision (b06000a) and a bad revision (50cc6273) and then picked a midpoint between them. This then recurses until only that commit is left, and in this case, it's the one that introduced the 'bug' (Line two). We can even see what we've done after the fact:


((3c30996...)|BISECTING) $ git bisect log
git bisect start
# good: [b06000a98bd9d88f71356746680404de14b68a89] First
git bisect good b06000a98bd9d88f71356746680404de14b68a89
# bad: [50cc627394f5647e16921c30e4a984b4fd76577c] Fifth
git bisect bad 50cc627394f5647e16921c30e4a984b4fd76577c
# bad: [4b2a81b86fcdb0e59981dc5d0ecdef5e6567c71b] Third
git bisect bad 4b2a81b86fcdb0e59981dc5d0ecdef5e6567c71b

Clearly in this case we could have just identified the line with git blame, but it highlights the principle of solving a problem. In the case of doing these steps, we were manually running grep two to find out if there was a problem; this would typically be replaced with a specific test command, such as make or equivalent. But wait! We can do it faster! Firstly, we can specify the starting good/bad endpoints:


((3c30996...)|BISECTING) $ git bisect reset
Previous HEAD position was 3c30996... Second
Switched to branch 'master'
(master) $ git bisect start HEAD HEAD~5
Bisecting: 1 revision left to test after this (roughly 1 step)
[4b2a81b86fcdb0e59981dc5d0ecdef5e6567c71b] Third

Secondly, we can run a script which can perform some kind of automated tests. With git bisect run we can execute a script per commit which detects whether the condition is good or bad. This could be some kind of make script, or it could be a custom script to test for a particular condition:


((4b2a81b...)|BISECTING) $ echo 'grep two test && exit 1'  > ~/test.sh
((4b2a81b...)|BISECTING) $ echo 'exit 0' >> ~/test.sh
((4b2a81b...)|BISECTING) $ chmod a+x ~/test.sh
((4b2a81b...)|BISECTING) $ git bisect run ~/test.sh
running /Users/alex/test.sh
Line two
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[3c30996f285905b16f1d1d8fb293372615272ce4] Second
running /Users/alex/test.sh
3c30996f285905b16f1d1d8fb293372615272ce4 is the first bad commit
…
bisect run success

If the script to test needs to be the same for all runs, it's best to put it outside the repository (which will be reset per bisect operation). It's also possible to have a script which merges in simple fixes or builds with additional parameters (e.g. -DDEBUG).


Come back next week for another instalment in the Git Tip of the Week series.