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.
Come back next week for another instalment in the Git Tip of the Week series.