How do I use git bisect?
Asked Answered
R

7

653

I have read some articles saying that git bisect is awesome. However, I can't understand why it's awesome.

  1. How do I use it?
  2. Is it just like svn blame?
Roundelay answered 17/1, 2011 at 12:22 Comment(7)
@01: As the git book says: perform a brute-force search through the project's history.Delarosa
Not so ///brute :-), it uses binary search.Stead
"git blame" is similar to "svn blame". "git bisect" is a completely different thingPetite
For what it's worth, there's a good description of bisect in Pro Git too. Sylvain's answer is another good shot at it. If after looking at all of that you still don't get it, I'd suggest you ask a more specific question. General questions beget general answers.Lordly
ProGit has better explanation, but i think Sylvain explain it a lot better. From what I understand its not only search.Roundelay
updated book link: git-scm.com/book/en/Git-Tools-Debugging-with-Git#Binary-SearchFreeman
bisect is not similar as blame. It has different purpose. It uses binary search, not brute force. Brief explanation on link: git-scm.com/docs/git-bisectKindless
H
879

The idea behind git bisect is to perform a binary search in the history to find a particular regression. Imagine that you have the following development history:

... --- 0 --- 1 --- 2 --- 3 --- 4* --- 5 --- current

You know that your program is not working properly at the current revision, and that it was working at the revision 0. So the regression was likely introduced in one of the commits 1, 2, 3, 4, 5, current.

You could try to check out each commit, build it, check if the regression is present or not. If there are a large number of commits, this can take a long time. This is a linear search. We can do better by doing a binary search. This is what the git bisect command does. At each step it tries to reduce the number of revisions that are potentially bad by half.

You'll use the command like this:

$ git stash save
$ git bisect start
$ git bisect bad
$ git bisect good 0
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[< ... sha ... >] 3

After this command, git will checkout a commit. In our case, it'll be commit 3. You need to build your program, and check whether or not the regression is present. You'll also need to tell git the status of this revision with either git bisect bad if the regression is present, or git bisect good if it is not.

Let's suppose that the regression was introduced in commit 4. Then the regression is not present in this revision, and we tell it to git.

$ make
$ make test
... ... ...
$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[< ... sha ... >] 5

It will then checkout another commit. Either 4 or 5 (as there are only two commits). Let's suppose it picked 5. After a build we test the program and see that the regression is present. We then tell it to git:

$ make
$ make test
... ... ...
$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[< ... sha ... >] 4

We test the last revision, 4. And since it is the one that introduced the regression, we tell it to git:

$ make
$ make test
... ... ...
$ git bisect bad
< ... sha ... > is the first bad commit
< ... commit message ... >

In this simple situation, we only had to test 3 versions (3, 4, 5) instead of 4 (1, 2, 3, 4). This is a small win, but this is because our history is so small. If the search range is of N commits, we should expect to test 1 + log2 N commits with git bisect instead of roughly N / 2 commits with a linear search.

Once you've found the commit that introduced the regression, you can study it to find the issue. Once this is done, you use git bisect reset to put everything back on the original state before using git bisect command.

Hightoned answered 17/1, 2011 at 14:32 Comment(6)
I'm going to contrare here, this is a good explanation of bisect but really doesn't help me use it. Especially since I've managed to find a good commit and I'm on that branch now. From this position this explanation is no help at all. How do I specify the bad branch without checking it out, for exampleBatho
You can use git bisect bad <rev> [<rev>...] to mark specific revisions as bad (or good with git bisect good <rev> [<rev>...]). rev can be any revision identifier like a branch name, a tag, a commit hash (or unique prefix of commit hash), ...Hightoned
...and once you are done, you type git bisect reset to put everything back on the recent commitHydatid
One of the most awesome answers on stack. Very well articulated. I've been doing this process manually for years, just kind of picking an arbitrary halfway point between a good and bad commit, then again between that one and the good/bad one depending on if it was good/bad itself. It has always been a huge pain in the ass and I had never even heard of this git subcommand until today... hahahaAudwin
How can it be useful for PHP projects? If it can?Clannish
@Nemoden, yes, it can, basically it can be useful for any type of project. You just need to replace "make test" step with "deploy the web site and reproduce the issue"Kokaras
B
251

git bisect run automatic bisect

If you have an automated ./test script that has exit status 0 iff the test is OK, you can automatically find the bug with bisect run:

git checkout KNOWN_BAD_COMMIT
git bisect start

# Confirm that our test script is correct, and fails on the bad commit.
./test
# Should output != 0.
echo $?
# Tell Git that the current commit is bad.
git bisect bad

