Altijd in beweging...

Technische ontwikkeling staat nooit stil

Git bisect

November 8, 2020
  • Git

Version control is great since we can document each change, but what if you suddenly realize somewhere along the way the application is not behaving like before. A bug was introduced somewhere but when is unknown. Of course, we could checkout every commit in the version history and check if the issue was present in the application at that point but that can be very time-consuming. When you need a more efficient way you can use the bisect command.

Create a mini git history to try the command

To show how to use the command we will create a local git repository and some commit history with the following steps.

Create a directory for the repository

mkdir test-commit-history
cd test-commit-history

Initialize a git repository in the directory

git init

Create some (simple) commit history

echo car > history.txt
git add -A
git commit -m "Adding the word car"
echo cars >> history.txt
git add -A
git commit -m "Adding the word car to cars"
echo car > history.txt
git add -A
git commit -m "Override file with original start"
echo bike >> history.txt
git add -A
git commit -m "Adding the word bike"
echo boat >> history.txt
git add -A
git commit -m "Adding the word boat"
echo train >> history.txt
git add -A
git commit -m "Adding the word train"
echo sailboat >> history.txt
git add -A
git commit -m "Adding the word sailboat"
echo motorcycle >> history.txt
git add -A
git commit -m "Adding the word motorcycle"
echo cruiseship >> history.txt
git add -A
git commit -m "Adding the word cruiseship"
echo subway >> history.txt
git add -A
git commit -m "Adding the word subway"
In case you are unfamiliar with the echo command. echo car > history.txt writes the word car to a file named history.txt. If the file would be present already it will be overridden as if the file did not exist (so previous content is gone). echo car >> history.txt appends the word car to a file named history.txt. If the file would be present already it will append it to the content.
The commit ID's will unique for each commit so the commit ID mentioned in this article will be different in another situation. They are only mentioned for the purpose of illustration.

Find the commit that caused the issue

Let's say that we look at the content of the file and see that the word 'sailboat' should have never been there. Although deleting the word would be simpler in this example ;-) imagine it to be a more complex case and we want to find the commit that caused the undesired state. The example is kept very simple on purpose so we can focus on the command and not the complexity of the example.

How will bisect help us here?

Bisect can help us go through the commits and determine the commit that caused the undesired state through a process of elimination (using a binary search algorithm). By marking a good and bad commit as a starting point, with bisect we can zoom in on a commit between these markers which you will also mark as either good or bad. Bisect will continue this process until it can determine the first commit where the issue was introduced.

Let's get back to our example and see this in action. I'm going to pretend that I only know the first commit was correct and the last was bad.

Start the bisect session en mark the first commit as good (commit ID: 30029a75635d5e63f420b03901640ef8d494bec8) and the last commit as bad (commit ID: 7b7089c3695942d4c85457ca3667cd4a6a1d5ac8)

git bisect start
git bisect bad
git bisect good 30029a75635d5e63f420b03901640ef8d494bec8
If you omit the commit ID it will use the current commit.

or in one line

git bisect start HEAD 30029a75635d5e63f420b03901640ef8d494bec8

If bisect can't determine the commit yet it will checkout the next commit to mark as either good or bad:

Bisecting: 4 revisions left to test after this (roughly 2 steps)
[001e179509061fa429d8fedcb8be84cc6e2ae1ce] Adding the word boat

Since 'sailboat' isn't in this commit yet we will mark this revision as good:

git bisect good

Which results in the following:

Bisecting: 2 revisions left to test after this (roughly 1 step)
[c4f16a069094304a89d94cb370f101b737f0b844] Adding the word sailboat

Since in this commit we see the word 'sailboat' we mark this revision as bad:

git bisect bad

Which results in the following:

Bisecting: 0 revisions left to test after this (roughly 0 steps)
[17caebfc7a5988e21d7f12df727896b7fb9f1917] Adding the word train

If you know a certain commit won't be testable (has other issues which are not related to the issue you are tracking) you can use: git bisect skip so a nearby commit will be selected next. The next selected commit is checked out and we see the state is as we would want it, so the commit is marked as good:

git bisect good

After marking this commit bisect reports that it has determined the commit that caused the issue:

c4f16a069094304a89d94cb370f101b737f0b844 is the first bad commit
commit c4f16a069094304a89d94cb370f101b737f0b844

Now we can end the bisect session:

git bisect reset

This means it will restore to the state it was in when the bisect session was started:

Previous HEAD position was 17caebf Adding the word train
Switched to branch 'master'

In case you want to end the session and return to another commit you can pass the commit ID you want to checkout:

git bisect reset 5118356f62c29db49cd00a0d0377b1791ef62937
There are advanced scenarios possible by automating the above steps through git bisect run but that is beyond the scope of this article.

What about the blame command?

It has similarities in that you want to find the commit that introduced the undesired state, but a key difference is that with blame you are looking for the code that caused the issue (which is known to you). bisect is useful when you are only aware of the problem in the application but have no idea what (code) change caused the issue. Bisect helps to find the code.