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.