From time to time, developers need to fix features that are no longer working, which can be tedious and challenging since the detection is not as immediate.
To better understand what happened, developers try to find the incorrect commit on the Git history. A common way is to check out old commits and make sure the feature isn't broken anymore. However, this process can take many steps and attempts resulting in developers giving up and adding a bunch of breaking points or console logs to debug the issue.
Fortunately for us, Git has a tool to reduce time spent on this type of task through automation called git bisect. Git will do a binary search to find which commit introduced a bug in the project's history. On the other hand, with this tool, we will be able to tell Git what is the “bad” commit, which contains the bug, and what is the “good” commit that we know doesn't have the bug. From here, Git will automate the search process by automatically checking out commits until it can finger point the incorrect commit.
Example environment
To better exemplify the tool, we prepared a Git repository that is publicly available on GitHub. For context, this repository has a simple Svelte project with a canvas animation that changes the color fill of an SVG with the Svelte logo. This logo should be animated with a rainbow of colors, but it only animates to darker colors due to a bug that was introduced.
You can test it yourself by cloning the repository into your machine and using pnpm to install dependencies. You can use the Node package manager of your choice, but we don't provide a lock file for others. If something doesn't work, consider this.
Now, let's clone the repo, install the dependencies, and start the dev server. Just follow the commands below, and everything should go just fine.
https://gist.github.com/pixelmattersdev/bfcfdeb00486abc79c58805da033196e?file=install.sh
Unless you have the port 5000 busy, a development server will start on that same port. Visit http://localhost:5000, and you will notice the animation looks off, as mentioned above.
Using Git Bisect
First off, let's find the commit with the animation still working. For that, we will use the git log command:
https://gist.github.com/pixelmattersdev/bfcfdeb00486abc79c58805da033196e?file=git-history.txt
We know that the most recent commit is bad, so we will use it as our bad commit. On the other hand, we also know the animation was working when first delivered so we will use the animation implementation commit as our good commit:
https://gist.github.com/pixelmattersdev/bfcfdeb00486abc79c58805da033196e?file=commit-ddb8e.txt
Now that we have the good and bad commit, we can start to bisect. Bisect is like a wizard that assists you in the whole process. We must indicate if the commit is good or bad on each bisect iteration. To start, execute the following command:
https://gist.github.com/pixelmattersdev/bfcfdeb00486abc79c58805da033196e?file=bisect-start.sh
Now, Git is on bisecting mode. The next step is to provide a good commit. Let's pick the commit hack from above and tell Git that it is a good one:
https://gist.github.com/pixelmattersdev/bfcfdeb00486abc79c58805da033196e?file=bisect-good.sh
Let git bisect know the current commit is a bad one. Since Git stayed on the most recent commit of our branch, we can omit the commit hash:
https://gist.github.com/pixelmattersdev/bfcfdeb00486abc79c58805da033196e?file=bisect-bad.sh
Now that Git knows about the bad commit, it will start the bisect process. Since we have few commits between the good and bad commit, the bisect tool let us know that there is roughly 1 step ahead to find the bad commit.
https://gist.github.com/pixelmattersdev/bfcfdeb00486abc79c58805da033196e?file=bisect-msg.txt
After we check our browser, we can see the animation still now correctly working, so we need to mark this as a bad commit. Bisect uses a binary search algorithm, meaning it cuts the git history in halves until it finds the bad commit.
https://gist.github.com/pixelmattersdev/bfcfdeb00486abc79c58805da033196e?file=bisect-bad-msg.txt
The next commit that Git jumped to is also a bad one, so we need to do the same thing — tell bisect that this is a bad one.
https://gist.github.com/pixelmattersdev/bfcfdeb00486abc79c58805da033196e?file=bisect-bad-msg-2.txt
We got it! Git just tells us the exact commit where the bug was introduced. Since there is no commit left between this one and the original implementation, there is no other alternative. We can reach the git commit author with this information if we need to get additional information or address the issue by ourselves.
To finalize, tell Git to bisect to end the wizard:
https://gist.github.com/pixelmattersdev/bfcfdeb00486abc79c58805da033196e?file=bisect-reset.sh
Now that we know which commit is the bad one, we can look at the diff to see what changed:
https://gist.github.com/pixelmattersdev/bfcfdeb00486abc79c58805da033196e?file=bisect.patch
There it is, a change on the imageData. We can revert this change, commit the fix and open a Pull Request for our teammates to review it.
Conclusion
In this case, all the commits that bisect navigated to were bad ones. If in your case, it shows you a good commit before finishing bisect, tell Git that iteration is a good one, git bisect good.
It was possible to see how rapid and straightforward it is to find which commit introduced a specific issue using bisect. The example presented here is small, but imagine doing the same on more significant projects with many git activities and code contributors — things could become complicated in scenarios like these.
After getting used to this tool, you will never use any other technique to find incorrect code committed to the Git repository.