Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use git bisect?

Tags:

git

git-bisect

I have read some articles saying that git bisect is awesome. However, I'm not a native speaker and I can't understand why it's awesome.

Could someone please demonstrate with some code sample:

  1. How to use it?
  2. Is it just like svn blame?
like image 285
IAdapter Avatar asked Jan 17 '11 12:01

IAdapter


People also ask

What is git bisect and how do you use it?

The git bisect command is used to discover the commit that has introduced a bug in the code. It helps track down the commit where the code works and the commit where it does not, hence, tracking down the commit that introduced the bug into the code.

What is git bisect how can you use it to determine the source of a regression bug?

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.

How do I remove a bad commit in git bisect?

Use git log to check the previous commits. Use Git Bisect to find the commit in which line 2 is changed from 'b = 20' to 'b = 0.00000'. Remove the bad commit by using Git Revert. Leave the commit message as is.


Video Answer


2 Answers

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 grealy, 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 withing 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.


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 is 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.

like image 194
Sylvain Defresne Avatar answered Sep 16 '22 21:09

Sylvain Defresne