9

Ok, here's the case:

A couple of years ago, multiple changes was made to multiple files in our codebase, and was committed all at once. Somewhere in those changes hides a bug. Using git bisect, I was quickly able to track down the culprit commit, but the amount of changes in that commit made me a little less enthusiastic.

Finding a bad commit is a breeze with git bisect, but once found, what is the best way to track down the single change that made it all go boom? Revert the affected files to their previous version, one by one?

Johan Fredrik Varen
  • 3,796
  • 7
  • 32
  • 42
  • 2
    As you've already pointed out, there's nothing more that source control can do for you. Are you actually just asking "How do I debug my code?" without showing us code, or even telling us which language you're writing in? – Gareth Oct 17 '13 at 13:09
  • 2
    This is a PERFECT example of why commits should be small, atomic, operations with clear commit messages. – Jason Whitehorn Oct 17 '13 at 13:12
  • 2
    @Gareth: No, I'm not asking how to debug code, so the actual code here is irrelevant. I'm asking for the best way to further narrow down a bad commit, by eliminating its good changes in much the same way git bisect does. The answers given below I think shows very well that source control can in fact do more. – Johan Fredrik Varen Oct 18 '13 at 07:50
  • @JohanFredrikVaren: You might be able to create a minimal failing patch using Delta (http://delta.tigris.org/). You'd need to test if applying the patch lets you pass some tests before the commit and still fail for the bug you found now. Minimizing that patch should point you to the faulty changes. – Benjamin Bannier Oct 18 '13 at 09:09

3 Answers3

4

This can be pretty tedious unless you understand very well all the changes that occured within that large commit.

Typically a very large (bad large) commit involves many different changes. What you need to do is isolate all those changes conceptually and rewrite different commits.

I suggest breaking down changes according to these 4 criteria:

[NEW] involves all code related to a singly identified technical-level feature (as opposed to user-level which may involve more than one tech-level feature)

[RFG] any behavioral invariant changes. Preserves executed behavior and APIs (interfaces)

[CHG] implementation of anything that represents a change of specifications/requirements

[FIX] any changes that may change behavior to make it conformant to the intentions behind the codeing.

then, git-wise here is what you need to do:

  git checkout <bad commit SHA1> -b CULPRIT

this will create a "CULPRIT" branch. I always keep this one as a reference as you may need to perform many tedious itteration of the following steps. As a sidenote, keeping partial references along the way help (as branches or tags).

  git reset HEAD^ --mixed

this will undo the commit as if all the changes from the commit were applied as patch of unstaged changes to the previous commit. Then using

  git add --patch

you can change a subset of those changes. Do not hesitate to use the [s]plit option to singly pick the changes line by line. Sometimes, you can't avoid an edition to hand pick the changes you want. And restage as multiple commits broken down in the NEW,RFG,CHG,FIX scheme I suggest above and rewrite as many commits as you want.

Be mindful of:

  • staging a new commit that do not compile
  • staging a new commit that produce "trivial" runtime error (such as segfault for example)
  • sub commits that needs to be combined to make the thing work

...because the goal is to make bisect work. Furthermore, ensure your new commits are the same as the old commit by git diff the new HEAD commit againts CULPRIT to ensure you did not introduced further changes.

This is a pain at first and requires a lot of practice but once you become good enough at doing this, you will become a debugging god to be worshipped by your whole team and then can spread the gospel of small commits in the form of NEW,CHG,RFG and FIX.

Sebastien
  • 1,439
  • 14
  • 27
3

You can always create a branch off that commit, split the commit into smaller commits, and proceed with bisection from there.

Also, remember that git bisect is just a tool for narrowing down the change that leads to the problem. It doesn't exonerate you from having to analyze the problem, locate the bug, and come up with a fix.

Community
  • 1
  • 1
user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • Thanks. I tried out the "split the commit into smaller commits" solution, but lost my way somewhere in there. Git noobs like myself really need it spelled out for them. – Johan Fredrik Varen Oct 18 '13 at 08:29
  • @JohanFredrikVaren There are numerous tutorials for splitting a git commit out there, some of them spelling out the steps in quite some detail - just google them. I simply linked to the one that seemed like a good match. – user4815162342 Oct 18 '13 at 08:55
1

Since there were only a few files in the bad commit, I chose a somewhat naive way to go about it. Drawing from the accepted answer, this is what I ended up doing:

1) Prepare an unstaged version of the commit:

git checkout <bad commit SHA1> -b CULPRIT
git reset HEAD^ --mixed

2) Stage another file:

git add <filename>

3) Stash the remaining files, keeping the staged files intact:

git stash --keep-index

4) If the bug still doesn't show:

git stash pop

5) Repeat steps 2, 3 and 4 until the bug appears, meaning the file previously staged is the culprit

Since there were several unrelated changes in the culprit file, I chose to continue the elimination process, but this time down on the code line (or "hunk") level:

6) Stage individual hunks of code in the culprit file:

add --patch <culprit filename>

7) Repeat steps 3 and 4 until the bug appears, meaning the hunk previously staged is the culprit

I assume I could have done this a bit more elegantly and used git bisect, but since there were only 7 affected files in the bad commit, I resolved to do the elimination manually. Once the bad code was found, I deleted the local CULPRIT branch.

Johan Fredrik Varen
  • 3,796
  • 7
  • 32
  • 42