DEV Community

Cover image for Debug using git bisect
Nayeem Reza
Nayeem Reza

Posted on • Updated on

Debug using git bisect

What's git bisect?

I just learned about git bisect from this

and from the very first look of it, I was like - whooosh! It should be awesome. From the man page, it says this -
TLDR; git-bisect - Use binary search to find the commit that introduced a bug

So, that's kind of our day-to-day software engineering need, amirite? In the middle of our development of a particular feature, somehow we stumbled upon a situation where an existing code fragment is broken, but it shouldn't because we didn't touch (or actually couldn't remember) any related codes! What do we do then? I personally checkout to particular commits that I feel suspicious or check the file changes, but that's a hell lot of work to actually narrow down the bug. To automate this process, we can use git bisect.

This command uses a binary search algorithm to find which commit in our project’s history introduced a bug. First, we have to tell which one is the "bad" commit that is known to contain the bug, most of the cases that's where the HEAD is, but we can specify a commit hash also! Then, we have to mark a "good" commit where we specifically know everything was working fine. So, if you can remember the binary-search technique, we are marking the low and high indexes here! Then git bisect picks a commit between those two endpoints and asks us whether the selected commit is "good" or "bad". It continues narrowing down the range until it finds the exact commit that introduced the problem.

In fact, git bisect can be used to find the commit that changed any property of our project; e.g., the commit that fixed a bug, or the commit that caused a benchmark’s performance to improve. To support this more general usage, the terms "old" and "new" can be used in place of "good" and "bad", or we can choose our own terms.

How to use git bisect?

First, we have to start a bisect session and mark the good and bad commit hashes i.e. defining the boundary in which we'll search for the bug -

# startup git bisect
$ git bisect start

# give git a commit where there is not a bug
$ git bisect good <commit-hash>

# give git a commit where there is a bug
$ git bisect bad <commit-hash>

Bisecting: X revisions left to test after this (roughly Y steps)
[commit-hash] <commit-message>

Now, the process has been started automatically and git will start splitting the revisions in half and loading them up for us. It will checkout each revision and then ask us if the commit is good or bad. Then, we have to compile and test the code for that specific version to give a verdict to it i.e. good/bad! Our answer will be like this -

# If that version works correctly, type
$ git bisect good

# If that version is broken, type
$ git bisect bad

# Then git bisect will respond with something like
Bisecting: 12 revisions left to test after this (roughly 4 steps)

And after each prompt, it will use a binary search to very quickly narrow down the offending commit. The number of revisions we have between our “good” and “bad” commits will determine how long this process takes but it will still be quicker than individually checking out each commit. Roughly it takes log2(revisionCount) i.e. Y = log2(X)

We have to keep repeating the process: compile, test, and depending on whether it is good or bad hit git bisect good or git bisect bad to ask for the next commit that needs testing. And, eventually, there will be no more revisions left to inspect, and the command will print out a description of the first bad commit. The reference refs/bisect/bad will be left pointing at that commit.

Suppose we are trying to find the commit that broke a feature that was known to work in between these two commits 8c054db and 5908ecf. Now, after starting the git bisect session, we'll specify the good and bad commit -

$ git bisect start
$ git bisect good 5908ecf
$ git bisect bad 8c054db

And you'll see something similar to it, printed in your console -

Bisecting: 4 revisions left to test after this (roughly 2 steps)
[88f2ada15e8b2890b618e0300134deff9e4050c6] Add typography fixes

Here, I have tested my code and it's working fine; let's mark it as good -

Bisecting: 4 revisions left to test after this (roughly 2 steps)
[88f2ada15e8b2890b618e0300134deff9e4050c6] Add typography fixes
$ git bisect good

Then, it'll give us this -

Bisecting: 2 revisions left to test after this (roughly 1 step)
[aa91d493c90a854b78a02fb10b8c097f323b9d4a] 1.1.0

After testing I've found that this is not working properly; so, let's put git bisect bad. Finally, it'll print this -

aa91d493c90a854b78a02fb10b8c097f323b9d4a is the first bad commit
commit aa91d493c90a854b78a02fb10b8c097f323b9d4a
Author: uraniumreza <reza.uranium@gmail.com>
Date:   Sun May 10 19:37:40 2020 +0600

    1.1.0

 src/helper/validation.js            |   9 +++
 src/landing/components/ContactUs.js |  52 +++++++++++++---
 src/landing/service/api.js          |  12 +++
 3 files changed, 73 insertions(+), 11 deletions(-)

After finishing a bisect session i.e. you've successfully tracked down the bug, to clean up the bisection state and return to the original HEAD, we have to run the following command:

$ git bisect reset

By default, this will return our tree to the commit that was checked out before git bisect start. With an optional argument, we can return to a different commit instead:

$ git bisect reset <commit-hash>

For example, git bisect reset bisect/bad will checkout to the first bad revision, while git bisect reset HEAD will leave us to the current bisection commit and avoid switching commits at all

Can we automate the process?

That was basically a manual approach of debugging, though it helps us automating the checkout process and narrowing down the buggy commit effectively. But we can run git bisect with a script (if we have a script that can tell if the current source code is good or bad) which will automate the whole process of compiling, testing, and marking the commit as good/bad!

# git bisect run <cmd> ...
$ git bisect run node bisect/index.js

N.B. The script should exit with code 0 if the current source code is good/old, and exit with a code between 1 and 127 (inclusive), except 125, if the current source code is bad/new.

To know more about this feature please follow the reference section

Reference


Follow me on twitter and RT if you loved it!

Top comments (2)

Collapse
 
shivenigma profile image
Vignesh M

Very good article Nayeem. I will almost always forget git bisect exists in the moment of bug.

I started doing squash and rebase merging PRs from today to be able to run bisect more efficiently.

I know it will help a lot. Keeping the history straight forward will make the bisect a whole easier.

Collapse
 
uraniumreza profile image
Nayeem Reza

Yeah I agree on the squash and rebase merging PRs to keep the history straight forward and clean! It just not only helps to bisect, but also manual code revision seems super easy then :)