XClose

COMP0233: Research Software Engineering With Python

Home
Menu

Debugging With Git Bisect

NOTE: using bash/git commands is not fully supported on jupyterlite yet (due to single thread/process restriction), and the cells below might error out on the browser (jupyterlite) version of this notebook

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

In [1]:
import os
top_dir = os.getcwd()
git_dir = os.path.join(top_dir, 'learning_git')
os.chdir(git_dir)
In [2]:
%%bash
rm -rf bisectdemo
git clone https://github.com/UCL-ARC-RSEing-with-Python/bisectdemo.git
Cloning into 'bisectdemo'...
In [3]:
bisect_dir=os.path.join(git_dir,'bisectdemo')
os.chdir(bisect_dir)
In [4]:
%%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:

In [5]:
%%bash
./breakme.sh > break_output
Switched to a new branch 'buggy'
fatal: unable to read 77b5cd0660cb0d3181ceca7b448ba3f749da615f

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

In [6]:
%%bash
python squares.py 2 # Error message
Traceback (most recent call last):
  File "/home/runner/work/rsd-engineeringcourse/rsd-engineeringco
urse/ch00git/learning_git/bisectdemo/squares.py", line 9, in <module>
    print(integer**2)
        
  ~~~~~~~^^~
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
---------------------------------------------------------------------------
CalledProcessError                        Traceback (most recent call last)
Cell In[6], line 1
----> 1 get_ipython().run_cell_magic('bash', '', 'python squares.py 2 #\xa0Error message\n')

CalledProcessError: Command 'b'python squares.py 2 #\xc2\xa0Error message\n'' returned non-zero exit status 1.

Bisecting manually

In [7]:
%%bash
git bisect start
git bisect bad # We know the current state is broken
git switch main
git bisect good # We know the main branch state is OK
status: waiting for both good and bad commits
status: waiting for good commit(s), bad commit known
warning: you are switching branch while bisecting
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
Bisecting: 500 revisions left to test after this (roughly 9 steps)
[5b87c8d9b997b0f9528cdf1324414e6cc80d0220] Comment 500

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

Stop the bisect process with:

git bisect reset

Solving automatically

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

(NOTE: You don't need to redirect the stderr and stdout (with &>) of git bisect run to a file when running these commands outside a jupyter notebook (i.e., on a shell). This is done here so the errors appears with the right commits)

In [8]:
%%bash
git bisect start
git bisect bad HEAD # We know the current state is broken
git bisect good main # We know main is good
git bisect run python squares.py 2 &> gitbisect.out
cat gitbisect.out
Previous HEAD position was 5b87c8d Comment 500
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)
[5b87c8d9b997b0f9528cdf1324414e6cc80d0220] Comment 500
running 'python' 'squares.py' '2'
4
Bisecting: 250 revisions left to test after this (roughly 8 step
s)
[1309605f45574125502350ae490c2b17a6d673eb] Comment 749
running 'python' 'squares.py' '2'
Tracebac
k (most recent call last):
  File "/home/runner/work/rsd-engineeringcourse/rsd-engineeringcourse/ch0
0git/learning_git/bisectdemo/squares.py", line 9, in <module>
    print(integer**2)
          ~~~~~~
~^^~
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Bisecting: 124 revision
s left to test after this (roughly 7 steps)
[126b5d1b518f4ce3e1056822e02734471c2ee11b] Comment 625
r
unning 'python' 'squares.py' '2'
4
Bisecting: 62 revisions left to test after this (roughly 6 steps)
[c7eaec1a70c6dd546238b78935645aae7e0b68d8] Comment 686
running 'python' 'squares.py' '2'
Traceback 
(most recent call last):
  File "/home/runner/work/rsd-engineeringcourse/rsd-engineeringcourse/ch00g
it/learning_git/bisectdemo/squares.py", line 9, in <module>
    print(integer**2)
          ~~~~~~~^
^~
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Bisecting: 30 revisions l
eft to test after this (roughly 5 steps)
[7ebb0c1bfcb9c0641647c1c65a3d9c42cc9d7bc1] Comment 656
runn
ing 'python' 'squares.py' '2'
4
Bisecting: 15 revisions left to test after this (roughly 4 steps)
[9
13cc0091fedb27d2bbdc980ebf0a094a923f1ff] Comment 671
running 'python' 'squares.py' '2'
4
Bisecting: 
7 revisions left to test after this (roughly 3 steps)
[524faa9c7c01ad1a7bed69b4e6825fe1c31aa965] Com
ment 679
running 'python' 'squares.py' '2'
4
Bisecting: 3 revisions left to test after this (roughly
 2 steps)
[2ee4439cc54da867931745b8de75e93467ff5f5d] Comment 682
running 'python' 'squares.py' '2'
T
raceback (most recent call last):
  File "/home/runner/work/rsd-engineeringcourse/rsd-engineeringcou
rse/ch00git/learning_git/bisectdemo/squares.py", line 9, in <module>
    print(integer**2)
         
 ~~~~~~~^^~
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Bisecting: 1 rev
ision left to test after this (roughly 1 step)
[75acb55921d3643e9e032187b5beac9393335b95] Comment 68
1
running 'python' 'squares.py' '2'
4
Bisecting: 0 revisions left to test after this (roughly 0 step
s)
[8599d87c5382f203f7685d1a68ba8ec179a93b05] Breaking argument type
running 'python' 'squares.py' '
2'
Traceback (most recent call last):
  File "/home/runner/work/rsd-engineeringcourse/rsd-engineerin
gcourse/ch00git/learning_git/bisectdemo/squares.py", line 9, in <module>
    print(integer**2)
     
     ~~~~~~~^^~
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
8599d87c5382
f203f7685d1a68ba8ec179a93b05 is the first bad commit
commit 8599d87c5382f203f7685d1a68ba8ec179a93b05
Author: Shawn Siefkas <shawn.siefkas@meredith.com>
Date:   Thu Nov 14 09:23:55 2013 -0600

    Brea
king argument type

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

Boom!