git bisect performs a binary search through a repository’s commit history to find the exact commit that introduced a bug — given a “good” commit (where the bug did not exist) and a “bad” commit (where the bug is present), git bisect checks out the midpoint between the two, waits for you to test and mark it good or bad, eliminates half the remaining commits, and repeats until exactly one commit is identified as the regression. The binary search requires at most log₂(N) steps to find the regression among N commits — a bug introduced somewhere in the last 512 commits requires at most 9 steps, compared to manually inspecting dozens of commits one by one. A bisect session starts with three commands: git bisect start (initializes bisect state), git bisect bad (marks current HEAD as bad), git bisect good v2.3.0 (marks a known-good tag or SHA). After each checkout you run your test and type git bisect good or git bisect bad — git bisect reset ends the session and returns to the original HEAD. Automated bisect with git bisect run {script} replaces manual testing entirely — git runs the script after each checkout and uses its exit code to classify the commit: exit 0 = good, exit 1–127 = bad, exit 125 = skip (commit is untestable — try the next one). When the regression is detectable by a PHPUnit test, a WP-CLI command, or a curl response check, the entire session runs unattended in under 60 seconds. git bisect log records every step and git bisect replay {logfile} replays a saved session — both are useful for sharing a debugging investigation with teammates. The --term-good / --term-bad flags rename the state labels: git bisect start --term-bad broken --term-good working lets you type git bisect working / git bisect broken, which reads more naturally for regressions in previously-fixed behavior. The git hooks post covered automated workflow triggers; git bisect run uses the same exit-code convention to automate regression hunting across dozens of commits from a single command.
Problem: A WooCommerce plugin shows a TypeError in the checkout process that appeared somewhere between tag v2.3.0 and current HEAD — 87 commits apart. Manually reviewing git log and checking out commits to test is estimated at 2–3 hours. The bug is reproducible by a specific PHPUnit test that fails when the regression is present.
Solution: Run git bisect run with the failing PHPUnit test as the automated script — git bisect finds the regression commit in at most log₂(87) ≈ 7 steps, running unattended in about 90 seconds.
# ── Manual bisect session ──────────────────────────────────────────────────────
git bisect start
git bisect bad # HEAD is where the bug exists
git bisect good v2.3.0 # v2.3.0 was known-good
# Git: "Bisecting: 43 revisions left to test (roughly 6 steps)"
# Git checks out the midpoint commit automatically
# Test, then mark:
git bisect bad # bug present -> git eliminates the later half
# OR
git bisect good # bug absent -> git eliminates the earlier half
# Repeat until:
# "a1b2c3d4 is the first bad commit"
git bisect reset # always reset to return to original HEAD
# ── bisect-test.sh — the automated test script ────────────────────────────────
#!/usr/bin/env bash
set -e
# Reinstall Composer at each commit — exit 125 means "skip this commit"
composer install --quiet 2>/dev/null || exit 125
# PHPUnit exits 0 on pass (good commit), 1 on fail (bad commit)
vendor/bin/phpunit --filter "test_cart_contents_returns_array" tests/Unit/CartTest.php --no-coverage --quiet
# ── Run the automated bisect session ──────────────────────────────────────────
chmod +x bisect-test.sh
git bisect start
git bisect bad # HEAD is broken
git bisect good v2.3.0 # v2.3.0 is known-good
git bisect run ./bisect-test.sh
# Sample output (unattended):
# Bisecting: 43 revisions left (roughly 6 steps)
# running './bisect-test.sh'
# ...
# a1b2c3d4e5 is the first bad commit
# commit a1b2c3d4e5
# Author: Developer <dev@example.com>
# Date: Mon Jan 15 14:23 2024
# Refactor cart contents serialization for session storage
git bisect reset
# ── Review the regression commit ──────────────────────────────────────────────
git show a1b2c3d4 # inspect the diff that introduced the bug
git log --oneline v2.3.0..a1b2c3d4 # see the commit in context
# ── Save and share the bisect session ─────────────────────────────────────────
git bisect start
git bisect bad
git bisect good v2.3.0
git bisect log > bisect-session.log # save progress at any point
# Replay on a different machine:
git bisect replay bisect-session.log
# ── WP-CLI bisect test for plugins without a PHPUnit suite ────────────────────
#!/usr/bin/env bash
# bisect-wpcli.sh
wp plugin activate my-woocommerce-plugin --quiet 2>/dev/null || exit 125
output=$(wp eval '
WC()->cart->add_to_cart(1);
$contents = WC()->cart->get_cart_contents();
echo is_array($contents) ? "OK" : "FAIL";
' 2>/dev/null)
[ "$output" = "OK" ] && exit 0 || exit 1
NOTE: git bisect run requires the test script to produce a consistent result for the same commit — if the test outcome depends on database state, cache files, or external services, the same commit may be classified differently on consecutive runs and git bisect may report an incorrect first bad commit. For WordPress plugin bisect sessions, start from a clean database state at each step (import a known-good SQL fixture or use an in-memory SQLite test database) to eliminate state variability. Also, if some commits in the history are untestable (build broken, Composer dependency unresolvable), exit with code 125 from your test script — git bisect will skip that commit and try the next one, keeping the session moving without misclassifying an untestable state as good or bad.