4.11 Debugging With git bisect#

Estimated time to complete this notebook: 5 minutes

You can use

git bisect

to find out which commit caused a bug.

An example repository#

In a nice open source example, I found an arbitrary exemplar on github

import os

top_dir = os.getcwd()
git_dir = os.path.join(top_dir, "learning_git")
os.chdir(git_dir)
%%bash
rm -rf bisectdemo
git clone https://github.com/shawnsi/bisectdemo.git
Cloning into 'bisectdemo'...
bisect_dir = os.path.join(git_dir, "bisectdemo")
os.chdir(bisect_dir)
%%bash
python squares.py 2 # 4
4

This has been set up to break itself at a random commit, and leave you to use bisect to work out where it has broken:

%%bash
./breakme.sh > break_output
error: branch 'buggy' not found
Switched to a new branch 'buggy'

Which will make a bunch of commits, of which one is broken, and leave you in the broken final state

python squares.py 2 # Error message
  Cell In[6], line 1
    python squares.py 2 # Error message
           ^
SyntaxError: invalid syntax

Bisecting manually#

%%bash
git bisect start
git bisect bad # We know the current state is broken
git checkout master
git bisect good # We know the master branch state is OK
status: waiting for both good and bad commits
status: waiting for good commit(s), bad commit known
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
Bisecting: 500 revisions left to test after this (roughly 9 steps)
[c615679add4e832dd60f81dbc457bfb8a3900e4b] Comment 499

Bisect needs one known good and one known bad commit to get started

Solving Manually#

python squares.py 2 # 4
git bisect good
python squares.py 2 # 4
git bisect good
python squares.py 2 # 4
git bisect good
python squares.py 2 # Crash
git bisect bad
python squares.py 2 # Crash
git bisect bad
python squares.py 2 # Crash
git bisect bad
python squares.py 2 #Crash
git bisect bad
python squares.py 2 # 4
git bisect good
python squares.py 2 # 4
git bisect good
python squares.py 2 # 4
git bisect good

And eventually:

git bisect good
    Bisecting: 0 revisions left to test after this (roughly 0 steps)

python squares.py 2
    4

git bisect good
2777975a2334c2396ccb9faf98ab149824ec465b is the first bad commit
commit 2777975a2334c2396ccb9faf98ab149824ec465b
Author: Shawn Siefkas <shawn.siefkas@meredith.com>
Date:   Thu Nov 14 09:23:55 2013 -0600

    Breaking argument type
git bisect end

Solving automatically#

If we have an appropriate unit test, we can do all this automatically:

%%bash
git bisect start
git bisect bad HEAD # We know the current state is broken
git bisect good master # We know master is good
git bisect run python squares.py 2
Previous HEAD position was c615679 Comment 499
Switched to branch 'buggy'
status: waiting for both good and bad commits
status: waiting for good commit(s), bad commit known
Bisecting: 500 revisions left to test after this (roughly 9 steps)
[c615679add4e832dd60f81dbc457bfb8a3900e4b] Comment 499
running 'python' 'squares.py' '2'
Traceback (most recent call last):
  File "squares.py", line 9, in <module>
    print(integer**2)
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Bisecting: 249 revisions left to test after this (roughly 8 steps)
[99982aa79d7f441338c8fffc3ecdb4daa173389c] Comment 249
running 'python' 'squares.py' '2'
Traceback (most recent call last):
  File "squares.py", line 9, in <module>
    print(integer**2)
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Bisecting: 124 revisions left to test after this (roughly 7 steps)
[19f08a32b307b1eda64fd6c5deed3780fe2f16ba] Comment 124
running 'python' 'squares.py' '2'
Traceback (most recent call last):
  File "squares.py", line 9, in <module>
    print(integer**2)
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Bisecting: 62 revisions left to test after this (roughly 6 steps)
[e72210dde8b5801e4f088bf45fcae72c7ae13fd6] Comment 61
running 'python' 'squares.py' '2'
Traceback (most recent call last):
  File "squares.py", line 9, in <module>
    print(integer**2)
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Bisecting: 30 revisions left to test after this (roughly 5 steps)
[feb14756a9a23b16988fa09a1970b26230de4b78] Comment 30
running 'python' 'squares.py' '2'
Traceback (most recent call last):
  File "squares.py", line 9, in <module>
    print(integer**2)
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Bisecting: 15 revisions left to test after this (roughly 4 steps)
[b60fe57e25eb5702a5777fc055c377810a505ccf] Comment 15
running 'python' 'squares.py' '2'
4
Bisecting: 7 revisions left to test after this (roughly 3 steps)
[4a5760ea0c880015108a1fd52fba748cf9a01ebc] Comment 23
running 'python' 'squares.py' '2'
4
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[f0afe2bccb2b626e4014b56bfd62d71a8c1c2739] Comment 26
running 'python' 'squares.py' '2'
Traceback (most recent call last):
  File "squares.py", line 9, in <module>
    print(integer**2)
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Bisecting: 1 revision left to test after this (roughly 1 step)
[cde848bb4fd333464a801344c5e664315deaaa93] Breaking argument type
running 'python' 'squares.py' '2'
Traceback (most recent call last):
  File "squares.py", line 9, in <module>
    print(integer**2)
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[a90309fa758b2fc8e1b6c994a41ebf0a4af180ca] Comment 24
running 'python' 'squares.py' '2'
4
cde848bb4fd333464a801344c5e664315deaaa93 is the first bad commit
commit cde848bb4fd333464a801344c5e664315deaaa93
Author: Shawn Siefkas <shawn.siefkas@meredith.com>
Date:   Thu Nov 14 09:23:55 2013 -0600

    Breaking argument type

 squares.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
bisect found first bad commit

Boom!