lesson ── git-labs ── ~18 мин ── 7 шагов
The goal is to find the exact commit that broke a behavior without
reading through the whole history by hand. git bisect does a binary
search: you say "it worked here", "it is broken here", Git halves the
interval, you test, you repeat. log(n) steps instead of n.
интерактивный sandbox
Поднимется контейнер gitlab/git-base с git, bash, pre-commit. В браузере откроется терминал, можно сразу git init. Каждый шаг проверяется автоматически. Сеть air-gapped, github.com недоступен.
stack ── git · bash · 256 MB RAM · air-gapped · самоуничтожается через 30 мин простоя
cd /home/student/work
mkdir -p bisect-lab && cd bisect-lab
git init -b main
cat > app.sh <<'EOF'
#!/bin/bash
echo "ok"
EOF
chmod +x app.sh
git add . && git commit -m "init: working app"
for i in 1 2 3 4; do
echo "# noop change $i" >> app.sh # noise commits before the broken one
git commit -am "tweak $i"
done
# the breaking commit: change output from "ok" to "fail" via sed -i (in place)
sed -i 's/echo "ok"/echo "fail"/' app.sh
git commit -am "refactor: tune output"
for i in 5 6 7 8 9; do
echo "# more noop $i" >> app.sh # noise commits after the broken one
git commit -am "tweak $i (later)"
done
git log --oneline | wc -l # should print 10
10 commits. One of them broke the output. Which one is unknown for now.
✓ 10 commits, current output is 'fail'. You need to find which commit is to blame.
cd /home/student/work/bisect-lab
./app.sh # current output: fail
git stash 2>/dev/null # set local changes aside just in case
# --max-parents=0 = a commit with no parent, the root (init)
FIRST=$(git rev-list --max-parents=0 HEAD)
git checkout $FIRST -- app.sh # pull app.sh from the init commit into the working tree
./app.sh # output "ok": confirms that init worked
git checkout HEAD -- app.sh # restore the current (broken) version
The first commit gives ok. HEAD gives fail. The search starts
on the interval "between them".
✓ HEAD is broken, init works. Start bisect.
cd /home/student/work/bisect-lab
git bisect start # turn on search mode
git bisect bad HEAD # the current commit is broken
FIRST=$(git rev-list --max-parents=0 HEAD)
git bisect good $FIRST # the root commit worked
Git jumps to the middle of the interval and says something like
Bisecting: 4 revisions left to test after this (roughly 2 steps).
That is binary search in action.
✓ Bisect is running. Git picked the commit in the middle.
Check the current commit:
cd /home/student/work/bisect-lab
./app.sh
If you see ok, the current commit is good: git bisect good.
If fail, it is bad: git bisect bad. Act on the result:
# grep -q = quiet (exit code only); ok found = good, otherwise bad
./app.sh | grep -q ok && git bisect good || git bisect bad
Git picks the next commit to check.
✓ You took one step. The interval shrank by half.
You can keep going by hand (./app.sh && git bisect good/bad
a few times), but it is simpler to switch to the automatic search
with bisect run. First leave the current one:
cd /home/student/work/bisect-lab
git bisect reset
This returns HEAD to main.
✓ Bisect stopped. Next, the automatic search.
git bisect run expects a script that returns 0 for a good
commit and 1 for a bad one.
cd /home/student/work/bisect-lab
# test: exit 0 if the output has "ok", exit 1 if not
cat > test.sh <<'EOF'
#!/bin/bash
./app.sh | grep -q ok
EOF
chmod +x test.sh
./test.sh && echo "exit 0 (good)" || echo "exit 1 (bad)"
On HEAD it prints exit 1 (bad). That is expected.
✓ The test script works. Start the automatic search.
cd /home/student/work/bisect-lab
# bisect start <bad> <good> = set the bounds right away
git bisect start HEAD $(git rev-list --max-parents=0 HEAD)
git bisect run ./test.sh # auto run: runs test.sh at each step
Git jumps over several commits, runs test.sh for each one,
and reports: <sha> is the first bad commit. The commit message
should be refactor: tune output, the very one where we replaced
ok with fail.
Leave bisect:
git bisect reset
✓ The automatic bisect found the culprit. log(n) against n.
bisect halves the interval of commits, you mark the good ones
and the bad ones. When you can check automatically, bisect run
does everything for you.
команды
git bisect startstart the searchgit bisect badthe current commit is badgit bisect good <sha>point to a good basegit bisect run ./test.shauto search with a scriptgit bisect resetleave search modeконцепции