Git has a feature to help hunt down bugs for a very specific sort of problem: when you have a regression, but you don’t know when it was introduced.
This feature, known as `bisect`, can assist is tracking down such an issue by running a binary search through a repository’s history to find the exact commit that such a regression was introduced.
Basic setup
# To initialize git bisect start # Declaring the current commit as broken; a tag or commit hash can also be included if an earlier broken commit is already known git bisect bad # Declaring a specific tag as a working commit; specific commit hashes may again be used git bisect good v1.2.0-release
At this point, Git will check out the commit at the midway point between the latest known working and earlier known not-working commits. From here, it is a simple matter to test the checked out commit and:
# If the checkout has the bug: git bisect bad # Or if it does not: git bisect good # Or, if there is something preventing testing with this specific commit: git bisect skip
Every time a commit is determined to be good or bad, Git will automatically checkout the next “middle” commit to continue the process. In doing this, one can track down the commit that introduced the bug in O(log2(n)) steps, in addition to any skips done in the process. That means even with a thousand commits, it won’t take more than 10 steps to reach an answer. Ten thousand commits? Not more than 14 steps.
Workflow Considerations
The bisect feature is at its best when commits are atomic; that is to say small and focused changes that affect one logical area at a time. One bug fix, or one feature, or one refactorization are all considered atomic.
Bisect results are most useful when they land on a small commit, as a small diff will make it clearer as to why an undesirable change has happened. Squash commits should also be avoided for this reason, as they remove history and make multiple smaller commits into singular large ones.
It’s also a good idea to not commit work-in-progress commits, or commits that don’t pass needed tests. This is not always possible to avoid depending on the environment. If this is the case, it would be wise to start commit messages by saying so, e.g. with “[WIP]” or something equivalent, to indicate that the skip command should be used immediately.
Automation
Good practices with workflow can also allow for ease of automation. Having a test suite that can determine if a commit is good or not can assist greatly.
This can be done via exit codes:
- 0 for a successful run (indicating good)
- A value between 1 and 124 or 126 or 127 for a failed test (indicating bad)
- A value of 125 if a build failed (indicating skip)
- Any other value to abort the bisect process
This test need not be a part of the repo itself, so any new testing that needs to be done won’t need to interfere with multiple checkouts.
To start automated tests, we begin in much the same way:
git bisect start git bisect bad git bisect good v1.2.0-release # To declare how to test automatically: git bisect run ~/tests/run-test.sh # or wherever your script is
Tests can be done inline as well. This is useful for many functions, with perhaps the best example being scanning output for a specific message or message fragment:
git bisect run bash -c '/usr/bin/env python3 main.py arg1 arg2 | grep -q "ERROR"'
Finally, using worktree, a specific current and committed test suite can also be defined, allowing checkouts of running code while leaving the most current tests intact:
git worktree add ../bisect-tests HEAD git bisect run ../bisect-tests/tests/run.sh
Other uses
Finding the point of origin of bugs isn’t the end of utility for Git bisect. Using other available scripts, one can use bisect to find performance issues. Some additional things that can be done include:
- Finding when a performance issue began
- Finding when a function or feature was added to the repo (with run grep -q)
- Finding when CI pipelines began failing
- Finding when a security vulnerably was introduced
- Detecting when code coverage degraded
Bisect works well as a general purpose tool for determining when and where a change happened. The only requirement is that it is something testable with a script that can return a pass/fail response.
Official Documentation: https://git-scm.com/docs/git-bisect.
| MT, Software and Platform Engineer Versatile software developer with expertise in programming (C, C++, Python, PHP, JavaScript), databases, cross-platform systems, and debugging, specializing in UNIX/Linux and Windows environments. |

