Automatically Locate When and Where Bugs Were Introduced with git bisect
Let’s face it, when developing software things will break. Most of these things were probably working before hand, but some change directly or indirectly has broken it.
Best case scenario is you immediately recognise why it is broken. On the other end of the scale is some obscure bug that’s only happening sometimes, you have no idea why and it seems like none of the recent code changes should have even effected it. Argh! What next?
Well, first of all, take a deep breath, confidently crack your knuckles and open your terminal with with a menacing grin. Now you’re ready.
Let’s start with what we do know. Well, we know that at some point in the past it was working and we know now that’s it not. Therefore it’s pretty fair to surmise that one of the commits between now and then must have broke it.
You should be on a commit that is known to be broken. This will probably just be the branch you’re on. Make sure you have a clean checkout (no changes) and start the bisect process.
git bisect start
If all goes well you will not receive any output. As mentioned above we know that this version of the software has the bug, so we mark this commit as bad:
git bisect bad
Again, there will be no output if it’s all good.
Now we need to find and checkout a commit that we know is good (does not have the bug). In this case I know it was working on 2016–08–03 so I can use
git rev-list to find me the latest commit at that date. Alternatively you could just checkout a known tag, branch, commit, or whatever you are fairly sure was working.
git checkout $(git rev-list --max-age=2016–06–03 --date-order --reverse HEAD | head -n 1)
The important next step here is that you confirm that the bug is not occurring before proceeding. If the bug is still there you need to checkout an even older commit and try again. Keep doing this until you can confirm the bug is not there, and now proceed by telling git the commit is good:
git bisect good
Now you should see some meaningful output as
git bisect is ready to do it’s magic:
Bisecting: 987 revisions left to test after this (roughly 10 steps)
Bisect literally means to divide into two parts, and that’s what git has just done. It has found the middle commit between the 987 revisions and performed a git checkout.
Now it’s up to you to test if the bug exists at this commit. If the bug does exist you should type
git bisect bad. If it does not exist you should type
git bisect good. Each time you do this the number of revisions should half:
Bisecting: 489 revisions left to test after this (roughly 9 steps)
[03c4c4e5bd1740a277a606dd208e17a855761243] Merge branch 'release/1.5' of https://github.com/elliotchance/concise into 1.4/193-set-color-scheme
You will have to repeat this around 9 more times with
git bisect good or
git bisect bad until there is no more to bisect and you should see something like this:
57a00e86622073c887be101a2e76c079245e519c is the first bad commit
Hooray! Here are some useful commands to inspect the bad commit and hopeful it will be very obvious what the code change was that caused the bug:
git show 57a00e8
git diff 57a00e8 57a00e8^
It’s important to tell
git bisect when you are finished:
git bisect reset
This will tell
git bisect you are finished and clear all the progress (so be careful if you’re not finished). It will also checkout the commit you were on when you did the
git bisect start.
You will find that the rinse-and-repeat process above works most of the time, but there are a few little tricks for when it goes wrong.
- If you cannot manually test a commit. For example, your program does not compile then you can use
git bisect skipto let
git bisectknow that you can’t verify it either way and
git bisectwill try to recover by picking a commit thats very close to this without further bisecting.
git bisectis very clever in that it will isolate the commit even if it’s not a completely linear ancestry. If you know several commits that are good or bad going in you can checkout and confirm these individually at any time without affecting progress (or hopefully lessening it).
git bisectrequires a
git checkoutwhich requires a clean working directory. It is common to have to tweak the code at each step to verify your bug. However, you will have to return the code to a clean checkout state before proceeding. This is especially annoying if you need to apply and revert the same patch for each step. For this I would recommend
For Lazy People
If your test is something that can verified through the console, such as finding when a unit test started to fail. You can use
git bisect run to automate the process.
git bisect run will use the exit status on the command you give it to decide if the commit was good or bad. Here is an example:
git bisect run phpunit --filter testRecordIsSavedToTheDatabase
Originally published at http://elliot.land on September 3, 2016.