Git: Find the First Bad Commit With a One-Liner
Scenario: a test fails on your current commit and you have no idea when it broke. This one-liner searches the whole Git history and prints just the hash of the commit that introduced the problem.
git bisect start HEAD $(git rev-list --max-parents=0 HEAD) > /dev/null 2>&1 && git bisect run ./test.sh 2>&1 | grep -i ' is the first bad commit' | cut -d " " -f1Output:
7a9f828538ddf19ba23389630bdf56b3b8e91d03The test script
test.sh is whatever tells a good commit from a bad one, and Git only looks at its exit code. Remember to chmod +x it.
#!/bin/sh
make || exit 125 # can't build: skip this commit
make test # 0 = good, anything else = bad0: this commit is good125: this commit can't be tested, skip it1to127, except 125: this commit is bad
How it works
git rev-list --max-parents=0 HEAD: finds the commit with no parents, the very first one in the repo.git bisect start HEAD <first-commit>: starts the search with the bad commit first and the good one second. The> /dev/null 2>&1hides its chatter.git bisect run ./test.sh: checks out the commit in the middle, runs the script, and halves the range based on the result. 1,000 commits take about 10 runs.2>&1: sends the errors from the test runs down the pipe too, so they don't end up on your screen.grep -i ' is the first bad commit': keeps the one line with the answer,<hash> is the first bad commit.cut -d " " -f1: keeps the first field of that line, the hash.
Before you run it
- Check both ends yourself. Git never runs the test on
HEADor on the first commit, it just believes they are bad and good. If the bug was there from the start, you get the second commit as the culprit, and that is wrong. - Keep
test.shout of the history. Every checkout swaps the tracked files, so if the script was committed recently, older commits don't have it and the bisect stops. Leave it untracked, or put it in/tmpand use the full path. - Don't remove the
exit 125. A commit that doesn't build says nothing about your bug. Count it as bad and Git can blame the wrong commit. - No output means no answer. Usually the script went missing, or the commits around the bug were all skipped and Git can only give you a list of suspects. Run it without the
grepandcutto see why. - It doesn't take you back. When it finishes you are still bisecting, on a detached
HEAD. Rungit bisect reset, or add it to the end of the one-liner:
git bisect start HEAD $(git rev-list --max-parents=0 HEAD) > /dev/null 2>&1 && git bisect run ./test.sh 2>&1 | grep -i ' is the first bad commit' | cut -d " " -f1; git bisect reset > /dev/null 2>&1
$ ls ./see-also/
$ ./buy-me-a-coffee ☕ // found this useful? I write these in my spare time