# Same for a known good commit in the past.
git checkout KNOWN_GOOD_COMMIT
./test
# Should output 0.
echo $?
# After this, git automatically checks out to the commit
# in the middle of KNOWN_BAD_COMMIT and KNOWN_GOOD_COMMIT.
git bisect good

# Bisect automatically all the way to the first bad or last good rev.
git bisect run ./test

# End the bisect operation and checkout to master again.
git bisect reset

This supposes of course that if the test script ./test is git tracked, that it does not disappear on some earlier commit during bisection.

I have found that very often you can get away by just copying the in-tree script out of tree, and possibly playing with PATH-like variables, and running it from there instead.

Of course, if the test infrastructure on which test depends breaks on older commits, then there is no solution, and you will have to do things manually, deciding how to test commits one by one.

I have found however that using this automation often works, and can be a huge time saver for slower tests lying in your backlog of tasks, where you can just let it run overnight, and possibly have your bug identified by next morning, it is worth the try.

More tips

Stay on the first failing commit after bisect instead of going back to master:

git bisect reset HEAD

start + initial bad and good in one go:

git bisect start KNOWN_BAD_COMMIT KNOWN_GOOD_COMMIT~

is the same as:

git checkout KNOWN_BAD_COMMIT
git bisect start
git bisect bad
git bisect good KNOWN_GOOD_COMMIT

See what has been tested so far (by manual good and bad or run):

git bisect log

Sample output:

git bisect log
git bisect start
# bad: [00b9fcdbe7e7d2579f212b51342f4d605e53253d] 9
git bisect bad 00b9fcdbe7e7d2579f212b51342f4d605e53253d
# good: [db7ec3d602db2d994fe981c0da55b7b85ca62566] 0
git bisect good db7ec3d602db2d994fe981c0da55b7b85ca62566
# good: [2461cd8ce8d3d1367ddb036c8f715c7b896397a5] 4
git bisect good 2461cd8ce8d3d1367ddb036c8f715c7b896397a5
# good: [8fbab5a3b44fd469a2da3830dac5c4c1358a87a0] 6
git bisect good 8fbab5a3b44fd469a2da3830dac5c4c1358a87a0
# bad: [dd2c05e71c246f9bcbd2fbe81deabf826c54be23] 8
git bisect bad dd2c05e71c246f9bcbd2fbe81deabf826c54be23
# bad: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05] 7
git bisect bad c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05
# first bad commit: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c0

Show good and bad refs on git log to get a better notion of time:

git log --decorate --pretty=fuller --simplify-by-decoration master

This only shows commits with a corresponding ref, which reduces the noise greatly, but does include autogenerated refs of type:

refs/bisect/good*
refs/bisect/bad*

which tell us which commits we marked as good or bad.

Consider this test repo if you want to play around with the command.

Failure is fast, success is slow

Sometimes:

  • failure happens fast, e.g. one of the first tests breaks
  • success takes a while, e.g. the broken test passes, and all other tests we don't care about follow

For those cases, e.g. supposing the failure always happens within 5 seconds, and if we are lazy to make the test more specific as we really should, we can use timeout as in:

#!/usr/bin/env bash
timeout 5 test-command
if [ $? -eq 1 ]; then
  exit 1
fi

This works since timeout exits 124 while the failure of test-command exits 1.

Magic exit statuses

git bisect run is a bit picky about exit statuses:

  • anything above 127 makes the bisection fail with something like:

    git bisect run failed:
    exit code 134 from '../test -aa' is < 0 or >= 128
    

    In particular, a C assert(0) leads to a SIGABRT and exits with status 134, very annoying.

  • 125 is magic and makes the run be skipped with git bisect skip.

    The intention of this is to help skip broken builds due to unrelated reasons.

See man git-bisect for the details.

So you might want to use something like:

#!/usr/bin/env bash
set -eu
./build
status=0
./actual-test-command || status=$?
if [ "$status" -eq 125 ] || [ "$status" -gt 127 ]; then
  status=1
fi
exit "$status"

Tested on git 2.16.1.

