Git bisect на реальном баге: найти коммит и не сломать ревью
Тесты проходили неделю назад, а теперь падают на границе скидки. Покажем на воспроизводимом примере, как Git bisect сузит поиск до коммита, что проверить в найденном изменении и как не превратить исправление в рискованный рефакторинг.

Тест на скидку проходит на сумме 999 рублей и падает на 1000, хотя неделю назад обе проверки были зелёными. Если с тех пор в ветку попало 40 коммитов, Git bisect поможет найти первый коммит, после которого результат стал неправильным. Ниже — сценарий для Git 2.x: разберём границы поиска, автоматический запуск теста и то, что стоит написать в ревью до рефакторинга.
Сначала закрепите, что именно сломалось
Пример условный, но повторяемый: приложение рассчитывает скидку, а тест проверяет пороговое значение. Регрессия появилась после изменения условия с «больше или равно» на «больше». Важно не принять этот пример за реальный инцидент из чужого проекта: здесь он нужен, чтобы показать процесс без выдуманной истории о конкретной компании.
Перед поиском зафиксируйте три вещи: тест, который воспроизводит ошибку; заведомо исправный коммит; заведомо сломанный коммит. Убедитесь, что тест запускается на обеих версиях проекта и сравнивает поведение, а не случайные детали окружения. Если тест зависит от сети, текущего времени или данных в сторонней базе, сначала сделайте его воспроизводимым: иначе bisect будет искать случайные колебания, а не регрессию.
Проверьте также рабочее дерево. Git переключает коммиты, а незакоммиченные изменения могут помешать переходу или повлиять на тест. Сохраните нужную работу коммитом либо уберите её в отдельное безопасное хранилище. Не начинайте поиск с состояния, которое нельзя однозначно восстановить.
Как bisect сужает диапазон
Вместо просмотра коммитов по одному Git выбирает промежуточные точки между известным хорошим и плохим состоянием. Вы проверяете выбранный коммит и сообщаете результат. Поиск повторяется, пока не останется первый коммит, на котором поведение изменилось. Для 40 последовательных коммитов это обычно означает несколько проверок, а не 40 ручных переключений.
Поиск опирается на предположение: между хорошей и плохой точками можно разделить историю по признаку «тест проходит / тест падает». Если тест ведёт себя нестабильно, регрессия появилась и исчезла или коммиты нельзя собрать, это предположение нарушается. В таких случаях bisect может сузить диапазон, но результат нужно перепроверять.
Git bisect находит границу между двумя наблюдаемыми состояниями. Он не доказывает, что найденная строка — единственная причина бага.
Монотонность поиска — допущение, что внутри выбранного диапазона состояние меняется в одном направлении: от исправного к сломанному. Если тест то проходит, то падает, это допущение не выполняется.
Ручной поиск: каждый результат под вашим контролем
Найдите хеш исправного коммита и зафиксируйте его, затем запустите bisect. В командах ниже замените <good-hash> на реальный хеш из своего репозитория. Начните с текущего сломанного коммита, только если вы проверили, что именно он воспроизводит ошибку.
git status --short
git bisect start
git bisect bad
git bisect good <good-hash>
Git переключит рабочее дерево на один из промежуточных коммитов. Запустите на нём один и тот же тест, затем сообщите результат:
git bisect good
Если тест падает, вместо последней команды выполните git bisect bad. Продолжайте, пока Git не назовёт коммит, на котором обнаружена граница. После этого выйдите из режима поиска:
git bisect reset
Сверьте вывод с историей изменений и самостоятельно запустите тест на найденном коммите и его родителе. Смысл проверки — подтвердить переход «до проходит, после падает», а не просто довериться сообщению инструмента. После git bisect reset проверьте git status: так вы убедитесь, что вернулись в исходное состояние и не потеряли рабочие изменения.
Автоматический запуск: быстрее, но строже к тесту
Если регрессию надёжно ловит одна команда, Git может запускать её на промежуточных коммитах самостоятельно. Для Python-проекта с установленным pytest это может выглядеть так:
git bisect start
git bisect bad
git bisect good <good-hash>
git bisect run pytest -q tests/test_discount.py
git bisect reset
Команда должна возвращать ноль, когда тест проходит, и ненулевой код — когда он обнаруживает регрессию. Убедитесь, что тестовый файл существует во всём диапазоне истории и что зависимости можно установить или они уже доступны. Ошибка запуска, отсутствие тестов или сломанная среда тоже могут дать ненулевой код; Git не умеет сам отличать такой сбой от провала проверки.
Для случая, когда на отдельном коммите тест нельзя выполнить, предусмотрен код завершения 125: Git пропускает эту точку и продолжает поиск. Используйте это осознанно. Если пропущено много коммитов, диапазон сужается хуже, а точность вывода зависит от того, что осталось проверяемым.
Что проверить в найденном коммите на ревью
Найденный коммит — кандидат на источник регрессии, а не готовый ответ. Откройте его diff и сопоставьте каждое относящееся к задаче изменение с падающим тестом. В примере со скидкой ключевой вопрос не «почему автор использовал такой стиль», а «какие значения входят в скидку и что должно происходить ровно на пороге».
- Проверьте крайние значения: ниже порога, точно на пороге и выше него. Убедитесь, что ожидаемое поведение следует из требования, а не придумано после обнаружения бага.
- Посмотрите, менялось ли рядом преобразование типа, округление, порядок проверки условий или входные данные. Не ограничивайтесь строкой, которая первой бросилась в глаза.
- Убедитесь, что тест действительно ловит регрессию: на родителе коммита он проходит, на найденной версии падает.
- Проверьте область влияния: используется ли та же функция в других сценариях, где условие может иметь другой смысл.
Хорошее замечание в ревью указывает на наблюдаемое последствие и проверку, а не на личное предпочтение. Например: «При сумме, равной порогу, условие теперь не включает скидку; тест на границе падает на этом коммите. Это изменение соответствует требованию?» Или: «Можно добавить проверку до и после порога, чтобы зафиксировать оба сценария?»
Неудачные варианты — «код странный» и «надо всё переписать». Они не называют риск и не помогают автору проверить гипотезу. Если бизнес-правило неясно, попросите подтвердить его у владельца требования; Git не может определить, какое поведение продукт считает правильным.
Исправление и рефакторинг — не одна операция
Сначала добавьте регрессионный тест, затем внесите минимальную правку и запустите тест повторно. Для порогового правила полезно проверить три точки: ниже границы, ровно на ней и выше. Так видно, что исправлена именно граница, а не только один пример, который обнаружил баг.
Рефакторинг уместен, если он уменьшает риск повторения ошибки — например, делает правило порога читаемым или собирает разрозненные проверки в одно место. Но не меняйте одновременно бизнес-логику, структуру модуля и способ округления без необходимости. Если тесты упали после такого набора изменений, ревьюеру сложнее понять, что именно изменило результат.
Удобный порядок для небольшого исправления: отдельный тест, минимальная правка, затем отдельный рефакторинг при наличии ясной причины. Если процесс команды требует одного коммита, разделите изменения хотя бы логически и объясните это в описании. Цель — не соблюсти формальную красоту истории, а оставить diff, в котором причина и исправление прослеживаются.
Где поиск ломается
- Тест нестабилен. Одна и та же версия то проходит, то падает. Сначала устраните источник случайности или запустите проверку несколько раз; иначе Git классифицирует шум как границу.
- Промежуточный коммит не собирается. Отметьте его как пропускаемый, если знаете, что именно сборка делает проверку невозможной. Затем проверьте, не стало ли пропусков слишком много для надёжного вывода.
- Плохая точка выбрана неверно. Если текущая версия не воспроизводит баг, bisect ищет не то изменение. Сначала подтвердите падение теста на верхней границе.
- Регрессия не укладывается в одну границу. Если изменение появлялось, исчезало и возникало снова, один поиск может найти только одну из таких границ. Разделите историю на диапазоны и повторите проверку.
- Есть некоммиченные изменения. Они способны помешать переключению коммитов или подменить результат теста. Сохраните их до начала поиска и проверьте состояние после сброса bisect.
Что делать дальше
После исправления запишите в задаче хеш найденного коммита, команду теста и подтверждённый результат до и после правки. В проде следите за тем же симптомом, который привёл к поиску: для пороговой логики это могут быть ошибки или неожиданные значения именно на границе, если такие метрики уже есть. Если тест пришлось пропускать или он зависит от окружения, сначала сделайте его стабильным — иначе следующий bisect снова упрётся не в историю Git, а в ненадёжную проверку.
Примеры используют базовые команды Git bisect в Git 2.x. Для деталей о пропуске коммитов и кодах завершения сверяйтесь с документацией своей установленной версии.
Поиск источников: Реальные статьи и книги со ссылками, библиография по ГОСТ
Спросить за 15 ₽Источники: Git Documentation: git-bisect, Pro Git: Debugging with Git
Частые вопросы
Git bisect сам исправит найденный баг?
Можно ли запустить bisect без тестов?
Что делать, если тест иногда проходит, а иногда падает?
Можно ли использовать git bisect run для любого тестового набора?
Что делать, если Git назвал коммит, но тест на нём не падает?
Нужно ли сразу рефакторить код после поиска?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.