LearnAI ToolsCareerPractice BuildsPlayDevelopmentContact
Git & GitHubIntermediate~1 hour

Git History Detective

Generate a repository with a hidden bug, investigate it with git log, git log -S and git blame, let git bisect find the guilty commit automatically, then fix it.

git loggit blamegit bisectBug Fixing

Overview

Customers of a small online shop are being charged far too much tax. Somewhere in ten commits by three developers, the GST rate changed from 18% to 81%. Your job: find exactly which commit did it, who made it and why — using only Git — and then fix it.

A setup script builds the repository for you with fixed authors and dates, so the commit IDs on your screen match this tutorial exactly.

What You'll Practise
  • Filtering history by file and by content with git log -- <file> and git log -S.
  • Finding the commit behind a single line with git blame.
  • Automating a binary search through history with git bisect run.
  • Fixing a bug with a small, focused commit.

Prerequisites

  • git log, git show and git blame — lesson 4.
  • git bisect — lesson 16.
  • A Bash terminal: Git Bash on Windows, or Terminal on macOS and Linux.

Step 1: Create the Repository

Save this script as create-shop-repo.sh in an empty folder. It creates a shop-cart repository with ten commits and a v1.0 tag:

create-shop-repo.sh
#!/usr/bin/env bash
# create-shop-repo.sh: builds a practice repository with a hidden bug.
# Authors and dates are fixed, so your commit IDs match the tutorial.
set -e
mkdir shop-cart && cd shop-cart
git init -q -b main
commit() { # usage: commit "Author Name" email "YYYY-MM-DD HH:MM" "message"
git add -A
GIT_AUTHOR_NAME="$1" GIT_AUTHOR_EMAIL="$2" GIT_AUTHOR_DATE="$3 +0530" \
GIT_COMMITTER_NAME="$1" GIT_COMMITTER_EMAIL="$2" GIT_COMMITTER_DATE="$3 +0530" \
git -c commit.gpgsign=false commit -q -m "$4"
}
cat > cart.js <<'EOF'
// Shopping cart calculations
function subtotal(items) {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
EOF
commit "Ravi Kumar" ravi@example.com "2026-09-01 10:00" "Create cart module"
cat > README.md <<'EOF'
# Shop Cart
Price calculations for our online shop.
EOF
commit "Meera Iyer" meera@example.com "2026-09-01 11:30" "Add README"
cat >> cart.js <<'EOF'
const TAX_RATE = 0.18; // 18% GST
function total(items) {
return subtotal(items) * (1 + TAX_RATE);
}
EOF
commit "Ravi Kumar" ravi@example.com "2026-09-02 09:15" "Add total with GST"
cat >> cart.js <<'EOF'
function shipping(items) {
return subtotal(items) >= 500 ? 0 : 40;
}
EOF
commit "Arjun Mehta" arjun@example.com "2026-09-02 14:00" "Add free shipping over 500"
cat >> README.md <<'EOF'
- subtotal(items): price x quantity for every item
- total(items): subtotal plus 18% GST
- shipping(items): free from 500, otherwise 40
EOF
commit "Meera Iyer" meera@example.com "2026-09-03 10:45" "Document cart functions"
git tag v1.0
cat > cart.js <<'EOF'
// Shopping cart calculations
// ---- Settings ----
const TAX_RATE = 0.81; // 18% GST
const FREE_SHIPPING_FROM = 500;
const SHIPPING_FEE = 40;
function subtotal(items) {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
function total(items) {
return subtotal(items) * (1 + TAX_RATE);
}
function shipping(items) {
return subtotal(items) >= FREE_SHIPPING_FROM ? 0 : SHIPPING_FEE;
}
EOF
commit "Arjun Mehta" arjun@example.com "2026-09-04 16:20" "Move settings to the top of cart.js"
cat >> cart.js <<'EOF'
function applyDiscount(amount, code) {
return code === 'WELCOME10' ? amount * 0.9 : amount;
}
EOF
commit "Ravi Kumar" ravi@example.com "2026-09-05 11:00" "Add discount codes"
cat > README.md <<'EOF'
# Shop Cart
Price calculations and discounts for our online shop.
- subtotal(items): price x quantity for every item
- total(items): subtotal plus 18% GST
- shipping(items): free from 500, otherwise 40
EOF
commit "Meera Iyer" meera@example.com "2026-09-05 15:30" "Mention discounts in README"
cat >> cart.js <<'EOF'
function itemCount(items) {
return items.reduce((count, item) => count + item.qty, 0);
}
EOF
commit "Arjun Mehta" arjun@example.com "2026-09-06 12:10" "Add item count helper"
cat > check.sh <<'EOF'
#!/usr/bin/env bash
# Exits 0 (good) if the GST rate is 18%, 1 (bad) otherwise.
grep -q "const TAX_RATE = 0.18;" cart.js
EOF
commit "Ravi Kumar" ravi@example.com "2026-09-07 09:40" "Add GST rate check script"
echo "Done: the shop-cart repository is ready."
bash create-shop-repo.sh
cd shop-cart
git log --oneline
Output

Click Run to see what this code prints.

Different Commit IDs?

If your IDs differ, you probably have commit signing or a commit hook configured, or edited the script. Everything still works — just use the IDs on your own screen.

Step 2: Confirm the Bug

The team’s check script exits with 0 when the GST rate is correct and 1 when it is not. In a real project this would be your test suite:

bash check.sh; echo "exit code: $?"
Output

Click Run to see what this code prints.

grep -n TAX_RATE cart.js
Output

Click Run to see what this code prints.

There it is: 0.81 with a comment that still says 18%. The question is how it got there.

Step 3: Search the History

Start broad — which commits touched cart.js?

git log --oneline -- cart.js
Output

Click Run to see what this code prints.

Six suspects. git log -S (the “pickaxe”) narrows it down instantly: it lists only commits that changed how many times a piece of text appears:

git log --oneline -S "0.81"
Output

Click Run to see what this code prints.

Step 4: Ask git blame

git blame -L 4,4 shows who last changed line 4 only:

git blame -L 4,4 cart.js
Output

Click Run to see what this code prints.

git show --stat c224f83
Output

Click Run to see what this code prints.

A refactoring commit — “Move settings to the top of cart.js” — retyped the constant while moving it and swapped two digits. Nobody meant to change the tax rate, which is why the commit message gives no hint. This is very common: bugs often hide in commits that claim to change nothing.

Step 5: Let git bisect Find It

Searching worked because we knew what text to look for. Often you only know that something is broken now and worked before. git bisect finds the first bad commit by binary search, and git bisect run automates it with a check script.

One catch: check.sh was added in the latest commit, so it does not exist in the older commits bisect will check out. Copy it outside the repository first:

cp check.sh ../check.sh
git bisect start
git bisect bad
git bisect good v1.0
Output

Click Run to see what this code prints.

git bisect run bash ../check.sh
Output

Click Run to see what this code prints.

Git tested two commits and named the same culprit, without you looking at a single line of code. With 1,000 commits, bisect needs only about ten tests. Always finish with git bisect reset to return to your branch:

git bisect reset
Output

Click Run to see what this code prints.

Step 6: Fix the Bug

Reverting the whole refactoring commit would also undo its useful changes (the FREE_SHIPPING_FROM and SHIPPING_FEE settings) and conflict with later commits. A one-line fix is better. Change 0.81 back to 0.18 in cart.js, then check:

git diff
Output

Click Run to see what this code prints.

bash check.sh; echo "exit code: $?"
git commit -am "Fix GST rate typo (0.81 -> 0.18)"
Output

Click Run to see what this code prints.

Write the Why

In a real project, add a body to the commit message that names the commit which introduced the bug (c224f83). Future readers of git blame will thank you.

Extend This Project

  • Run the bisect manually, typing git bisect good or git bisect bad yourself after running check.sh at each step.
  • Try git revert c224f83 instead of the one-line fix, and read the conflict it causes.
  • Use git log -p --follow -- cart.js to read the full history of the file, change by change.
  • Add a second hidden bug to the script (for example in shipping) and challenge a friend to find it.

Summary

You investigated a real regression the way experienced developers do: narrowing the history by file and content, tracing a line with blame, and letting bisect search automatically — then fixing it with a small, well-described commit.