Git - Bisect & Debugging
Advanced
Git bisect is a powerful debugging tool that uses binary search to efficiently find the exact commit that introduced a bug. Master bisect to solve complex issues and become a debugging expert.
What is Git Bisect?
Git bisect performs a binary search through your commit history to find the commit that introduced a bug:
# Binary search visualization
Commits: A---B---C---D---E---F---G---H (bug here)
✓ ✓ ✓ ✓ ✗ ✗ ✗ ✗
Step 1: Test middle (E) → Bad
Step 2: Test middle of A-D (C) → Good
Step 3: Test middle of C-E (D) → Good
Step 4: E is the first bad commit!
Benefits of using bisect:
- Efficiency: O(log n) instead of O(n) search
- Precision: Find exact problematic commit
- Automation: Can run automated tests
- Large Histories: Works well with thousands of commits
Basic Bisect Workflow
Manual Bisecting
# Start bisecting
git bisect start
# Mark current commit as bad (has the bug)
git bisect bad
# Mark a known good commit (before bug was introduced)
git bisect good v1.2.0 # or specific commit hash
# Git checks out a commit in the middle
# Test the commit, then mark it:
git bisect good # if no bug
git bisect bad # if bug exists
# Repeat until Git finds the first bad commit
# Git will show: "abc1234 is the first bad commit"
# End bisecting session
git bisect reset
# Example bisect session
$ git bisect start
$ git bisect bad
$ git bisect good v2.0
Bisecting: 12 revisions left to test after this (roughly 4 steps)
[def5678] Add user authentication feature
$ git bisect bad
Bisecting: 6 revisions left to test after this (roughly 3 steps)
[abc1234] Refactor database connection
$ git bisect good
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[ghi9012] Update user validation
# Continue until...
def5678901234567890123456789012345678901 is the first bad commit
One-Command Bisect Start
# Start bisect with good and bad commits
git bisect start HEAD v1.2.0
git bisect start <bad-commit> <good-commit>
# Start with specific range
git bisect start main feature/user-auth
Automated Bisecting
Using Test Scripts
# Create test script (test.sh)
#!/bin/bash
# Exit 0 for good, 1 for bad, 125 for skip
make clean && make
if [ $? -ne 0 ]; then
exit 125 # Skip if can't build
fi
# Run specific test
./run_test user_login_test
exit $?
# Run automated bisect
git bisect start HEAD v1.0
git bisect run ./test.sh
# Git will automatically find the first bad commit
# Automated bisect output
running ./test.sh
Bisecting: 12 revisions left to test after this (roughly 4 steps)
[def5678] Add user authentication feature
running ./test.sh
Bisecting: 6 revisions left to test after this (roughly 3 steps)
[abc1234] Refactor database connection
running ./test.sh
...
def5678901234567890123456789012345678901 is the first bad commit
Language-Specific Test Scripts
# Node.js test script
#!/bin/bash
npm install --silent
npm test -- --testNamePattern="user authentication"
exit $?
# Python test script
#!/bin/bash
python -m pytest tests/test_authentication.py::test_login
exit $?
# Java test script
#!/bin/bash
mvn test -Dtest=UserAuthenticationTest -q
exit $?
Advanced Bisect Techniques
Multiple Good/Bad Points
# Mark multiple good commits
git bisect good commit1 commit2 commit3
# Mark multiple bad commits
git bisect bad commit4 commit5
# Show current bisect status
git bisect log
Skipping Commits
# Skip commit that can't be tested (e.g., doesn't compile)
git bisect skip
# Skip specific commits
git bisect skip commit1 commit2
# Skip range of commits
git bisect skip commit1..commit2
Bisect with Path Limiting
# Only consider commits that changed specific files
git bisect start HEAD v1.0 -- src/auth/
# Bisect only commits affecting certain paths
git bisect start HEAD v1.0 -- "*.js" "*.ts"
Complex Debugging Scenarios
Performance Regression
#!/bin/bash
# performance-test.sh - Find performance regression
# Build application
make clean && make
if [ $? -ne 0 ]; then
exit 125 # Skip if build fails
fi
# Run performance test
start_time=$(date +%s.%N)
./run_benchmark
end_time=$(date +%s.%N)
# Calculate runtime
runtime=$(echo "$end_time - $start_time" | bc)
# Check if runtime exceeds threshold (e.g., 5 seconds)
if (( $(echo "$runtime > 5.0" | bc -l) )); then
exit 1 # Bad (too slow)
else
exit 0 # Good (fast enough)
fi
Intermittent Bug Detection
#!/bin/bash
# flaky-test.sh - Find intermittent bug
# Run test multiple times
for i in {1..10}; do
echo "Test run $i"
npm test -- --testNamePattern="flaky test"
if [ $? -ne 0 ]; then
exit 1 # Found the bug
fi
done
exit 0 # No bug detected
Memory Leak Detection
#!/bin/bash
# memory-test.sh - Detect memory leaks
# Start application with memory monitoring
valgrind --leak-check=full --error-exitcode=1 ./app &
APP_PID=$!
# Run load test
curl -f http://localhost:8080/stress-test
# Check if app is still running (no crash)
if kill -0 $APP_PID 2>/dev/null; then
kill $APP_PID
wait $APP_PID
exit $?
else
exit 1 # App crashed
fi
Bisect Visualization and Analysis
Visualizing Bisect Progress
# Show bisect log with visualization
git bisect log | git log --graph --oneline --stdin
# Visualize current bisect state
git log --graph --oneline --bisect --all
# Show commits being tested
git rev-list --bisect-all HEAD...v1.0
Analyzing Bisect Results
# After finding the bad commit
git show <bad-commit>
git log -p <bad-commit>^..<bad-commit>
# Show files changed in bad commit
git diff-tree --no-commit-id --name-only -r <bad-commit>
# Show commit context
git log --oneline -5 <bad-commit>
Bisect in Different Contexts
Bisect Across Branches
# Bisect between different branches
git bisect start feature/broken main
# Bisect within merge commits
git bisect start --first-parent HEAD v1.0
Bisect with Submodules
# Update submodules during bisect
git bisect start
git bisect bad
git bisect good v1.0
# In bisect test script
#!/bin/bash
git submodule update --init --recursive
make test
exit $?
Bisect in CI/CD
# CI bisect script
#!/bin/bash
# ci-bisect.sh
if [ "$1" = "good" ] && [ "$2" = "bad" ]; then
git bisect start $2 $1
git bisect run .ci/test-script.sh
# Report results
git bisect log > bisect-results.txt
git bisect reset
fi
Troubleshooting Common Issues
Build Failures During Bisect
Build Issues: Some commits might not compile due to intermediate development states.
# Handle build failures in test script
#!/bin/bash
# robust-test.sh
# Try to build
make clean && make
if [ $? -ne 0 ]; then
echo "Build failed, skipping..."
exit 125 # Tell git bisect to skip
fi
# Run actual test
./run_test
exit $?
False Positives/Negatives
# More robust test with retries
#!/bin/bash
# retry-test.sh
ATTEMPTS=3
for i in $(seq 1 $ATTEMPTS); do
echo "Attempt $i of $ATTEMPTS"
./run_test
RESULT=$?
if [ $RESULT -eq 0 ]; then
exit 0 # Success
fi
if [ $i -lt $ATTEMPTS ]; then
echo "Test failed, retrying..."
sleep 1
fi
done
exit 1 # All attempts failed
Recovering from Bisect Issues
# Reset bisect if something goes wrong
git bisect reset
# Start over with more specific range
git bisect start --term-new=broken --term-old=working
# Check bisect status
git bisect log
git status
Integration with Development Tools
IDE Integration
# VS Code bisect task (.vscode/tasks.json)
{
"version": "2.0.0",
"tasks": [
{
"label": "Git Bisect Run",
"type": "shell",
"command": "git",
"args": ["bisect", "run", "./test.sh"],
"group": "test"
}
]
}
GUI Tools
# GitKraken, SourceTree, Git Extensions
# Most GUI tools support bisect visually
# Command line with GUI diff
git config --global merge.tool vimdiff
git bisect start
git bisect bad
git bisect good v1.0
# At each step, use: git difftool HEAD~1 HEAD
Best Practices
Bisect Best Practices:
- Always test your bisect script before starting
- Use specific, reliable tests
- Handle build failures gracefully with exit code 125
- Document your bisect process
- Consider the cost of testing vs. manual inspection
- Use good commit messages to make bisect results clearer
Optimal Test Strategy
# Fast, focused test
#!/bin/bash
# optimal-test.sh
# Quick compile check
if ! make -j$(nproc) --quiet; then
exit 125
fi
# Run only the failing test, not entire suite
python -m pytest tests/specific_failing_test.py -v --tb=no -q
exit $?
Documenting Bisect Results
# Document bisect session
git bisect log > bisect-session-$(date +%Y%m%d).log
# Create bug report with bisect info
echo "Bug introduced in commit: $(git bisect bad)" > bug-report.md
echo "Files changed:" >> bug-report.md
git diff-tree --no-commit-id --name-only -r $(git bisect bad) >> bug-report.md
Advanced Debugging Workflows
Bisect + Blame Combo
# After finding bad commit, find specific line
BAD_COMMIT=$(git rev-parse --short HEAD)
git bisect reset
# Find who changed the problematic line
git blame $BAD_COMMIT -- problematic_file.js
# Show evolution of specific function
git log -p -S "function_name" -- file.js
Multi-Bug Bisecting
# Test for multiple bugs simultaneously
#!/bin/bash
# multi-bug-test.sh
# Test bug A
./test_bug_a.sh
BUG_A=$?
# Test bug B
./test_bug_b.sh
BUG_B=$?
# Both bugs must be present to mark as bad
if [ $BUG_A -ne 0 ] && [ $BUG_B -ne 0 ]; then
exit 1 # Both bugs present
else
exit 0 # At least one bug fixed
fi
Performance and Optimization
Speeding Up Bisect
# Use parallel builds
make -j$(nproc)
# Cache dependencies
if [ ! -d "node_modules" ]; then
npm ci --silent
fi
# Skip expensive operations
export NODE_ENV=test
export SKIP_INTEGRATION_TESTS=true
Limiting Bisect Scope
# Only bisect commits that touch relevant files
git bisect start HEAD v1.0 -- src/authentication/
# Skip merge commits
git bisect start --first-parent HEAD v1.0
# Bisect specific author's commits
git log --author="John Doe" --format="%H" v1.0..HEAD | git bisect start --stdin
Key Takeaways
- Binary Search: Efficient O(log n) algorithm for finding bugs
- Automation: Automated bisecting saves time and reduces errors
- Precision: Identifies exact commit that introduced the issue
- Flexibility: Works with any type of regression or bug
- Professional Debugging: Essential skill for maintaining large codebases
Master Git bisect to become a debugging expert. This powerful tool transforms the tedious process of hunting bugs into an efficient, automated workflow that scales with your project's complexity.