Git bisect
- 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"
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.
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
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:
[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:
[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:
[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:
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:
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
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.