Image of Git Bisect | How to Use Git Bisect

ADVERTISEMENT

Table of Contents

What is Git Bisect?

Locating bugs in a timely and efficient manner can make or break some development teams. On a large project, with a multitude of branches and many commits, it can be distressing when the team realizes there is an issue with the codebase. No one on your team knows when the bug was introduced or has a clear timeline for when the software was last operational.

So how can you locate this bug, without poring over lines of code? Without reviewing each commit one by one, looking for when the bug might have been introduced?

That's where git bisect comes in. No need to go line-by-line through countless commits. The git bisect command is a fantastic tool that can help you determine which commit introduced the bug.

A regression bug is a specific type of software bug that prevents a feature or software from working when it was working before the bug was introduced.

Just remember, git bisect won't "fix" the broken commit for you. Once you've identified the problematic commit, you will need to run a few Git commands and dive into the code, to find the issue, resolve the issue, and redeploy the code. At that point, your program should work again!

Starting with Git Bisect

Git bisect is used for a singular purpose, to locate a specific commit that introduced a change or bug into your code. It does this by using a binary search algorithm to help you identify the bad commit. This means that git bisect will help you efficiently search through your commits to find the problem, by repeatedly cutting the number of potential problematic commits in half.

If you don't use git bisect, you can manually check each commit one by one in a linear fashion. This means you would need to use git checkout on each commit that you think might have introduced the bug, then build and run your program to check for the regression bug until you find the offender. Using git bisect is preferred because it drastically reduces the number of commits you need to check.

