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

Когда тест или функция ломаются после серии изменений, просмотр коммитов по одному быстро превращается в лотерею. git bisect превращает этот поиск в управляемый эксперимент: вы отмечаете заведомо рабочую и сломанную точки, а Git выбирает промежуточные коммиты, пока не останется первый коммит, после которого баг появился. Ниже — разбор на типичном реальном сценарии регрессии, механики поиска и мест, где результат легко испортить неверной проверкой.
Сценарий: тест работал, затем перестал
Представьте репозиторий приложения, в котором тест загрузки изображения проходил на старой версии. После обновления ветки тест начал падать с ошибкой декодирования файла. Между рабочим и сломанным состоянием накопилась серия изменений: рефакторинг, обновление зависимости, правка конфигурации и несколько несвязанных коммитов.
Ваша задача — найти не «подозрительный» коммит, а первый коммит, в дереве которого поведение стало неправильным. Это важное различие: коммит, где баг заметили, может быть намного новее причины.
Bisect отвечает не на вопрос «какой коммит выглядит плохим», а на вопрос «в какой границе между хорошим и плохим состоянием произошло изменение».
Регрессия[1] — это случай, когда ранее работавшее поведение перестаёт работать после изменения кода, конфигурации или зависимостей.
Как запустить поиск
Пример рассчитан на Git 2.44 и совместимые версии Git 2.x. Команды bisect start, good, bad, run и reset давно входят в стандартный Git, но результат всё равно зависит от того, одинаково ли вы воспроизводите ошибку на каждом коммите.
1. Зафиксируйте плохое состояние
Перейдите в рабочую копию, где баг стабильно воспроизводится. Сохраните незакоммиченные изменения или уберите их во временное хранилище: во время bisect Git будет переключать HEAD между коммитами.
git status
git bisect start
git bisect bad
Если текущий коммит действительно ломает проверяемый сценарий, Git примет его как верхнюю границу поиска. Проверка — команда git bisect status должна показать активный bisect и плохую ревизию.
2. Назначьте рабочую границу
Найдите коммит, тег или ветку, где тот же сценарий точно проходил. Это может быть последний релиз, предыдущий тег или сохранённый SHA-1. Не выбирайте «примерно старую» точку: если в ней баг уже есть, поиск начнётся с неправильной границы.
git bisect good v2.7.0
Git переключит рабочую копию на один из промежуточных коммитов. Проверка — команда git status показывает состояние bisect, а git log -1 --oneline показывает выбранную ревизию.
3. Проверяйте каждый предложенный коммит
Запустите тот же тест или ручной сценарий, который отличает рабочее состояние от сломанного. Если ошибка воспроизводится, отметьте коммит как плохой. Если функция работает, отметьте как хороший.
git bisect bad
# или, если проверка прошла
git bisect good
После каждой отметки Git выберет следующий коммит. Проверка — в терминале появится сообщение о следующей ревизии, а после завершения поиска Git сообщит первый плохой коммит.
4. Изучите результат и завершите режим
Когда граница найдена, Git выведет коммит, который считает первым плохим. Посмотрите его содержимое и родителей, но не меняйте вывод теста задним числом.
git show --stat <commit>
git show <commit>
git bisect reset
Проверка — после git bisect reset HEAD возвращается туда, где находился до запуска поиска. Сам найденный коммит не обязательно содержит очевидную строку с ошибкой: он мог изменить формат данных, порядок вызовов или контракт зависимости.
Что Git делает внутри
Bisect хранит две границы: множество плохих коммитов и множество хороших. Плохим считается указанный коммит и его потомки в рассматриваемом диапазоне; хорошим — указанный рабочий коммит и его предки. Кандидатами остаются коммиты, которые могут лежать на пути от хорошей границы к плохой.
Дальше Git не идёт по датам. Дата коммита не говорит, сколько изменений находится между ним и границами, а в графе могут быть ветвления и слияния. Git анализирует граф достижимости и выбирает ревизию, которая должна разделить оставшийся набор кандидатов как можно ближе к середине. Поэтому bisect — это поиск по графу, а не сортировка журнала по времени.
В линейной истории результат напоминает обычный двоичный поиск. Если диапазон содержит примерно N проверяемых коммитов, число шагов обычно близко к логарифму по основанию два от N. Но это оценка для линейного случая. Merge-коммиты, пропуски и неполные границы могут изменить число проверок.
Почему Git иногда выбирает «странный» коммит
В истории со слияниями у одного коммита может быть несколько родителей. Проверка merge-коммита отвечает на вопрос о состоянии всей объединённой истории, а не только одной ветки. Если ошибка появилась в боковой ветке, Git может сначала предложить коммит слияния, потому что он лучше делит текущий граф кандидатов.
Это не означает, что алгоритм ошибся. Это означает, что вы проверяете граф, в котором несколько путей к плохому состоянию. Если задача — найти изменение в конкретной ветке, заранее определите, какие границы и какие родители считаются частью расследования.
Автоматический bisect и его цена
Ручная проверка подходит, если результат виден только в браузере или требует анализа лога. Для детерминированного теста лучше передать проверку Git. Команда должна завершаться кодом 0, когда состояние хорошее, и ненулевым кодом, когда оно плохое.
git bisect start
git bisect bad HEAD
git bisect good v2.7.0
git bisect run ./scripts/check-image-regression.sh
git bisect reset
Уберите пробел перед второй командой, если копируете пример: он показан только как отдельная строка shell-команды. Сам скрипт должен быть исполняемым и не менять исходный код в рабочей копии. Git будет переключать коммиты, запускать проверку и повторять цикл.
Специальный код выхода 125 означает, что текущий коммит нельзя проверить: например, проект не собирается из-за независимой поломки. Git пометит такую ревизию как пропущенную и попробует продолжить. Любая автоматизация должна отличать «тест упал» от «тест невозможно запустить».
| Подход | Когда использовать | Цена и риск |
|---|---|---|
Ручной good/bad | Баг виден в интерфейсе, логе или ручном сценарии | Требует времени; легко отметить коммит по неполному наблюдению |
git bisect run | Есть повторяемый тест с однозначным кодом выхода | Быстрее, но нестабильный тест даст ложную границу |
| Просмотр истории без bisect | Изменений мало или виновный файл уже известен | Не требует подготовки, но плохо масштабируется по числу коммитов |
Где результат ломается
Тест зависит от окружения
Если на одном коммите тест падает из-за версии Node.js, PostgreSQL, внешнего сервиса или переменной окружения, Git запишет это как «плохое» поведение. Перед стартом зафиксируйте версии инструментов, очистите кеши и проверьте, что ручной запуск даёт одинаковый результат несколько раз.
Сборка не проходит на старых коммитах
Старая ревизия может не собираться из-за устаревшего компилятора, исчезнувшего пакета или несовместимого lock-файла. Это не всегда означает наличие исходного бага. В таком случае используйте git bisect skip, но помните: если пропущено много коммитов, Git может не получить единственную точную границу.
Проверка меняет рабочую копию
Скрипт, который генерирует файлы, применяет миграции или записывает кеш в репозиторий, искажает следующий запуск. Проверка должна работать во временной базе и отдельном каталоге артефактов. После каждого шага смотрите git status.
Баг не монотонный
Bisect предполагает, что между хорошей и плохой границей есть понятный переход. Если баг появляется, затем исчезает, а потом возвращается, найденный первый плохой коммит относится только к выбранному диапазону и условию проверки. Для повторного появления нужен отдельный запуск с другими границами.
Причина находится в окружении, а не в коммите
Если одинаковая ревизия иногда проходит, а иногда падает, сначала расследуйте гонку, время, случайные данные или внешний сервис. Bisect не исправляет нестабильный тест: он лишь быстро размножит его случайность на нескольких состояниях.
Как ревьюить найденный коммит
После результата не ограничивайтесь git show. Сопоставьте изменение с наблюдаемым симптомом:
- какие входные данные впервые проходят через новый путь;
- изменился ли порядок вызовов, формат сериализации или обработка ошибки;
- не требует ли коммит другой версии зависимости или настройки окружения;
- не является ли найденный коммит merge-точкой, скрывающей реальное изменение в одном из родителей;
- воспроизводится ли проблема на родителе и на самом найденном коммите в одинаковом окружении.
Если коммит большой, продолжите расследование внутри него обычным тестированием или временно разделите изменение на части в отдельной ветке. Повторный bisect по незакоммиченным локальным правкам уже не даёт чистого ответа.
Что делать дальше
Сохраните SHA-1 найденного коммита, команду воспроизведения и версии окружения в задаче или отчёте об инциденте. Затем добавьте автоматический тест, который падает на плохом поведении и проходит на исправленном. В проде отдельно проверьте метрику, связанную с регрессией: долю ошибок, время ответа, число неуспешных обработок или размер очереди. Bisect находит границу в истории, но только тест и наблюдаемость защищают от повторения.
[1] В этом материале «регрессия» означает ухудшение ранее работавшего поведения, а не любую новую ошибку в коде.
Поиск источников: Реальные статьи и книги со ссылками, библиография по ГОСТ
Спросить за 15 ₽Источники: Официальная документация Git: git-bisect, Официальная документация Git: git-bisect-lk
Частые вопросы
Можно ли запускать git bisect, если в рабочей копии есть незакоммиченные изменения?
Что делать, если промежуточный коммит не собирается?
Чем git bisect bad отличается от ошибки теста?
Найдёт ли bisect причину, если баг то появляется, то исчезает?
Можно ли использовать bisect в истории со слияниями?
Что делать после того, как найден первый плохой коммит?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.