Git Tricks: Debugging with git bisect

Matheus Santana
Goiabada
Published in
5 min readNov 25, 2019

--

Context is everything when it comes to reading, understanding and evolving a codebase — which may have been around for a long time and modified by dozens of developers. In such cases debugging our applications might entail more than just wiping out the current bad code. Defects may have been introduced quite long before and many questions may arise when it comes the time to finally fix them:

  • Who introduced the bug?
  • Why did that happen?
  • Is the issue the result of a single version update or is it the cumulative result of a bunch of minor modifications?

Answering these questions can help a lot in understanding the context in which a no-longer-correct modification was introduced and then applying the correct solutions. That's where git bisect comes in particularly handy.

The basics of git bisect

Bisect performs a binary search — one of Computer Science most iconic search algorithms — on our git tree in order to find a specific commit which introduced a sought-after change. Vocabulary seems fancy but it basically means:

  1. Pick a commit which splits the tree into two pieces — say a commit A
  2. Is this commit, A, affected by change you're looking for?
  3. If so, start over from 1 using the subtree composed by the commits preceding A
  4. If not, start over from 1 using the subtree composed by the commits following A
  5. Stop if you reach a single commit.

In the end, this usually (and hopefully) gives us a final single commit in which the change we are looking for was originally applied.

The binary search thing (splitting the tree into two pieces over and over again) allows us to get a response more quickly. Imagine we’re searching for a word in the dictionary. One practical way we can approach this task is opening it to some page about the middle of the bulk and then answer: is our word on this page? If not, we can start over opening the leftmost portion of what's left — if the word we’re seeking lexicographically precedes the words on the page we’re on — or otherwise the rightmost portion left of the lot until we answer a joyful “yes!” to our guiding question.

This question is essential for bisecting. We must have an objective way of providing git with an answer: is this the commit I’m looking for? The widely used way to do so is through automated tests. Let’s see how it works in practice…

(We'll use ruby and rspec for the sake of this example, but the technique is language-agnostic and can be used with any git repo)

git bisect in action

In the name of science, let us introduce a defect into the great Faraday library codebase. Then we’ll pretend we don't know about it and want to discover in which commit the bug was introduced.

Setup

I injected into the original Faraday’s git tree a flawed commit for us to seek and destroy — in this post we’re sticking to the seek part, though. So all we have to do is clone the repository:

$ git clone https://github.com/embs/faraday-bisect

and start bisecting with

$ git bisect start

Next thing we need to provide bisect with is the state of the current commit: is it buggy or not? In our example, answering to this question means running this spec and checking the results:

$ bundle exec rspec spec/faraday/request/authorization_spec.rb

A bunch of tests failures, so it is a bad commit. We inform the bisect process of this by typing:

$ git bisect bad

We also need to provide bisect with a good version — i.e., a good commit — to be used as an upper bound to our search. Well, there are a couple of different ways of finding that but for this example we’re going with this one:

which is the commit introducing the RSpec file we’re using as our guiding question — and we should know that all tests were green at that point. So we have to run

$ git bisect good f1b2657523f9d95ba5594edce7f707a2545171f8

Binary searching the git tree

Git then jumps us onto the next commit it “thinks” splits our tree in two pieces and outputs how far we are from finding our culprit:

Bisecting: 105 revisions left to test after this (roughly 7 steps)
[a3689ecfe6f1734863de778192124df914c44052] Fixes Rubocop Style/SingleLineMethods.

We shall run authorization_spec.rb again:

$ bundle exec rspec spec/faraday/request/authorization_spec.rb

and mark the commit as good with $ git bisect good if tests pass or bad with $ git bisect bad if tests fail.

At the end of the day, we might find someone to blame:

85ba6ccb9f000a47666d1302406f11fbc4b88aaf is the first bad commit
commit 85ba6ccb9f000a47666d1302406f11fbc4b88aaf
Author: Matheus Santana <embs@cin.ufpe.br>
Date: Wed Jun 12 15:06:33 2019 -0300

BUG

:040000 040000 c4910aca60cd5d0f91e300c9cfdcdc617a43246e cb3023f40a74a96dee5da25bacea66f0a2600ab2 M lib

Checkout this asciinema for the complete example:

Search automation with git bisect run

In case we have a script handling a proper output code (0 for success, other value otherwise) we can automate the iterative process and let git do it for us with

$ git bisect run our_script_or_command_here

This still requires setting everything up before bisecting. So, for our example, we’d have to $ git bisect start, $ git bisect bad and $ git bisect good f1b2657523f9d95ba5594edce7f707a2545171f8 just like we did before in our setup phase.

However, the command we provide git with can’t be just

$ bundle exec rspec spec/faraday/request/authorization_spec.rb

because Faraday uses simplecov for checking code coverage and output for running a single spec file usually returns something other than 0. But we can automate our search with a helper script like this and save it as bisect-faraday.sh:

After that, we run

$ git bisect run ./bisect-faraday.sh

There we have it! The complete automation example can be seen below:

If you want to delve deeper into the depths of this particular tool, the best place would probably be the official docs:

Git bisect helps us understand code in context through a project's git commit history. This is often handy for debugging and exploring our codebase — not only seen as what it is right now but also as what it took for the code to become so. If you want to learn more, follow Goiabada and enjoy a delicious mix of software development, design and business.

--

--