Skip to main content
Finding Bugs with git bisect (Advanced)
This material was adapted from the OxfordRSE "Git and GitHub" course, a derivative work of the UCL Research Software Development Group teaching materials and the Software Carpentry "Version Control with Git" lesson.

This material was adapted from the OxfordRSE "Git and GitHub" course, a derivative work of the UCL Research Software Development Group teaching materials and the Software Carpentry "Version Control with Git" lesson.

Creative Commons License

Finding Bugs with git bisect (Advanced)

Advanced: safe to skip

This module covers more advanced Git features. If you are still getting comfortable with the everyday add / commit / push / pull / branch workflow, feel free to skip it for now and come back once those feel familiar.

The problem

Sometimes you discover that something which used to work is now broken, but you have no idea which of the last few dozen commits introduced the problem. Reading every diff by hand would be slow and error-prone.
git bisect solves this by performing a binary search through your history. You tell it one commit that is known to be good and one that is known to be bad, and it repeatedly checks out a commit half-way between them and asks you whether that one is good or bad. Each answer halves the range, so even hundreds of commits are narrowed down in a handful of steps.

A worked example

Imagine our climate-analysis project has a test that used to pass:
python -m pytest tests/test_conversion.py
but now fails. We know it worked when we tagged v1.0, and it is broken at the current HEAD. Let's find the culprit.
(A tag is just a human-readable label for a commit - v1.0 here is assumed to already exist, perhaps marking a release. git tag itself is covered in the next module, A Git Toolbox.)

Bisecting manually

We start a bisect session and mark the two endpoints:
git bisect start git bisect bad # the current state (HEAD) is broken git bisect good v1.0 # this tag (or commit) was known to work
Bisecting: 6 revisions left to test after this (roughly 3 steps) [a1b2c3d...] Add rainfall parsing
Git checks out a commit half-way between v1.0 and HEAD. We test it:
python -m pytest tests/test_conversion.py
If it passes, mark the current commit as good:
git bisect good
Or, if it fails, mark the current commit as bad:
git bisect bad
We keep going - good when the tests pass, bad when they fail - and Git keeps halving the range. Eventually it announces the first bad commit:
2777975a2334c2396ccb9faf98ab149824ec465b is the first bad commit commit 2777975a2334c2396ccb9faf98ab149824ec465b Author: A. Developer <dev@example.org> Date: Thu Nov 14 09:23:55 2013 -0600 Change argument type
That is exactly the commit that introduced the bug. When we're done, we return to where we started:
git bisect reset

Bisecting automatically

If you have a command that exits 0 when the code is good and non-zero when it is bad - which is exactly what a test runner like pytest does - you can let Git run the whole search for you:
git bisect start git bisect bad HEAD git bisect good v1.0 git bisect run python -m pytest tests/test_conversion.py
Git will check out each candidate commit, run the command, interpret the exit code as good/bad automatically, and print the first bad commit without any further input from you. Remember to finish with:
git bisect reset
This is a strong argument for keeping a good test suite: with automated tests and bisect, finding when a regression was introduced becomes a much simpler process.

Key Points

  • git bisect runs a binary search through history to find the commit that introduced a bug.
  • Start with git bisect start, then mark one good and one bad commit.
  • Answer good/bad at each step, or automate the whole search with git bisect run <command>.
  • Finish with git bisect reset to return to where you started.
  • A reliable test suite makes finding a regression a much simpler process, especially combined with git bisect run.