Git bisect: как найти коммит, который сломал тест

Git bisect: как найти коммит, который сломал тест
Содержание статьи

git bisect помогает найти изменение, после которого появился воспроизводимый дефект. Вы задаёте исправную и сломанную версии, проверяете предложенные Git коммиты и постепенно сужаете диапазон. Самая важная часть работы здесь: одинаково определять результат каждой проверки.

На Mentoring.digital разберём поиск регрессии по одному граничному случаю. Предполагается, что вы уже умеете читать историю Git и запускать тест проекта.

Сначала подтвердите две контрольные версии

Допустим, доставка заказа от 5000 рублей должна быть бесплатной. В исправной версии условие написано как total >= 5000, а затем его случайно заменили на total > 5000. Заказ ровно на 5000 обнаруживает ошибку; заказ на 6000 её скрывает. Это учебный пример, а не история реального магазина.

Запустите одну и ту же проверку на старой версии и на текущей. Старый коммит должен проходить её, новый должен падать. Зафиксируйте команду, входные данные, версию интерпретатора и зависимости. Если результат зависит от внешнего API или случайных данных, сначала сделайте сценарий устойчивым.

Не начинайте поиск с предположения «релиз вроде работал». Ошибочная отметка good исключит часть истории, в которой находится дефект.

Подготовьте отдельную рабочую директорию

Bisect переключает содержимое рабочей директории между коммитами. Чтобы не смешивать это с незавершённой работой, создайте дополнительную рабочую директорию из текущего HEAD. Путь ../bisect-lab должен быть новым:

git status
git worktree add --detach ../bisect-lab HEAD
cd ../bisect-lab
git bisect start HEAD v1.0

Здесь v1.0 обозначает проверенную исправную версию. Замените её своим существующим тегом или хешем коммита. В новой директории отдельно подготовьте зависимости проекта. Команда git bisect start уже выберет промежуточную ревизию; это нормальное состояние без обычной рабочей ветки.

Отмечайте результат каждого шага

Выполните проверку в выбранной версии. Если именно исследуемый дефект отсутствует, введите git bisect good. Если он воспроизводится, введите git bisect bad. Повторяйте до сообщения о первом плохом коммите.

git bisect good
# На следующей выбранной версии выполните тест.
# Отметьте good или bad по его результату.
git bisect log

Не запускайте good и bad подряд как готовый скрипт: это ответы на разные проверки. Журнал git bisect log помогает понять, какие версии и как были отмечены.

Если старая ревизия не собирается по посторонней причине и тест выполнить нельзя, используйте git bisect skip. Пропуск не означает, что версия исправна. Несколько пропущенных коммитов рядом с границей могут оставить несколько кандидатов.

Автоматизируйте устойчивую проверку

Для учебного модуля fees.py с функцией delivery_fee сохраните следующий скрипт как check_fee.py рядом с директорией bisect-lab, вне переключаемой истории:

import sys
from pathlib import Path
sys.path.insert(0, str(Path.cwd()))
from fees import delivery_fee
sys.exit(0 if delivery_fee(5000) == 0 else 1)

Внутри bisect-lab запустите автоматический поиск. Команда python должна указывать на подготовленный для проекта интерпретатор:

git bisect run python ../check_fee.py
git bisect log
git bisect reset

Нулевой код завершения означает good. Код 1 в нашем скрипте означает bad. Специальный код 125 предназначен для версии, которую невозможно проверить. Не маскируйте произвольное падение теста этим кодом: можно пропустить нужную границу.

Перед автоматизацией отдельно убедитесь, что команда запуска существует и скрипт импортирует нужный модуль. Коды 126 и 127 Git тоже трактует как плохую версию, хотя оболочка может сообщать ими о проблеме запуска. Падающее окружение способно направить поиск по неверному пути.

Проверьте найденный коммит и завершите поиск

Прочитайте изменения найденного коммита через git show. Повторите исходную проверку на нём и на предшествующей исправной версии. На линейной истории это позволяет подтвердить границу. В истории слияний сначала выясните, с каким родителем сравнивать результат.

Команда git bisect reset завершает сеанс и возвращает состояние, с которого он начался. Она не является исправлением ошибки. Найденное изменение может быть корректным само по себе, но выявлять старую проблему в другом месте.

Для этого материала мы создали локальный репозиторий из девяти учебных коммитов, намеренно поменяли условие на шестом и запустили bisect run. Git указал именно этот коммит; после сброса исходный HEAD восстановился. Это проверка сценария на искусственном примере, а не обещание определённой скорости на вашем проекте.

Когда поиск стоит остановить

  • Одинаковая ревизия то проходит тест, то падает. Сначала разберитесь с нестабильностью.
  • На старых версиях не удаётся восстановить окружение. Отделите проблему зависимостей от исследуемого поведения.
  • Ошибка исчезала и возвращалась несколько раз. Выберите диапазон с одной понятной границей.
  • Проверяется весь большой набор тестов, хотя нужен один дефект. Выделите минимальный сценарий, который его ловит.

Если проблема проявляется только в CI, полезно разобрать воспроизводимость сборки с ментором по DevOps. А превратить ручной сценарий в устойчивую проверку можно на разборе автотестов с наставником. Для такой встречи подготовьте диапазон коммитов, команду теста и журнал bisect.

Похожие статьи по данной теме