Once upon a time, there was a young developer who worked on a small side project, after a few hours he created a working version and started to refactor the code to make it nice, tidy, and clean. He refactored the code for a couple of hours and kept working neatly in small and meaningful commits. When he was pleased with his work he ran the code again but oh no, something terrible happened… the code didn’t work.
We have all been there, for me it happened a week ago, I sat in my bed and stared at my IDE rat me out that it was I who wrote that buggy code, “I got only myself to git blame that’s for sure” I grinned with myself but after a second a light bulb popped with a new idea — 💡 what if I can use Git to traverse through all of my commits and hunt down my bug? fortunately, after a short search on Google I discovered that it is possible - using the simple git bisect command.
In this tutorial, we will explore the
git bisect command. How it works, why, and how to use it so hopefully you will find it useful and powerful as I do!
As you may know for git everything is about commits, a branch for that matter is nothing but a pointer to a single commit. This allowed git to traverse through the commits and provide a couple of debugging tools to help us debug our code: File Annotation(a.k.a ‘git blame’) and Binary Search.
File annotation shows the last commit to modify each line of any file and done using the
git blame command, this is often the most useful tool for tracking down a bug and know who is the one to blame. If you are not familiar with git’s file annotation I highly recommend you read about it form git documentation.
A way to locate which commit introduced an issue using binary search through all the commits history of a particular branch. It’s done with the
git bisect command which checks out each commit allowing you to run a test to see if the issue exists in this commit. When you’re hunting down a bug but you don’t know where to look for it and there were hundreds of commits since the last good commit you remember - the
git bisect command is your answer.
Let’s say you worked on a new branch…
🥳 You worked for some time until you finally reach a good point with a good working product.
😌 You are also a responsible developer who wishes to keep his tree clean so you probably added a few commits along the way and had the following commits history:
💎 You decided it’s time to start refactoring the code, make it nicer and clean, eventually you ended up with the following commits history:
😰 Unfortunately, your code isn’t working properly at the current commit, apparently somewhere along the way a part of the code got broken.
🧐 You know that it had definitely worked before the refactor — when you added commit
271cb1c — meaning there is a bug in the code between commit
9c07a95, the only question is…. How do you find it?
Inspired by Clint Eastwood I like to think about this part as the story of ‘The Good, the Bad and the Wizard’:
git bisect start command to startup the
git bisect wizard.
git the binary search works by repeatedly dividing in half the commits that could contain the bug and narrowing down the possible commits to one, this makes the scope of the search essential. The way to define the
git bisect wizard what is the searching scope is by running the
git bisect bad command which tells
git that the
current commit is broken and contains a contagious bug.
The bad commit is only one end of the scope now we need to tell
git when the last known good commit was, we do that by running git bisect
<good_commit> command, for example in our case, the good commit was commit
271cb1c so we’ll run the following command:
git bisect 271cb1c
After running this command
git will start checkout commits using binary search and allow at each commit to build the app and check whether or not the bug exists, all left for you to do is to make sure you tell
git the status of the last
git bisect command with either
git bisect bad or
git bisect good so it will continue to narrow down the possible commits, repeat until it finds the commit where the bug was introduced.
Let’s assume we checked out on commit
66e0075 and that we found out that the bug occurred before it so we run:
git bisect bad
git has all it needs to determine that the commit between commit
66e0075 and commit
271cb1cis the commit where the bug introduced. When
git finally finds the commit with the bug it prints all the information it has about it to help you figure out what happened that may have introduced this bug, for instance:
$ git bisect bad 66e00756e887030abcbde19ac3e10a7c2942036a is the first bad commit commit 66e00756e887030abcbde19ac3e10a7c2942036a Author: Noaa Barki <firstname.lastname@example.org> Date: Sun Apr 26 12:10:56 2020 +0300 fix: added client base/client.go base/client_test.go 1 files changed, ...... create mode 100644 base/client_test.go
Like every time-travel journey its important to go back to where it all began otherwise we might be checked out on a commit from the past and end up in a weird branch state. We go back to the present by running the simple
git bisect reset command which shuts down the
git bisect wizard and reset the
HEAD to where we were before we started.
This is the part when
git bisect really shines - one of
git bisect subcommand is the
git bisect run which accepts a command as an argument and allows to fully automate
git bisect run <cmd>...
After running this command
git automatically runs
cmd on each checked-out commit and uses the return value of that call to decide if the commit is good or bad until it will find the first broken commit.
First, you tell
git the scope of the bisect by providing the bad and good commits:
$ git bisect start <bad_commit> <good_commit>
git bisect run with your command and let
git do all the work for you, for example:
$ git bisect start HEAD 991debbc8b9e05ddc217023a898109822a70dd60 $ git bisect run npm run test
The command can be any script that will exit 0 for “good commit” or non-0 “bad-commit”.
In this tutorial, you learned how to track down a bug using the
git bisect command. Moreover, you learned how to automate it and by that reduce debugging time significantly. I hope you’ll try
git bisect in the future when you’ll look for a nice solution for hunting down a bug.
Special thanks for my teammates at datreeio 🙏🏻