lesson ── git-labs ── ~18 мин ── 7 шагов
Цель - найти конкретный коммит, который сломал поведение, не
просматривая всю историю руками. git bisect делает бинарный поиск:
ты говоришь «здесь работало», «здесь сломано», Git делит интервал
пополам, ты тестируешь, повторяешь. log(n) шагов вместо 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 # шумовые коммиты до сломанного
git commit -am "tweak $i"
done
# сломанный коммит - меняем вывод "ok" на "fail" через 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 # шумовые коммиты после сломанного
git commit -am "tweak $i (later)"
done
git log --oneline | wc -l # должно вывести 10
10 коммитов. Один из них сломал вывод. Какой - сейчас неизвестно.
✓ 10 коммитов, текущий вывод 'fail'. Нужно найти, какой коммит виноват.
cd /home/student/work/bisect-lab
./app.sh # текущий вывод: fail
git stash 2>/dev/null # на всякий случай отложить локальные правки
# --max-parents=0 = коммит без parent'а, корневой (init)
FIRST=$(git rev-list --max-parents=0 HEAD)
git checkout $FIRST -- app.sh # достать app.sh из init-коммита в working tree
./app.sh # вывод "ok" - подтверждение, что init работал
git checkout HEAD -- app.sh # вернуть текущую (сломанную) версию
Первый коммит даёт ok. HEAD даёт fail. Поиск стартует
на интервале «между ними».
✓ HEAD сломан, init работает. Запускай bisect.
cd /home/student/work/bisect-lab
git bisect start # включаем режим поиска
git bisect bad HEAD # текущий коммит сломан
FIRST=$(git rev-list --max-parents=0 HEAD)
git bisect good $FIRST # корневой коммит работал
Git перепрыгнет на середину интервала и скажет что-то вроде
Bisecting: 4 revisions left to test after this (roughly 2 steps).
Это и есть бинарный поиск в действии.
✓ Bisect запущен. Git выбрал коммит посередине.
Проверь текущий коммит:
cd /home/student/work/bisect-lab
./app.sh
Если видишь ok - текущий коммит хороший: git bisect good.
Если fail - плохой: git bisect bad. Делай по результату:
# grep -q = тихий (только exit-код); ok найден - good, нет - bad
./app.sh | grep -q ok && git bisect good || git bisect bad
Git выберет следующий коммит для проверки.
✓ Сделал один шаг. Интервал сократился вдвое.
Можешь продолжить руками (./app.sh && git bisect good/bad
несколько раз), но проще - перейти на автоматический поиск
через bisect run. Сначала выйди из текущего:
cd /home/student/work/bisect-lab
git bisect reset
Это вернёт HEAD в main.
✓ Bisect остановлен. Дальше - автомат.
git bisect run ждёт скрипт, который возвращает 0 для хорошего
коммита, 1 - для плохого.
cd /home/student/work/bisect-lab
# тест: exit 0 если в выводе есть "ok", exit 1 если нет
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)"
На HEAD выйдет exit 1 (bad). Это ожидаемо.
✓ Тест-скрипт работает. Запускай автоматический поиск.
cd /home/student/work/bisect-lab
# bisect start <bad> <good> - сразу задаём границы
git bisect start HEAD $(git rev-list --max-parents=0 HEAD)
git bisect run ./test.sh # автопрогон: на каждом шаге запускает test.sh
Git перепрыгнет несколько коммитов, для каждого запустит test.sh,
и сообщит: <sha> is the first bad commit. Сообщение коммита
должно быть refactor: tune output - тот самый, где мы заменили
ok на fail.
Выйди из bisect:
git bisect reset
✓ Автоматический bisect нашёл виновного. log(n) против n.
bisect делит интервал коммитов пополам, ты отмечаешь хорошие
и плохие. Когда умеешь автоматически проверять - bisect run
делает всё сам.
команды
git bisect startначать поискgit bisect badтекущий коммит плохойgit bisect good <sha>указать хорошую базуgit bisect run ./test.shавтопоиск через скриптgit bisect resetвыйти из режима поискаконцепции