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.
- 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:
#!/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 -emkdir shop-cart && cd shop-cartgit 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 calculationsfunction subtotal(items) { return items.reduce((sum, item) => sum + item.price * item.qty, 0);}EOFcommit "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.EOFcommit "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);}EOFcommit "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;}EOFcommit "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 40EOFcommit "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% GSTconst 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;}EOFcommit "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;}EOFcommit "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 40EOFcommit "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);}EOFcommit "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.jsEOFcommit "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.shcd shop-cartgit log --onelineClick Run to see what this code prints.
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: $?"Click Run to see what this code prints.
grep -n TAX_RATE cart.jsClick 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.jsClick 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"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.jsClick Run to see what this code prints.
git show --stat c224f83Click 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.shgit bisect startgit bisect badgit bisect good v1.0Click Run to see what this code prints.
git bisect run bash ../check.shClick 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 resetClick 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 diffClick Run to see what this code prints.
bash check.sh; echo "exit code: $?"git commit -am "Fix GST rate typo (0.81 -> 0.18)"Click Run to see what this code prints.
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.