git-bisect Is Your New Best Friend

written by trotter on September 27th, 2008 @ 07:33 PM

To anyone not using git, jump to the bottom of the post then come back up.

Update: I’ve got an even faster method at the bottom now. Skip down there if you already know the basics of git-bisect.

Ok, now let’s move on to the cool shit, git-bisect. Git-bisect helps you figure out exactly what code change broke a feature in your app, even when that code change was made months ago. It works by assisting you in a binary tree search through your commits, pausing at each one so that you can run a test and mark that commit as good or bad. This can remarkably decrease the amount of time you spend trying to figure out what is causing a new bug, because you quickly can find the exact code change that introduced it.

To use git-bisect, you first need a good test to run. Though you could do a manual test like loading a page in your browser and verifying that things look correct, you will be much happier if you write an automated test that you can run for each commit. Since I usually live in Ruby land, I’m fairly partial to TestUnit and rSpec for my automated tests. If you’re in iPhone land, I strongly recommend using google-toolbox-for-mac.

With automated test in hand, you can kick off git-bisect with git bisect start. You then mark your current commit as bad using git bisect bad. You then checkout a known good commit using git checkout commit_hash. Run your test and mark it as good when it passes using git bisect good. At this point, git-bisect takes over and starts moving you through commit after commit. At each stop, you run your test and then mark the commit using either git bisect bad or git bisect good. At the end, git-bisect will tell you which commit first caused your error. You can then use git diff commit_hash to see what was changed in that commit. When you’re done, you run git bisect reset to set everything back to normal.

A typical git-bisect session looks somewhat like this:

(master) $ git bisect start

(master|BISECTING) $ git bisect bad

(master|BISECTING) $ git checkout eb5eecbb8fc4e2a964e8d2043d8b95f4eb7b563a
HEAD is now at eb5eecb... Add MainViewController

... run test which passes ...

(eb5eecb...|BISECTING) $ git bisect good
Bisecting: 3 revisions left to test after this
[d82c1595b6363484fe0d7f60f9ffa096d777bf17] First CompsView test

... run test which fails ...

(d82c159...|BISECTING) $ git bisect bad
Bisecting: 1 revisions left to test after this
[93af33167019fa039f5372dff602a76cbcbc99bb] Add first integration test

... run test which passes ...

(93af331...|BISECTING) $ git bisect good
Bisecting: 0 revisions left to test after this
[4f12091a287c363737ceb650df46196e5008d3f2] Add Comps target

... run test which fails ...

(4f12091...|BISECTING) $ git bisect bad
4f12091a287c363737ceb650df46196e5008d3f2 is first bad commit
commit 4f12091a287c363737ceb650df46196e5008d3f2
Author: Trotter Cashion <cashion@example.com>
Date:   Tue Sep 23 20:18:09 2008 -0400

    Add Comps target

:000000 100644 0000000000000000000000000000000000000000 789bf7877c6059a7f3ac8cb2b53fdb2c903e58ff A    Comps-Info.plist
:040000 040000 d260571a48328d4a575a7395cd6ece3d651a93ac a622a23fdb80c915eaba49d1d53f7bf0dbf44a70 M    ShootAndSpeak.xcodeproj

... Figure out what's wrong ...

(4f12091...|BISECTING) $ git bisect reset
Switched to branch "master" 

I hope you learn to use and love git-bisect. It’s really helped me when trying to find the cause of nasty bugs that seemingly came out of nowhere.

Update

The above is too much work. While searching the net, I found something even easier and faster. You can start git-bisect with the commit hashes like so git bisect start bad_commit good_commit. Even better, you can then tell git-bisect to run the tests itself… this is where things get awesome: git bisect run some_test. It’ll iterate through your commits until it finds the bad one. Checkout the sample session below.

/tmp/fake(master) $ git bisect start 68d5ab7a61a871fd097d8820e248cfd168395e4e 20cbc038973d6c78805bc8bfc3d187c2b537f183
Bisecting: 1 revisions left to test after this
[3da37ed0ee87c9129a61142ecefef17ab0de7f0f] Test works

/tmp/fake(3da37ed...|BISECTING) $ git bisect run testrb test/unit/some_test.rb -n test_truth
running testrb test/unit/some_test.rb -n test_truth
Loaded suite some_test.rb
Started
.
Finished in 0.000338 seconds.

1 tests, 1 assertions, 0 failures, 0 errors
Bisecting: 0 revisions left to test after this
[765d7e5c4eba730078907fc00121b8b35ada64b0] Test fails
running testrb test/unit/some_test.rb -n test_truth
Loaded suite some_test.rb
Started
F
Finished in 0.009607 seconds.

  1) Failure:
test_truth(ThisTest) [./test/unit/some_test.rb:5]:
<false> is not true.

1 tests, 1 assertions, 1 failures, 0 errors
765d7e5c4eba730078907fc00121b8b35ada64b0 is first bad commit
commit 765d7e5c4eba730078907fc00121b8b35ada64b0
Author: Trotter Cashion <cashion@example.com>
Date:   Sun Sep 28 13:13:09 2008 -0400

    Test fails

:040000 040000 167dd04f2b4101ea256a7a6525859bc03e5433d3 0b062d4b5b03e7ac51ac4050fc7397c8983a2f13 M    test
bisect run success

/tmp/fake(765d7e5...|BISECTING) $ 

As you can see above, I’m using ruby for this set of tests. My preferred method is to have git-bisect run testrb (or spec) and specify a single test for it to execute. This ensures that everything runs quite quickly.

Land Here!

If you jumped here from the top, I regret to inform you that this post will make you very sad. Turn back now before you’re stuck improving your life by installing git. Once you’ve installed git, go check out Peepcode. They’ve got a really good screencast and pdf that explain git excellently.

Comments

  • Dustin on 29 Sep 19:45

    Looks like we were inspired to post about the same thing at roughly the same time. :) http://www.rockstarprogrammer.org/post/2008/sep/25/automating-git-bisection-rails-apps/ My inspiration was really the script I ended up writing towards the bottom of that where I needed to actually test HTTP failures against the development server.

Comments are closed

Options:

Size

Colors