Finding the Villain Commit in the Magento 2 Codebase Using the GIT Bisect

A few days ago I was working as usual from my home office. I was entertained working on an investigation for a non-critical bug for one of our clients. Suddenly a Slack message popped up on my screen:

“We have a critical bug happening on production for this given client that started happening after the last deployment. Could you please stop what you’re currently working on and investigate this?”

This kind of message gives me bumps. But I’m all game, babe!

The bug was that when the customer clicked on the Add to Cart button from the product detail page (PDP), nothing happened. That meant that no customers were able to add products to the cart and, therefore, place orders. I started by trying to reproduce the issue and, actually, I got to replicate it the first time I tried, however, no error messages, no warnings, and absolutely nothing on the browser console. No clue why that tiny piece of feature simply stopped working.

As I had no error messages and no clue of where I should start my debugging process from, I reflected for a bit trying to easily find a way to identify what was introduced to the code that could have made the Add To Cart feature stop working so silently.

Then a thought came to my mind! A powerful too called git bisect.

For those who never used it before and don’t know exactly what I’m talking about, git bisect is a powerful tool in Git that helps you quickly identify the commit that introduced a bug or issue in your codebase. It automates the process of narrowing down the range of commits that might be responsible for a problem.

Here’s how git bisect works:

  1. Binary Search: Git bisect uses a binary search algorithm. It starts with two known commits: one where the issue is present (bad commit) and one where the code is working fine (good commit).
  2. Marking Commits: You start by marking a commit as “good” (known to work) and another as “bad” (known to have the issue). Git will then automatically pick a commit halfway between the good and bad ones.
  3. Testing: You test this intermediate commit. If it’s good, you mark it as “good”. If it’s bad, you mark it as “bad”.
  4. Repeating the Process: Git will continue this process, narrowing down the range of commits until it finds the exact commit where the issue was introduced.

git bisect is incredibly useful for finding the exact commit that introduced a bug, especially in large codebases with many contributors and frequent commits.

I’m not gonna dig into the nitty-gritty details of the git bisect on this blog post, but if you’re interested in learning more about it, you can refer to its official documentation page.

As mentioned above, in order to use this powerful tool I’ll need two things:

  1. Obviously, I first need to tell git what is the commit that is failing and not working, git calls it a bad commit. Normally, the commit is the production branch itself, which was my case. But you have to check what’s yours when you start working with git bisect.
  2. The second thing is to find a commit where the bug doesn’t exist yet, that means that you need to travel back in time, by checking out your code in git, and make some tests to identify a commit where the bugged functionality is working without issues. You normally need to pick up a random commit from the past, revert your code to it, and make your tests, if the bug is not there, that’s the commit you can use as a good commit.

Well, having this information on hand, now it’s time to start playing with it a little bit. I’m gonna paste the exact steps I’ve taken in my investigation of this bug so you can follow it along and understand how it worked for me.

First, we start the git bisect:

╭─tiagosampaio at Tiago’s MacBook Pro in ~/cl.usl on integration✔
╰─± git bisect start
status: waiting for both good and bad commits

OK! We started the git bisect process. Now we need to let it know the good and bad commits. In my case, the bad commit was actually the integration branch itself.

╭─tiagosampaio at Tiago’s MacBook Pro in ~/cl.usl on integration✔
╰─± git bisect bad
status: waiting for good commit(s), bad commit known

Cool! Now git knows what commit has the bug and is waiting for us to tell it what was the good commit. You can check out your code to another branch, or another commit with no problems at this step. At the time you have the reference commit, git will keep going from where it was left off.

In my investigation and tests, I confirmed the commit 57d57363 was working perfectly so I used it as my good commit.

