Ad – 728×90
⚙️ Advanced Git

git bisect & git blame – Finding Who Changed What

When a bug appears and you don't know where it came from, Git gives you two powerful investigation tools. git blame shows you which commit (and which developer) last changed each line of a file. git bisect uses binary search to find the exact commit that introduced a bug — even across hundreds of commits. Together, these commands turn debugging from guesswork into a systematic process.

⏱️ 15 min read 🎯 Intermediate 📅 Updated 2026

git blame – Line-by-Line Authorship

git blame annotates every line of a file with the commit hash, author name, and date of the last change to that line. It's your first stop when you find a confusing line of code and need context about why it was written.

Bash
# Blame an entire file
git blame src/auth.js

# Blame a specific line range (lines 10–20)
git blame -L 10,20 src/auth.js

# Blame a range using a function name (Git 2.13+)
git blame -L :validateUser src/auth.js

# Follow renames — useful when a file was renamed or moved
git blame --follow -L 15,25 src/auth.js

# Show author email instead of name
git blame -e src/auth.js

# Ignore whitespace-only changes
git blame -w src/auth.js
▶ Sample git blame output
a1b2c3d4 (Jane Dev 2026-05-12 09:14:22 +0000 10) function validateUser(user) {
9f8e7d6c (Bob Smith 2026-04-01 14:22:11 +0000 11) if (!user.email) {
a1b2c3d4 (Jane Dev 2026-05-12 09:14:22 +0000 12) throw new Error('Email required');
^3c2b1a0 (Alice Lee 2026-01-08 11:55:03 +0000 13) }
9f8e7d6c (Bob Smith 2026-04-01 14:22:11 +0000 14) return user.email.toLowerCase();
a1b2c3d4 (Jane Dev 2026-05-12 09:14:22 +0000 15) }

The ^ prefix on line 13 means that line was in the very first commit of the file. After finding a suspicious commit hash, dig deeper:

Bash
# Inspect the full commit to understand the change in context
git show 9f8e7d6c
Ad – 336×280

git bisect – Binary Search Through History

When you know a bug didn't exist in version v1.0.0 but exists now (after 200 commits), manually checking each commit would take forever. git bisect uses binary search — it cuts the search space in half with each test, finding the bad commit in just log₂(n) steps. For 200 commits, that's only 8 checks.

ℹ️
The scenario

"The user dashboard was working fine three weeks ago. Now it crashes on load. There have been about 150 commits since then. Which commit broke it?" — git bisect will find it in ~8 steps.

Step-by-Step Bisect Workflow

Bash
# Step 1: Start bisect mode
git bisect start

# Step 2: Mark current state as BAD (the bug exists here)
git bisect bad

# Step 3: Mark a known GOOD commit (when the bug didn't exist)
# Use a tag, branch, or commit hash
git bisect good v1.0.0
# Bisecting: 75 revisions left to test after this (roughly 6 steps)
# [abc123] Add user profile endpoint

# Step 4: Git checks out the midpoint commit
# Test your code — does the bug exist here?
# Run your app, run tests, check manually...

# Step 5a: If the bug EXISTS at this commit:
git bisect bad

# Step 5b: If the bug does NOT exist at this commit:
git bisect good

# Git will keep halving the range and checking out the midpoint
# Repeat until Git prints:
# abc123 is the first bad commit
# commit abc123
# Author: Bob Smith <bob@example.com>
# Date: Fri May 30 11:22:00 2026 +0000
#     Refactor dashboard data fetching

# Step 6: When done, reset back to your original HEAD
git bisect reset
💡
Can't test a particular commit?

If Git checks out a commit that can't be tested (e.g., it won't compile due to an unrelated issue), mark it as git bisect skip. Git will find another commit to test nearby.

Automating Bisect with a Test Script

If you have an automated test or script that returns exit code 0 for good and non-zero for bad, you can let Git run bisect entirely automatically:

Bash
git bisect start
git bisect bad HEAD
git bisect good v1.0.0

# Fully automated: run this command after each checkout
# Exit 0 = good, non-zero = bad
git bisect run npm test

# Or a custom test script
git bisect run ./test-bug.sh

# Or a one-liner shell test
git bisect run sh -c "node -e \"require('./src/dashboard.js')\" 2>/dev/null"

# Git will run through all commits automatically and report the first bad one
git bisect reset

git blame in Your Editor

Most modern editors provide git blame functionality inline:

Editor / ToolHow to access blame
VS CodeGitLens extension → hover over any line or open "File History"
JetBrains IDEsRight-click line → Git → Annotate with Git Blame
GitHub webOpen a file → click "Blame" button (top right)
Neovim:Git blame via vim-fugitive plugin

📋 Summary

  • git blame <file> — annotates every line with its commit hash, author, and date.
  • Use -L <start>,<end> to blame a specific line range.
  • Use --follow to trace blame through file renames.
  • git bisect startgit bisect badgit bisect good <ref> — starts binary search.
  • At each checkpoint: test the code, then run git bisect good or git bisect bad.
  • Git identifies the first bad commit after ~log₂(n) steps.
  • git bisect run <script> — fully automates the search using an exit-code-based test.
  • git bisect skip — skip a commit that can't be tested.
  • Always end with git bisect reset to return to your original branch.

FAQ

git blame shows a merge commit — how do I find the real change? +

When blame shows a merge commit, the actual code change was made in one of the merged branches. Run git show <merge-commit-hash> to see which branches were merged. Then blame the individual branch commits before the merge to trace the actual author. The -C flag (git blame -C) can also detect code copied from other files, which is sometimes what a "merge commit" hides.

How do I bisect when the bug is intermittent (not 100% reproducible)? +

Intermittent bugs are harder to bisect. Options: (1) Use git bisect skip for commits where you can't determine good/bad with confidence. (2) Write a more reliable test that triggers the bug more consistently. (3) Run your test multiple times at each commit. If using git bisect run, your script can run the test N times and only exit 0 if it passes all N runs. The bisect result may be less precise but will still narrow the search significantly.

What does it mean when git bisect says "only skips left to test"? +

This happens when all remaining commits to test have been marked as skip. Git reports the boundary of the skipped range — the first bad commit is somewhere between the last known good and first known bad commit, but Git can't identify it precisely due to the skips. You'll need to inspect the commits in that range manually (using git log --oneline <good>..<bad>) and test them individually.

Is "blame" the right word? Does it actually blame developers? +

The name is a bit unfortunate — in practice, most development cultures use "git blame" as a neutral investigation tool, not to assign fault. The information it reveals is simply: "who has the most context about this line?" When you're confused by a piece of code, git blame helps you find the right person to ask (or the PR/commit to read for context). Some tools rename it to "git annotate" for a more neutral feel. The git annotate command in Git itself provides similar output.