Skip to content
Nate Comics cover art for issue #05: The bug that wouldn't quit: finding the commit that broke it with git bisect

Issue #05

The bug that wouldn't quit: finding the commit that broke it with git bisect

Published
Tags
Debugging, Git, Engineering
Read time
2 min

When a bug appeared somewhere in hundreds of commits, git bisect finds the culprit in about ten steps. Here's how to run it, and how to automate it.

Some bugs don't come from the change you're looking at. The feature worked last month, it's broken now, and somewhere in between there are hundreds of commits from a whole team. Reading them one by one is how you lose a night.

git bisect finds the commit that introduced the bug by binary search. You tell it one commit where things were good and one where they're bad. It checks out the commit halfway between, you say good or bad, and it halves the range again.

Why it's fast

Each step halves the commits left to check. A thousand commits need about ten steps, because 2¹⁰ is 1,024. A hundred thousand need about seventeen. You test a handful of commits instead of hundreds.

Running it by hand

git bisect start
git bisect bad                 # the current commit is broken
git bisect good v2.3.0         # this release was fine

Git checks out a commit in the middle. Test it (run the app, run the failing test), then tell Git what you saw:

git bisect good    # or: git bisect bad

Repeat until Git prints <sha> is the first bad commit. Then go back to where you started:

git bisect reset

If a commit can't be tested, for example because it doesn't build, skip it:

git bisect skip

Automating it with git bisect run

If you can write a script that reproduces the bug, Git can do the whole search on its own:

git bisect start HEAD v2.3.0
git bisect run npm test -- --run tests/checkout.test.ts

Git runs the command on each step and reads its exit code:

  • 0 means good.
  • 125 means this commit can't be tested, so skip it.
  • 1 to 127 (apart from 125) means bad.

A neat trick: write the failing test first, as a new file outside version control so every checkout keeps it. Then git bisect run uses the regression test you were going to write anyway to find the commit that needs it.

Making your history bisect-friendly

Bisect works best when every commit builds and passes tests. That's one more reason to keep commits small and logical, and to avoid "WIP" commits that leave the code broken. A good history is a debugging tool.

When you find it

The first bad commit tells you where, not why. Read the diff, write the failing test if you haven't, fix it, and mention the culprit commit in your fix's message so the next person can follow the trail.

Questions readers ask

How does git bisect work?
You mark one commit as bad and an older one as good. Git checks out the midpoint, you test it and mark it good or bad, and it halves the range again until one commit is left: the first bad one.
How many steps does git bisect take?
About log2 of the number of commits in the range. Roughly ten steps for a thousand commits and seventeen for a hundred thousand.
Can git bisect run automatically?
Yes. git bisect run <command> runs a script on each step. Exit code 0 means good, 125 means skip, and any other code from 1 to 127 means bad.

Sources

  1. git-bisect documentation (opens in a new tab), Git

Open to senior roles · Remote, hybrid or on-site

Hiring a senior engineer who writes like this?

I'm Nathan Mabasa, a senior software engineer in Johannesburg. I build React and Next.js applications in TypeScript, plus the APIs, payments and security underneath them. Seven years across agencies and in-house teams.

One quick thing

Who's asking?

Leave your details and the CV downloads straight away. It helps me follow up about the role.

Your details go only to me, to follow up about roles. No mailing lists, nothing shared.