Blanding answered 23/3, 2014 at 15:4 Comment(8)
How does git know to keep your new test from disappearing when reverting/bisecting back to a previous/bad revision (that didn't have your newly written test)?Caesaria
@Caesaria you have a point: as far as I know, either the test must be on an external executable in PATH, or in an untracked file in the repo. In many cases this is possible: put the test on a separate file, include necessary test boilerplate with a well crafted test_script + modular test suite, and run it from the separate file while bisecting. When you fix, merge the test into main test suite.Blanding
@CiroSantilli六四事件法轮功纳米比亚威视 Sorry, I rolled back your edit below, because I feel its more explanatory for newbies and its just adding a further point to your answer (not an exact answer as yours--so mentioning that, its to add a point)Settlement
There are lots of ways to go wrong with 'git bisect run' - for example seeing how a good commit was reversed by a bad merge. It comes in, out, back in and out again and only the last "out" is bad. However, you can always do a manual 'git bisect'. Because it's a bisect, it only takes a few steps - e.g 1024 commits in 10 steps.Floorman
@Floorman you are right that it might fail. I find bisect run specially useful when the test takes a long time to finish, and I'm pretty sure that the test system won't break. This way, I can just leave it running on the background, or overnight if it takes up too many resources, without losing any brain context switch time.Blanding
A newbie's doubt, once the bug is found, do I have to fix it before doing a reset? I mean bisect at the end it puts me in the bad commit, then I do a git stash saving my new changes and then I do a reset?Fertilizer
@Fertilizer you generally want to reset and then fix, because your fix could have merge conflicts with master if you do it on an old revision.Blanding
If the good and bad bisect commits aren't showing in git log, then use the --decorate-refs option, as described in https://mcmap.net/q/64993/-how-can-i-see-git-bisect-status-of-commits-in-git-logGeostatic
P
183

TL;DR

Start:

$ git bisect start
$ git bisect bad
$ git bisect good <goodcommit>

Or

$ git bisect start
$ git bisect good
$ git bisect bad <badcommit>

Bisecting: X revisions left to test after this (roughly Y steps)

Repeat:

Issue still exists?

  • Yes: $ git bisect bad
  • No: $ git bisect good

Result:

<abcdef> is the first bad commit

When Done:

git bisect reset
Potboy answered 18/5, 2016 at 17:36 Comment(3)
Make sure you're at the root of your git repo, or you'll get a weird "You need to run this command from the toplevel of the working tree." error.Trogon
So i did git bad on my HEAD and git good on the first commit, when the error was not present. So what to do next? when bug is not present git bisect good to move to next commit?Jodhpur
@Jodhpur When bug is not present, correct, git bisect good to move to next commit.Potboy
S
50

Just to add a further point:

We can specify a file name or path to git bisect start in case we know that the bug has come from particular files. For example, Suppose we knew that the changes that caused the regression were in the com/workingDir directory then we can run git bisect start com/workingDir This means that only the commits that changed the contents of this directory will be checked, and this makes things even faster.

Also,If it’s difficult to tell if a particular commit is good or bad, you can run git bisect skip, which will ignore it. Given there are enough other commits, git bisect will use another to narrow the search instead.

Settlement answered 12/9, 2014 at 6:12 Comment(1)
I really like this. Oftentimes I already know which file a bug is in, so all I'm looking for is in which commit the bug was introduced.Periosteum
A
16

$ git bisect .. bascically a Git tool for debugging. 'Git Bisect' debugs by going through the previous commits since your last (known) working commit. It uses binary search to go through all those commits, to get to the one which introduced the regression/bug.

$ git bisect start # Starting bisect

$ git bisect bad # stating that the current commit (v1.5) has the regression/ setting 'bad' point

$ git bisect good v1.0 # mentioning it the last good working commit (without regression)

This mentioning of 'bad' and 'good' points will help git bisect (binary search) pick the middle element (commit v1.3). If the regression is there at commit v1.3, you'll set it as the new 'bad' point i.e. (Good -> v1.0 and Bad -> v1.3)

$ git bisect bad

or similarly if the commit v1.3 is bug-free you'll set it as the new 'Good point' i.e. (*Good -> v1.3 and Bad -> v1.6).

$ git bisect good
Athletic answered 27/6, 2013 at 14:37 Comment(0)
S
8

Note: the terms good and bad are not the only ones you can use for marking a commit with or without a certain property.

Git 2.7 (Q4 2015) introduced new git bisect options.

 git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
                  [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]  

With documentation adding:

Sometimes you are not looking for the commit that introduced a breakage, but rather for a commit that caused a change between some other "old" state and "new" state.

For example, you might be looking for the commit that introduced a particular fix.
Or you might be looking for the first commit in which the source-code filenames were finally all converted to your company's naming standard. Or whatever.

In such cases it can be very confusing to use the terms "good" and "bad" to refer to "the state before the change" and "the state after the change".

So instead, you can use the terms "old" and "new", respectively, in place of "good" and "bad".
(But note that you cannot mix "good" and "bad" with "old" and "new" in a single session.)

In this more general usage, you provide git bisect with a "new" commit has some property and an "old" commit that doesn't have that property.

Each time git bisect checks out a commit, you test if that commit has the property:
If it does, mark the commit as "new"; otherwise, mark it as "old".

When the bisection is done, git bisect will report which commit introduced the property.


See commit 06e6a74, commit 21b55e3, commit fe67687 (29 Jun 2015) by Matthieu Moy (moy).
See commit 21e5cfd (29 Jun 2015) by Antoine Delaite (CanardChouChinois).
(Merged by Junio C Hamano -- gitster -- in commit 22dd6eb, 05 Oct 2015)


Make sure to use Git 2.39 (Q4 2022) for git bisect run: it includes fix a regression in the bisect-helper which mistakenly treats arguments to the command given to 'git bisect run'(man) as arguments to the helper.

See commit e9011b6, commit 464ce0a, commit 58786d7 (10 Nov 2022) by Đoàn Trần Công Danh (sgn).
(Merged by Junio C Hamano -- gitster -- in commit e3d40fb, 23 Nov 2022)

bisect--helper: parse subcommand with OPT_SUBCOMMAND

Reported-by: Lukáš Doktor
Signed-off-by: Đoàn Trần Công Danh
Signed-off-by: Taylor Blau

As of it is, we're parsing subcommand with OPT_CMDMODE, which will continue to parse more options even if the command has been found.

When we're running "git bisect run"(man) with a command that expecting a --log or --no-log arguments, or one of those "--bisect-..." arguments, bisect--helper may mistakenly think those options are bisect--helper's option.

We may fix those problems by passing "--" when calling from git-bisect.sh, and skip that "--" in bisect--helper.
However, it may interfere with user's "--".

Let's parse subcommand with OPT_SUBCOMMAND since that API was born for this specific use-case.


Git 2.44.0 (Q1 2024), rc1, clarifies the option used:

See commit 841dbd4, commit 47ac5f6 (07 Feb 2024) by Junio C Hamano (gitster).
(Merged by Junio C Hamano -- gitster -- in commit 13fdf82, 12 Feb 2024)

bisect: document command line arguments for "bisect start"

Suggested-by: Matthieu Moy

The syntax commonly used for alternatives is --opt-(a|b), not --opt-{a,b}.

List bad/new and good/old consistently in this order, to be consistent with the description for "git bisect"(man) terms".
Clarify <term> to either <term-old> or <term-new> to make them consistent with the description of "git bisect" (good|bad)" subcommands.

git bisect now includes in its man page:

git bisect start [--term-(bad|new)= --term-(good|old)=]

And:

bisect: document "terms" subcommand more fully

Acked-by: Matthieu Moy

The documentation for "git bisect"(man) terms, although it did not hide any information, was a bit incomplete and forced readers to fill in the blanks to get the complete picture.

git bisect now includes in its man page:

git bisect terms [--term-(good|old) | --term-(bad|new)]

You can get just the old term with git bisect terms --term-old or git bisect terms --term-good;
git bisect terms --term-new and git bisect terms --term-bad can be used to learn how to call the commits more recent than the sought change.

Skew answered 13/8, 2018 at 18:54 Comment(0)
S
0

git bisect exit status

Ciro Santilli's 2014 answer refer to them as "magic exit status"

They will be used slightly differently with Git 2.36 (Q2 2022): a not-so-common mistake is to write a script to feed "git bisect"(man) run without making it executable, in which case all tests will exit with 126 or 127 error codes, even on revisions that are marked as good.

Try to recognize this situation and stop iteration early.

See commit 48af1fd, commit ba5bb81, commit 8efa2ac, commit 80c2e96 (18 Jan 2022) by René Scharfe (rscharfe).
(Merged by Junio C Hamano -- gitster -- in commit e828747, 06 Mar 2022)

bisect--helper: double-check run command on exit code 126 and 127

Signed-off-by: René Scharfe

When a run command cannot be executed or found, shells return exit code 126 or 127, respectively.
Valid run commands are allowed to return these codes as well to indicate bad revisions, though, for historical reasons.

This means typos can cause bogus bisect runs that go over the full distance and end up reporting invalid results.

The best solution would be to reserve exit codes 126 and 127, like 71b0251 (Bisect run: , 2007-10-26, Git v1.5.4-rc0 -- merge) (Bisect run: "skip" current commit if script exit code is 125., 2007-10-26) did for 125, and abort bisect run when we get them.
That might be inconvenient for those who relied on the documentation stating that 126 and 127 can be used for bad revisions, though.

The workaround used by this patch is to run the command on a known-good revision and abort if we still get the same error code.
This adds one step to runs with scripts that use exit codes 126 and 127, but keeps them supported, with one exception: It won't work with commands that cannot recognize the (manually marked) known-good revision as such.

Run commands that use low exit codes are unaffected.
Typos are reported after executing the missing command twice and three checkouts (the first step, the known good revision and back to the revision of the first step).

See examples.

Skew answered 10/3, 2022 at 10:44 Comment(1)
See also github.com/git/git/commit/… (Git 2.40, Q1 2023)Skew

© 2022 - 2024 — McMap. All rights reserved.