Все главы учебника
Содержание учебника
Глава 12 / Git

Отладка по истории: найти первый плохой коммит

18 мин чтенияКонтент v0.10.0

Поломка появилась где-то между двумя версиями

Сегодня инструкция сообщает очередь на тридцать два задания, хотя проверенное правило — шестнадцать. Последний коммит менял описание автора и может вообще не быть причиной. Если вы знаете рабочую версию и воспроизводимую проверку поломки, Git может сузить диапазон поиском по истории.

Bisect выбирает промежуточный коммит, вы сообщаете good или bad, затем диапазон уменьшается. В автоматическом режиме скрипт даёт результат через код выхода: ноль — good, обычная ненулевая ошибка — bad, 125 — версию нельзя проверить. Это поиск перехода по выбранному признаку. Если признак то ломался, то исправлялся многократно, вывод требует внимательной интерпретации; не обещайте универсальную «первую причину любой ошибки».

Сначала убедитесь, что старый снимок действительно проходит ту же проверку, а новый не проходит. Ошибка среды, отсутствующая зависимость или нестабильный тест могут направить поиск неверно. Bisect описывает good/bad, пропуск непроверяемых версий и автоматический запуск. Нам не нужен язык программирования: достаточно shell-проверки одной строки.

Создайте контролируемую регрессию

lab=$(mktemp -d "${TMPDIR:-/tmp}/git-12.XXXXXX")
git init -b main "$lab/repo"
cd "$lab/repo"
git config user.name "Учебный автор"
git config user.email "learner@example.invalid"
git config commit.gpgsign false
printf 'queue=16\n' > settings.txt
git add settings.txt
git commit -m "Set tested capacity"
good=$(git rev-parse HEAD)
printf 'Project\n' > README.txt
git add README.txt
git commit -m "Add description"
printf 'Project\nRun locally\n' > README.txt
git add README.txt
git commit -m "Document launch"
printf 'queue=32\n' > settings.txt
git add settings.txt
git commit -m "Accidentally change capacity"
expected_bad=$(git rev-parse HEAD)
printf 'Author: learner\n' > authors.txt
git add authors.txt
git commit -m "Add authors note"
printf 'Project\nRun locally\nLocal data only\n' > README.txt
git add README.txt
git commit -m "Clarify data scope"

Только один коммит меняет требуемое правило, поздние записи не исправляют его. Сохраняем expected_bad как контроль опыта, но не используем этот хеш для угадывания во время поиска. Скрипт проверки разместим за пределами репозитория, чтобы переключение коммитов его не удалило:

cat > "$lab/check-capacity.sh" <<'SH'
#!/bin/sh
if [ ! -f settings.txt ]; then
    exit 125
fi
grep -qx 'queue=16' settings.txt
SH
git bisect start HEAD "$good"
git bisect run sh "$lab/check-capacity.sh"
git bisect log
found=$(git rev-parse HEAD)
test "$found" = "$expected_bad"
git bisect reset
git branch --show-current

В нашем линейном диапазоне bad сохраняется после регрессии, и поиск должен назвать Accidentally change capacity первым плохим коммитом. Сравнение test завершается успешно без текста. Bisect reset возвращает прежнюю ветку; это выход из режима поиска, а не отмена коммитов через reset, изученная ранее.

Что делать с находкой

Прочитайте git show "$found" после выхода. Проверьте, какую строку изменили и почему ваша проверка это считает ошибкой. Затем создайте обычный фикс или revert, если нужно отменить отдельную запись. Bisect обнаруживает границу наблюдаемого поведения, но не пишет исправление и не объясняет исходное бизнес-требование.

Blame помогает найти последнее изменение конкретной строки, а log с путём — историю файла. Это вспомогательные способы ориентироваться, не доказательство виновности автора. Форматирование, перенос и слияние могут менять картину. В лаборатории причина контролируема; в реальном проекте найденный diff надо связать с воспроизводимым сценарием.

Проверьте саму проверку

Скрипт сообщает bad не потому, что очередь неверная, а потому, что команда grep не найдена. Можно ли принять полученный коммит за причину? Нет: сломана среда проверки. Перед поиском запустите сценарий на обеих границах и убедитесь, что ошибки соответствуют исследуемому поведению. Если старая версия непроверяема, используйте skip, а не произвольное good.

Другой случай: очередь стала 32, затем вернулась к 16, затем снова стала 32. Название «первый плохой» без выбранного диапазона теперь двусмысленно. Задайте конкретный интервал и прочитайте историю значений; двоичный поиск не является перебором всех временных регрессий. В своей лаборатории признак намеренно сохраняется после одной поломки, поэтому предпосылка видна.

Наконец, баг может проявляться не на каждом запуске. Повторяемость проверки нужно обеспечить или явно исследовать до автоматического поиска. Если сегодня выбранный снимок good, а завтра bad при тех же данных, цепочка решений не даёт надёжного вывода. Сохраните вход, версию инструментов и короткое описание признака. Найденная запись — начало разбора причины, а не готовое обвинение автора или автоматическое исправление проекта.

Самостоятельная лаборатория

Практика — 40–60 минут. Создайте восемь коммитов маленького проекта, сломайте одну проверяемую строку в середине и оставьте её сломанной до конца. Сохраните хеш поломки отдельно как контроль, не подглядывайте в него. Проверьте границы good и bad, затем выполните поиск вручную или через свой скрипт.

Добавьте снимок, который невозможно проверить выбранным способом, и объясните, когда нужен skip или exit 125. Не называйте отсутствие файла автоматическим bad, если этот файл ещё не требовался в выбранной версии. Сохраните bisect log и одну фразу о предпосылке: почему признак в данном диапазоне можно искать как переход от рабочего к сломанному.

Критерии готовности

Один и тот же сценарий проходит на good и падает на bad. Поиск называет ожидаемую запись, её diff объясняет поломку. Скрипт не меняет отслеживаемые файлы и живёт вне переключаемой истории. После опыта вы вернулись в именованную ветку, а не продолжили случайно делать коммиты в промежуточной версии.

Разбор

Последняя запись автора не является найденной причиной: она уже создана поверх сломанного состояния. Скрипт различает результат содержимого и невозможность проверки. Если bisect сообщает другой коммит, сначала прочитайте сценарий проверки и историю значений: возможно, вы сломали строку раньше или проверяли условие, которое не было одинаковым для всех снимков.

Следующая глава — что не стоит сохранять и публиковать. Первичный источник механизма — git bisect; просмотр находки проверяется по git show. Привычка воспроизводить признак до поиска пригодится и за пределами Git.