╭─tiagosampaio at Tiago’s MacBook Pro in ~/cl.usl on 57d57363✔
╰─± git bisect good 57d57363
Bisecting: 24 revisions left to test after this (roughly 5 steps)
[3e8dd357f197d24158af258031dbfcf6e21b60cc] Merged in pipeline (pull request #1111)

Well, at this point, git will revert your code to a given commit and will ask you to make some tests and check if the bug exists in this version of the code. If it exists, you run git bisect bad, otherwise, git bisect good so git will take you to the next steps. I did some tests and identified the current version was working well.

╭─tiagosampaio at Tiago’s MacBook Pro in ~/cl.usl on 3e8dd357✔
╰─± git bisect good
Bisecting: 11 revisions left to test after this (roughly 4 steps)
[c5a814857c8092f350a24236ce237d6a300cb50a] Merged in feature/XXXR01-000 (pull request #1120)

Another round of tests were done. This time it failed for me.

╭─tiagosampaio at Tiago’s MacBook Pro in ~/cl.usl on c5a81485✔
╰─± git bisect bad
Bisecting: 6 revisions left to test after this (roughly 3 steps)
[ef092380bca0183f9a4de706a4a8f72fafb99196] Initializing Klevu modifications.

One more round of tests and it was still failing.

╭─tiagosampaio at Tiago’s MacBook Pro in ~/cl.usl on ef092380✔
╰─± git bisect bad
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[0445ff9bcb27729b5e7341ed63f2ea667a69179c] Merged in feature/XXXR01-000-inventory-availability-and-status-on-pdp (pull request #1108)

Another round of tests, and guess what: failing!

╭─tiagosampaio at Tiago’s MacBook Pro in ~/cl.usl on 0445ff9b✔
╰─± git bisect bad
Bisecting: 0 revisions left to test after this (roughly 1 step)
[3bd3fb1d29b5bc5e07900fa848c4fa31d96e4588] XXXR01-000 - Adding the stock information to the product detail page (PDP).

One more time. Failing.

╭─tiagosampaio at Tiago’s MacBook Pro in ~/cl.usl on 3bd3fb1d✔
╰─± git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[d847ea83b54c4d53513a690d625ddb38ba6e986b] XXXR01-000 - Applying little changes to the existing block.

More tests and now it worked as expected so I found a good version of the code!

╭─tiagosampaio at Tiago’s MacBook Pro in ~/cl.usl on d847ea83✔
╰─± git bisect good
3bd3fb1d29b5bc5e07900fa848c4fa31d96e4588 is the first bad commit
commit 3bd3fb1d29b5bc5e07900fa848c4fa31d96e4588
Author: Tiago Sampaio <[email protected]>
Date:   Thu Aug 31 17:47:07 2023 -0300

    XXXR01-000 - Adding the stock information to the product detail page (PDP).

 .../view/frontend/layout/catalog_product_view.xml  | 27 ++++--
 .../view/frontend/web/css/source/_module.less      | 98 ++++++++++++++++++++++
 .../web/css/source/extend/_listing.less            | 41 ---------
 3 files changed, 117 insertions(+), 49 deletions(-)
 create mode 100644 app/code/MyCompany/MyIntegration/view/frontend/web/css/source/_module.less

Now that I have the commit, I can reset my git bisect session:

git bisect reset
Switched to branch 'integration'
Your branch is up to date with 'origin/integration'.

Now, after a couple of rounds of tests, git was able to narrow down the exact commit that introduced the bug to the code (3bd3fb1d29b5bc5e07900fa848c4fa31d96e4588) and I was able to apply a proper fix for it. I’m not gonna dive into what was the fix because it’s out of context for this blog post, but I would like to point out that if you’re still not using git bisect in your debugging process, you definitely should do it.

By using git bisect, identifying the commit with the error was easy and painless, it pointed out to me exactly which modifications were done to the code that made the Add To Cart stop working.

If you’re interested in knowing more about how git bisect works under the hood, keep on reading this post.

Git bisect operates on a binary search algorithm, which is a divide-and-conquer strategy. Here’s how it works under the hood:

  1. Initialization:
    • You start by telling Git which commits are known to be “good” and “bad”. Git requires at least one “good” commit and one “bad” commit to begin the bisecting process.
    • Git creates a range of commits between the “good” and “bad” commits.
  2. Binary Search:
    • Git picks a commit in the middle of this range and checks it out. This commit serves as a test case.
    • If the commit is good, it means the bug was introduced after this commit, so Git ignores the commits before it. If it’s bad, Git ignores the commits after it.
  3. Repeat:
    • Git then narrows down the range to the middle of the remaining commits and checks out that commit.
    • This process continues, narrowing down the range by half each time, until Git identifies the exact commit that introduced the bug.
  4. Automated Process:
    • Git handles most of this process automatically. It identifies commits, checks them out, and prompts you to test them.
    • Based on your feedback (whether the commit is good or bad), Git continues the search.
  5. Termination:
    • Eventually, Git will pinpoint the exact commit where the bug was introduced. It will inform you of this commit.
  6. Result:
    • At the end of the process, Git leaves you in a “bisecting” state, which you can exit using git bisect reset once you’ve found the problematic commit.

Here’s a simplified example:

Suppose you have 64 commits. You tell Git that the bug is present in the latest commit (commit 64) and absent in commit 1.

  1. Git checks out commit 32. You test it and find the bug.
  2. Git checks out commit 16. You test it and find the bug.
  3. Git checks out commit 8. You test it and find the bug.
  4. Git checks out commit 4. You test it and find the bug.
  5. Git checks out commit 2. You test it and find the bug.
  6. Git checks out commit 3. You test it and find that the bug is fixed.
  7. Git now knows that the bug was introduced between commit 2 and commit 3.

This process is incredibly efficient, often requiring only a few steps to identify the problematic commit, making it an invaluable tool for debugging.

This powerful tool is so cool that even Batman would like to have it on his yellow belt if he were a software developer.

I hope this blog post finds you well and its content helps you as much as it helped me.

If you wish, you can buy me a coffee later. 🙂

-Tiago

Leave a comment