Git bisect is a powerful debugging tool that uses binary search to find the exact commit that introduced a bug. Instead of manually checking dozens or hundreds of commits, bisect intelligently narrows down the problematic commit in logarithmic time.
How Git Bisect Works
Bisect uses binary search to find the problematic commit:
- You mark a known bad commit (has the bug)
- You mark a known good commit (doesn't have the bug)
- Git checks out the middle commit
- You test and mark it as good or bad
- Git narrows the range and repeats until finding the culprit
With 1000 commits to search, you'll need only about 10 tests to find the bug!
Basic Bisect Workflow
Starting a Bisect Session
# Start bisecting
git bisect start
# Mark current commit as bad
git bisect bad
# Mark a known good commit
git bisect good v2.0.0
# or
git bisect good abc1234
Testing and Marking Commits
# Git checks out a commit for you to test
# Run your tests
npm test
# If tests pass (no bug)
git bisect good
# If tests fail (bug present)
git bisect bad
# Git automatically checks out the next commit to test
Ending the Bisect
# When found, Git reports:
# abc1234 is the first bad commit
# View the problematic commit
git show abc1234
# End bisect and return to original branch
git bisect reset
Automated Bisecting
Using a Test Script
# Create a test script that returns 0 for good, non-zero for bad
cat > test-bug.sh << 'EOF'
#!/bin/bash
# Returns 0 if good, 1 if bad
npm test specific-test.js
exit $?
EOF
chmod +x test-bug.sh
# Run automated bisect
git bisect start
git bisect bad HEAD
git bisect good v2.0.0
git bisect run ./test-bug.sh
Automated Testing Examples
# Example 1: Testing for specific output
cat > check-output.sh << 'EOF'
#!/bin/bash
output=$(node app.js --test)
if [[ "$output" == *"ERROR"* ]]; then
exit 1 # Bad commit
else
exit 0 # Good commit
fi
EOF
git bisect run ./check-output.sh
# Example 2: Performance regression
cat > check-performance.sh << 'EOF'
#!/bin/bash
time_ms=$(node performance-test.js)
if [ "$time_ms" -gt 1000 ]; then
exit 1 # Performance regression
else
exit 0 # Performance acceptable
fi
EOF
git bisect run ./check-performance.sh
Practical Bisect Scenarios
Scenario 1: Finding a Visual Bug
# Bug: Button disappeared from homepage
# Start bisect
git bisect start
git bisect bad HEAD
git bisect good tags/last-release
# Manual testing at each step
# 1. Git checks out middle commit
# 2. Run the app: npm start
# 3. Check if button exists
# 4. Mark as good or bad
# 5. Repeat until found
Scenario 2: Test Suite Failure
# A test that used to pass now fails
# Create test script
echo '#!/bin/bash
npm test -- --grep "user authentication"
' > bisect-test.sh
chmod +x bisect-test.sh
# Automated bisect
git bisect start HEAD v1.0.0
git bisect run ./bisect-test.sh
# Git finds the exact commit that broke the test
Scenario 3: Build Failure
# Build used to work, now fails
git bisect start
git bisect bad
git bisect good HEAD~50
# Automated with build command
git bisect run npm run build
Advanced Bisect Techniques
Bisecting with Skipped Commits
# Some commits can't be tested (e.g., broken builds)
git bisect start
git bisect bad HEAD
git bisect good v1.0
# Can't test current commit
git bisect skip
# Skip a range
git bisect skip v2.1..v2.3
Bisecting Merge Commits
# Include first-parent only to skip feature branch commits
git bisect start --first-parent
# Or exclude merge commits
git bisect start --no-checkout
Bisecting with Terms
# Use custom terms instead of good/bad
git bisect start --term-old=working --term-new=broken
git bisect working v1.0
git bisect broken HEAD
# At each step
git bisect working # Instead of 'good'
git bisect broken # Instead of 'bad'
Complex Bisect Examples
Finding Performance Regressions
// performance-test.js
const startTime = Date.now();
const result = expensiveOperation();
const duration = Date.now() - startTime;
if (duration > 500) {
console.error(`Performance regression: ${duration}ms`);
process.exit(1);
} else {
console.log(`Performance OK: ${duration}ms`);
process.exit(0);
}
git bisect start HEAD v1.0.0
git bisect run node performance-test.js
Finding Memory Leaks
#!/bin/bash
# memory-test.sh
# Run app and capture memory usage
node --expose-gc app.js &
PID=$!
sleep 5
# Check memory usage
MEM=$(ps -o rss= -p $PID)
kill $PID
# Fail if memory > threshold
if [ "$MEM" -gt 100000 ]; then
echo "Memory leak detected: ${MEM}KB"
exit 1
else
echo "Memory OK: ${MEM}KB"
exit 0
fi
Finding CSS Regressions
// visual-test.js
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://localhost:3000');
// Check if element has correct style
const color = await page.$eval('.button',
el => getComputedStyle(el).backgroundColor
);
await browser.close();
if (color !== 'rgb(0, 123, 255)') {
console.error('Button color regression');
process.exit(1);
}
process.exit(0);
})();
Bisect Best Practices
1. Write Reliable Test Scripts
#!/bin/bash
# Good test script practices
# Clean state before testing
git clean -fd
npm ci
# Run deterministic test
npm test specific-feature.test.js
# Capture exit code
TEST_RESULT=$?
# Cleanup
killall node 2>/dev/null
# Return result
exit $TEST_RESULT
2. Handle Build Dependencies
#!/bin/bash
# Handle changing dependencies during bisect
# Install correct dependencies for this commit
npm ci
# Build if necessary
npm run build
# Run actual test
npm test
3. Document Bisect Results
# After finding bad commit, document it
git bisect reset
git show abc1234 > bisect-result.txt
# Add to commit message when fixing
git commit -m "Fix: Resolve issue introduced in abc1234
The bug was introduced in commit abc1234 where the validation
logic was incorrectly modified. Found using git bisect.
Bisect command used:
git bisect start HEAD v2.0.0
git bisect run npm test validation.test.js"
Bisect Logging and Replay
Saving Bisect Progress
# Save bisect log
git bisect log > bisect.log
# If interrupted, restore progress
git bisect reset
git bisect start
git bisect replay bisect.log
Sharing Bisect Results
# Export bisect log for team
git bisect log > bisect-auth-bug.log
# Team member can replay
git bisect start
git bisect replay bisect-auth-bug.log
Common Pitfalls and Solutions
Non-Deterministic Tests
# Problem: Tests randomly fail
# Solution: Make tests deterministic
#!/bin/bash
# Run test multiple times
for i in {1..3}; do
npm test
if [ $? -ne 0 ]; then
exit 1
fi
done
exit 0
Environment-Specific Issues
#!/bin/bash
# Reset environment for each test
# Clear caches
rm -rf node_modules/.cache
redis-cli FLUSHALL
# Reset database
npm run db:reset
# Run test
npm test
Uncommitted Changes
# Bisect won't work with uncommitted changes
git stash
git bisect start
# ... perform bisect ...
git bisect reset
git stash pop
Integration with CI/CD
Automated Bisect in CI
# .github/workflows/bisect.yml
name: Automated Bisect
on:
workflow_dispatch:
inputs:
good_commit:
description: 'Known good commit'
required: true
test_command:
description: 'Test command to run'
required: true
jobs:
bisect:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Run bisect
run: |
git bisect start HEAD ${{ github.event.inputs.good_commit }}
git bisect run ${{ github.event.inputs.test_command }}
git bisect reset
Bisect Visualization
Creating a Bisect Graph
# Visualize bisect process
git bisect start
git bisect bad HEAD
git bisect good v1.0
# View remaining commits
git rev-list --bisect-all HEAD ^v1.0 | wc -l
# Visualize search space
git log --graph --oneline --bisect-all
Real-World Case Studies
Case 1: API Response Change
#!/bin/bash
# Finding when API response format changed
response=$(curl -s http://localhost:3000/api/users)
if echo "$response" | jq -e '.data.users' > /dev/null; then
exit 0 # Old format (good)
else
exit 1 # New format (bad)
fi
Case 2: Database Migration Issue
#!/bin/bash
# Finding problematic migration
# Reset and run migrations
npm run db:reset
npm run db:migrate
# Check if specific table exists
if psql -U user -d database -c "\dt" | grep -q "users_table"; then
exit 0
else
exit 1
fi
Conclusion
Git bisect transforms debugging from a tedious manual process into an efficient automated search. By leveraging binary search, it can find bugs in large codebases remarkably quickly.
The key to successful bisecting is having reliable, automated tests that can clearly distinguish between good and bad states. With proper test scripts and bisect techniques, you can track down even the most elusive bugs in minutes rather than hours.
Remember: the better your commit history and test suite, the more powerful git bisect becomes. Make small, atomic commits and maintain comprehensive tests to maximize bisect's effectiveness in your debugging toolkit.
Share this article
David Childs
Consulting Systems Engineer with over 10 years of experience building scalable infrastructure and helping organizations optimize their technology stack.