Imagine a repository with 9 commits. Suppose the 1st commit was known to be "good" (no bug) and the 10th commit is "bad" (contains the bug). Let’s also suppose that the bug was introduced in commit 4 (but you don't know that yet!). If you work backwards from the HEAD commit (commit 10) and check out each commit one by one to test each one, you would need to manually check commits 9, 8, 7, 6, 5, and 4. That's six commits.

Let's see how many commits need to be checked when using git bisect's binary search strategy.

The git bisect command will start by asking you to identify the last known "good" commit. In this case it is commit 1. It will then check out the "middle" commit in between the known good and known bad commit.

If the number of commits is even, one of the two middle commits will be selected. In this case, it will be commit 5. If the number of commits is odd, then it will perfectly bisect and select the middle commit.

So in this case, git bisect selected commit 5. Next, you need to verify if the 5th commit is "good". In this example, the answer is no, commit 5 is "bad". This means the bug was introduced somewhere between commits 2 and 4. Now, git bisect will split the remaining commits in half again, by checking out commit 3.

You find that the 3rd commit is "good". Since the 3rd commit is good and the 5th commit is bad, we have narrowed down the bug to the 4th commit. After testing for confirmation, you'll know that the regression bug was introduced in this commit. That is the overall basis for how the git bisect command works.

The efficiency of git bisect is even more apparent in larger projects.

Imagine a larger code base with 50 commits. The first bisect may eliminate up to 25 commits, that you no longer need to be concerned with. If you were manually checking these commits, you'd be checking 25 commits before realizing the bug was introduced in the latter half of your commit history. Splitting the search space in half each time exponentially decreases the number of commits you need to test manually.

How to Use Git Bisect?

In this section, you'll learn how to actually use git bisect and the associated subcommands. This guide will cover the 6 most common subcommands, but there are other subcommands that can be used for specialized situations.

Git Bisect Subcommands

Here is a summary of each subcommand and what it does:

SubcommandsWhat they do
git bisect startBegin using the git bisect command
git bisect good Identify if a commit is "good", without the bug
git bisect bad HEADIdentify if a commit is "bad", with the bug
git bisect resetReturn to original head
git bisect logDisplays commits you've identified as "good" or "bad"
git bisect visualizeView commits that have not yet been checked

Git Bisect Usage

Check the record of commits in your repository using git log. This will display the commit hash, author, date, and commit message for each commit.

$ git log
		
commit a3ebe8e06c80207d8c3a8adce267f0a09c3be910 (HEAD -> master, FreelanceWork/master)
Author: John Doe <11111+johndoe@users.noreply.github.com>
Date: Sun Feb 27 06:34:04 2022 -0500
		
	Final Draft

commit  c1e92010bb37601441bd1f3259b3263eebc256e4
Author: John Doe <11111+johndoe@users.noreply.github.com>
Date: Sun Feb 27 05:34:04 2022 -0500
		
	Passive Voice Edits pt 1

commit 4f7d793e07d58a9e9bccccea6382eb115460b7
Author: John Doe <11111+johndoe@users.noreply.github.com>
Date: Sun Feb 27 04:34:04 2022 -0500
		
	Critical Security Trends RD

commit 5ac7e5d45c029b4108e091ce393c474ba486e745
Author: John Doe <11111+johndoe@users.noreply.github.com>
Date: Sun Feb 27 03:34:04 2022 -0500
		
	Added Vision paragraph2

commit cbd50d400b0f3af15986f9bc46f384f700ff6b66
Author: John Doe <11111+johndoe@users.noreply.github.com>
Date: Sun Feb 27 02:34:04 2022 -0500
		
	introductory paragraphs

If you’ve got a regression bug, navigate to that repository in the command-line, and start up the git bisect tool by entering the following command:

git bisect start

Nothing will happen at this point, but you are letting Git know that you are going to start the bisection process by specifying the last known "good" commit. This will change the directory notation to:

User@System:Path
(master|BISECTING)

Next, you need to identify the last known "good" commit and the known "bad" commit. Usually, the HEAD is a known bad commit, so you can use that or enter the SHA-1 identifying hash. Consult with your team to find the last commit known to be good. If you have no clue, then you can still select the 1st commit in your repository and work from there. It makes take a little longer, but it's doable with git bisect:

git bisect bad HEAD
git bisect good <commit hash>

Let's imagine you know the first commit in your project's history above is good. This commit has the hash cbd50d400b0f3af15986f9bc46f384f700ff6b66 and commit message "introductory paragraphs". You would identify this as a good commit with the following code:

git bisect good cbd50d4

Note that you can specify only the first 6-7 characters of the commit ID for brevity.

Once you’ve identified a good and bad commit, the git bisect process will begin and will print out a readout of what it’s going to do. It will also note the next commit that is going to be checked out and tested, in the following line. This readout will be something along the lines of:

Bisecting: X revisions left to test after this (roughly Y steps)
[commit hash] commit message

Given our example, the output would be:

Bisecting: 1 revision left to test after this (roughly 1 step)
[4f7d793e07d58a9e9bccccea6382eb115460b7a9] Critical Security Trends RD

Compile the commit that is currently checked out. You want to determine if this commit is good or bad. If it’s working correctly, enter:

git bisect good

Otherwise, enter:

git bisect bad

Each time you do this, the X value in the readout printed by Git should be half of the previous number. For example, if it read 100 revisions left to test in step 3, it will now read out 50 revisions.

Bisecting: X revisions left to test after this (roughly Y steps)
	<commit hash> <commit message>

Continue repeating these steps until you locate the first bad commit. When there are no other commits left to verify, Git will print out:

<commit hash> is the first bad commit

Git will reference this commit with refs/bisect/bad. Once you've identified the commit where the regression started, you can fix the issue.

When closing out a bisect session, you can return to the original HEAD of your repository by entering:

git bisect reset 

Git Bisect Log to Check Commit Status

If at any time during the bisect session, you want to know how you have marked each commit, as either good or bad, you can use the git bisect log command. This will follow the template:

git bisect start
# bad: [commit hash] commit message
git bisect bad commit hash
# good: [commit hash] commit message
git bisect good commit hash

For our example, it would output the following log:

git bisect start
# bad: [a3ebe8e06c80207d8c3a8adce267f0a09c3be910] Final Draft
git bisect bad a3ebe8e06c80207d8c3a8adce267f0a09c3be910
# good: [cbd50d400b0f3af15986f9bc46f384f700ff6b66] introductory paragraphs
git bisect good cbd50d400b0f3af15986f9bc46f384f700ff6b66

Git Bisect Visualize

Similarly, if you want to review the commits that are remaining to be tested and may still contain the bug, you can use the git bisect visualize command. This will also display the current commit that is checked out, aka the next commit to be tested. Continuing with the example, you’d expect an output like:

user@path ((4f7d793...)|BISECTING)
$ git bisect visualize

commit a3ebe8e06c80207d8c3a8adce267f0a09c3be910 (FreelanceWork/master, master, refs/bisect/bad)
Author: John Doe <11111+johndoe@users.noreply.github.com>
Date: Sun Feb 27 06:34:04 2022 -0500
		
	Final Draft

commit  c1e92010bb37601441bd1f3259b3263eebc256e4
Author: John Doe <11111+johndoe@users.noreply.github.com>
Date: Sun Feb 27 05:34:04 2022 -0500
		
	Passive Voice Edits pt 1

commit 4f7d793e07d58a9e9bccccea6382eb115460b7 (HEAD)
Author: John Doe <11111+johndoe@users.noreply.github.com>
Date: Sun Feb 27 04:34:04 2022 -0500
		
	Critical Security Trends RD

commit 5ac7e5d45c029b4108e091ce393c474ba486e745
Author: John Doe <11111+johndoe@users.noreply.github.com>
Date: Sun Feb 27 03:34:04 2022 -0500
		
	Added Vision paragraph2

Make sure to note that the "Critical Security Trends RD" commit has been identified as the HEAD, whereas the "Final Draft" commit has been identified as bad with the refs/bisect/bad tag.

Fixing Bugs with Git Bisect

Once you’ve identified the commit where the bad code was added, you now need to fix this commit. There are many ways to undo changes in Git, but here we're just going to focus on the git revert command to undo the changes that introduced the bug.

Git revert is really a simple command, all it’s going to do is reverse all the changes from a previous commit.

For example, if you added 5 lines of code to your 15th commit, which introduced a bug, using git revert will effectively undo those 5 lines of code. Now the 15th commit matches the 14th commit and no bug has been introduced to your source code.

Fixing Commits with git diff and git revert

So you've located the problematic commit, with identifying-SHA hash a1b2c3.... Now you want to identify what changes were made or lines of code were added. You can do this using the git diff command. Git diff will display the differences between the commit files.

$ git diff
diff -- git a/file.txt b/file.txt 
file a1b2c3 .. d4e56f 100644
- - - a/file.txt
+++b/file.txt
@@ -1 +1, 2 @@
This sentence before.
Sentence after. 

Notice that the last two lines will display the differences between the files. If the bug is obvious, you can make any changes to rectify the problem.

If you make any edits, you can use git add to push the commit back to the staging area.

Then you need to use git commit to commit files to your repository. Now you can redeploy your code and test that it works as intended. If everything was done correctly with git bisect you will no longer have any issues.

If the problem isn't as obvious or wouldn't be easy to remedy manually, you can use git revert:

git revert <commit hash>

Following git revert you can verify that the changes have been made, you can use git log which will print out something like:

$ git log --oneline

af683sd Revert "Added 5 lines of code"
cdb76bf Added 5 lines of code
d425161 Added 10 lines of code

Git Bisect Old and Git Bisect New

The git bisect command is not limited to fixing regression bugs. Your team may be searching for the commit that introduced a new feature or anything else you can imagine. As long as you can identify a change, you can locate the commit that introduced it using binary search. But when git bisect is used for other purposes, the standard naming convention of "good" and "bad" can become confusing.

You can reduce this confusion when using git bisect by using the alternative terms "old" and "new". These terms are better for identifying changes or differences that are not project-breaking. Just remember you cannot mix these terms with the "good" and "bad" terms.

You can identify a commit using these terms, as seen below:

git bisect old
git bisect new

Summary

Git bisect is a great tool to help you efficiently and methodically find commits that are breaking your project. As a project grows, so too does the number of commits. Some commits may be small, adding only a few lines of code, while others may add major chunks of code and functionality. If a developer accidentally introduces a regression bug to the project, and it is not immediately noticed, this can cause confusion later on.

Instead of researching previous emails and messages and trying to figure out the last time your project was working, you can just use git bisect. Git bisect is far more effective than manually checking each commit, because it uses binary search. The Git bisect command will help you check your commits and identify each commit as "bad" or "good".

Once the problematic commit has been located, you and your team are then tasked with identifying the bug and fixing the code. You can check what changed between two commits using the git diff command, and you can undo changes to a commit using the git revert command.

Now that you’ve located and removed the bug, your project is functional again. You can return to development and continue making progress towards completion. With each new Git command you learn, you grow and mature as a developer.

Next Steps

If you're interested in learning more about how Git works under the hood, check out our Baby Git Guidebook for Developers, which dives into Git's code in an accessible way. We wrote it for curious developers to learn how Git works at the code level. To do this we documented the first version of Git's code and discuss it in detail.

We hope you enjoyed this post! Feel free to shoot me an email at jacob@initialcommit.io with any questions or comments.

References

  1. Khan Academy, Binary Search - https://www.khanacademy.org/computing/computer-science/algorithms/binary-search/a/binary-search
  2. Git SCM Docs, git revert - https://git-scm.com/docs/git-revert

Final Notes