Given a working commit and its next buggy commit, how to apply each chunk gradually to introduce a bug?

63 views Asked by At

In commit A I have a working feature 1 and a buggy feature 2. Several commits later, at commit B I fix the bug in feature 2, but feature 1 becomes buggy now. My strategy is to back at commit A, gradually add changes in each files and to see what breaks feature 1. I know that I can diff and copy-paste each chunk and each new file, but is there a way to automate this process? I'm thinking about something being similar to how the debugger works: it stops at each breaking point so that I can examine the result.

git bisect focuses on finding the first commit introduce the bug. The previous commit therefore is a working commit. What I want is to apply each chunk from the buggy one to the previous one automatically.

Is that possible?

2

There are 2 answers

1
Jonatan Ivanov On

git bisect: https://git-scm.com/docs/git-bisect

This command uses a binary search algorithm to find which commit in your project’s history introduced a bug. You use it by first telling it a "bad" commit that is known to contain the bug, and a "good" commit that is known to be before the bug was introduced. Then git bisect picks a commit between those two endpoints and asks you whether the selected commit is "good" or "bad". It continues narrowing down the range until it finds the exact commit that introduced the change.

0
hlovdal On

There is no out of the box way of doing this, however the following is my take on it.


The first principle is that you do not strictly have to run bisect on a "pristine", unmodified branch. Say you have discovered a problem that occurs on branch main and the last known good commit is tagged last-known-good-commit.

Instead of the straight forward approach

git checkout main
git bisect start
git bisect bad
git bisect good last-known-good-commit
...

you might do

git checkout -b main.bisect main
git rebase --interactive --rebase-merges last-known-good-commit
# Inject a commit at the beginning that adds debug prints, disables unrelated unit tests, etc
git bisect start
git bisect bad
git bisect good last-known-good-commit
...

So for instance if the bug you are investigating is CSS related, then running database or backend API tests is just wasted time and effort so you might disable those at the very beginning. Or change the default debug log level, etc.

Yes, you are then technically debugging something different than main, but very seldom are you completely blind to what are the problem is related to and you can with great confidence do changes that do not affect that.


Now for your particular case your main branch looks something like

M1 -> M2 -> A -> M3 -> M4 -> B -> M5 -> M6

Since A contains more than one thing you can do an interactive rebase and split A into at least A1 + A2 (and possibly A3 + A4 + ...).

M1 -> M2 -> A1 -> A2 -> A3 -> M3 -> M4 -> B -> M5 -> M6

Say that A1 is changes unrelated to feature 1 and feature2 (spelling corrections, rename a function, etc), A2 contains feature1 and A3 contains feature2.

Now you can experiment with things like

  • Changing order of commits. Since B fixes buggy feature2 in commit A3, does it still break on B if you move it backwards:
M1 -> M2 -> A1 -> A2 -> A3 -> B -> M3 -> M4 -> B -> M5 -> M6
  • Are you able to introduce feature2 before feature1 like
M1 -> M2 -> A3 -> B -> A1 -> A2 -> M3 -> M4 -> B -> M5 -> M6
  • Maybe B can and should be split up into multiple commits as well.

  • etc


As for automating splitting up existing commits into conceptually sound and consistent commits this is impossible.

The best thing is to start with having small, independent commit in the first place. In lieu of that, you have to split manually while bisecting.

What I do when bisecting and arriving at a too big commit that has a problem is to do an interactive rebase, edit the commit, then run git reset HEAD^ to discard the commit but not the content, followed by git add -p; git commit -m partX in as small commits as possible for however many parts there are until there are no uncommited changes left and then git rebase --continue. Then re-running bisect over these new, smaller commits.