Finding Bugs in log(n) Time With Git Bisect
You begin running your application, just for fun. It's gotten pretty large at this point, complete with dozens of features and interesting interactions. You decide to test one of them. An old favorite, perhaps? You trigger it, expecting to see a pleasant popup that you spent a weekend on a few months back.
Your application stutters, stops, and crashes.1 You do a double take. You try again. Same result. The expectations, the disappointment, the casual naivety you started with, the irony of it all: it's shocking, like stepping down to a nonexistent stair in the dark.
You've found a regression: a bug that breaks a that feature previously worked. In large codebases with long, storied git histories, they can be notoriously difficult to track down. In this post, I'll show you an effective way to find the exact commit which introduced the bug, in the order of time.2
What is Git Bisect?
Note: This section assumes you have some knowledge of git.
I will introduce git bisect
with an example: our goal is to find the first
version of the app that introduces the bug, in a history that looks like this:
Each circle represents a version of your app (i.e. a commit), where the bug may
or may not be hiding. G
represents your current version, which you know is
broken. Your first instinct might be to check if commit D
has the bug, since
it's in the middle of the "timeline":
It does! Importantly, we can make an assertion with this information: anything
to the right of commit D
must also have the bug, since git commits are
chronologically ordered (G
is your most recent version).3 So, really,
your history looks like this:
Your search space has now decreased by half. You now check if B
has the bug,
since it lies in the middle of the three unknown commits.
B
doesn't have the bug, so A
must not either! That means that there are two
options now:
C
is green: bug inD
!C
is red: bug inC
!
You check C
, and it's green:
So, you must've introduced the bug in D
, meaning that you know exactly where
to look to make a fix!
Summary
If you're familiar with algorithms, this process is basically a
binary search. git bisect
is a
handy sub-command built into git that automates what I just showed. Yes, you
could get the exact middle of your git history yourself, but git bisect
will
do it for you! In the next section, I'll go over how to use the command.
Usage
To start git bisect
, run
$ git bisect start
This will give you a message that looks like this:
status: waiting for both good and bad commits
git bisect
needs to know at least one commit that is "good" (no bug), and one
commit that is "bad" (has bug). git bisect
will find the first "bad" commit,
as shown in the example.
Usually, the commit I'm currently on (HEAD
) is bad, and we can let git
know
like so:
$ git bisect bad
status: waiting for good commit(s), bad commit known
git bisect
is telling us now that it only needs to know any commit that is
good. You can find this manually by checking out some old commit and seeing if
it has the bug, or you can just tell git
that your very first commit is good
(as is often the case):
$ git bisect good $(git rev-list --oneline HEAD | tail -n 1 | cut -wf1)
After entering this, you'll be automatically switched to a version of your
repository. From there, you can run your app and see if it has the bug. If it
does, run git bisect bad
. If it doesn't, run git bisect good
. In other
words, you have the following workflow:
- Check out a commit
- Good or bad
- Repeat until
git
finds the first bad commit!
Note: If at any point you need to quit the bisection, run
git bisect reset
.
Wrap-Up
This post went over how git bisect
can be used to fix regressions! I also
showed it actually works, using an example. I
personally use the sub-command quite often, especially as my projects grow older
and more complex.
If you want to learn more about git bisect
, you can
read the manual! If you have any
feedback, questions, or issues with this post, feel free to
submit an issue on this
website's GitHub repository!
Maybe it even gives you a really helpful crash message such as:
SIGBUS
. ↩︎That is, if you have 1000 commits, you'll find it in 10 steps worst-case. ↩︎
If you need some time to wrap your head around this, feel free to! This also makes the assumption that the bug isn't introduced, fixed, then reintroduced in the timeline. ↩︎