Выберите продукт

Git bisect: как найти «плохой» коммит и быстро остановить regression

Регрессия в проекте — это часы на угадывание «кто сломал». Git bisect делает бинарный поиск по истории и находит bad commit за 8–15 шагов. Разберём ручной bisect, bisect run с автотестом, skip для непроверяемых коммитов и практические советы для CI.
Git bisect: как найти «плохой» коммит и быстро остановить regression

Регрессия (regression) — это когда «вчера работало, сегодня нет». Для админов/DevOps и вебмастеров это часто выглядит так: после мержа падают тесты, ломается деплой, миграции БД перестают проходить или приложение начинает отдавать 500. В таких ситуациях git bisect — один из самых прикладных инструментов: он делает бинарный поиск по истории и помогает найти конкретный «плохой» коммит быстрее, чем ручное перебирание.

Ниже — практическая инструкция: как запустить bisect вручную, как подключить git bisect run с автопроверкой, чем полезны skip и --first-parent, и какие ловушки чаще всего ломают результат (особенно при CI).

Что делает git bisect и почему это быстрее ручного поиска

Логика простая:

  • вы задаёте «хорошую» точку (good) — там бага нет;
  • задаёте «плохую» точку (bad) — там баг есть;
  • Git выбирает коммит посередине диапазона, вы проверяете и помечаете good или bad;
  • диапазон сужается вдвое, пока не останется один коммит-кандидат.

Это бинарный поиск: даже на тысяче коммитов обычно хватает около 10 проверок. При инциденте в проде это часто разница между «полдня раскопок» и «15 минут до конкретного SHA».

Подготовка: как правильно выбрать good и bad точки

Точность результата упирается в качество входных данных. Перед стартом полезно сделать две вещи.

  1. Зафиксировать критерий «сломано/не сломано». Это должна быть проверка с однозначным исходом: тест, команда, smoke-запрос, конкретная ошибка в логах.
  2. Выбрать две точки истории. Чаще всего:
  • bad — текущий HEAD ветки, где вы видите regression;
  • good — последний известный релизный тег или коммит до большого мержа, где всё работало.

Если релизы отмечены тегами — используйте их как good: так проще объяснить результат команде и привязать к релизному окну.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Ручной git bisect: пошаговая инструкция

Базовый сценарий выглядит так.

1) Стартуем bisect и задаём плохую точку

git bisect start
git bisect bad

git bisect bad без аргументов помечает текущий HEAD как плохой.

2) Задаём хорошую точку

Например, по тегу или SHA:

git bisect good v1.8.3

После этого Git переключится на коммит-кандидат для проверки.

3) Проверяем и отмечаем результат

Запускаете критерий (тест/скрипт/ручная проверка) и отмечаете:

git bisect good

или

git bisect bad

Git переместит вас на следующий коммит. Повторяйте, пока не увидите сообщение вида is the first bad commit.

4) Завершаем bisect и возвращаемся на исходную точку

git bisect reset

Команда reset возвращает рабочую директорию к исходной ветке/коммиту и снимает состояние bisect. Это важно, чтобы случайно не продолжить работу «в середине истории».

Практика: нашли SHA — сразу зафиксируйте его в тикете/заметке и сделайте git bisect reset. Чем дольше вы живёте в bisect-состоянии, тем проще перепутать контекст и сломать себе рабочую копию.

Схема бинарного поиска в git bisect: good, bad и промежуточные коммиты

Критерий «good/bad»: примеры, которые удобно автоматизировать

Идеальный критерий выполняется одной командой и даёт однозначный exit code. Примеры для типовых стеков:

  • падает юнит-тест: pytest -q, phpunit, go test ./...;
  • ломается сборка: npm test, npm run build, composer test;
  • ломается миграция: прогон миграций на временной БД (контейнер/ephemeral instance) и проверка результата;
  • регрессия в конфиге: генерация итогового конфига из шаблонов и его проверка линтером/валидатором.

Ключевой момент: bisect «любит» автоматизацию. Как только вы превратили проверку в «одну команду» — подключайте git bisect run.

Если вы вылавливаете регрессии именно в пайплайне, полезно держать рядом отдельный воспроизводимый сценарий сборки/деплоя. В этом помогает дисциплина CI/CD: вынесенные шаги, быстрые smoke-тесты и понятный rollback. По теме можно дополнять процесс статьёй про деплой по SSH с откатом в CI/CD.

git bisect run: автоматический поиск «плохого» коммита через тест/скрипт

git bisect run запускает команду (или скрипт) на каждом выбранном коммите и сам проставляет good/bad по коду возврата.

Правило такое:

  • exit code 0 = good;
  • exit code 1..127 = bad;
  • exit code 125 = skip (невозможно проверить этот коммит в текущем окружении).

Пример: тесты как критерий regression

git bisect start
git bisect bad
git bisect good v1.8.3
git bisect run sh -c 'pytest -q'

Если тест прошёл — вернётся 0, если упал — ненулевой код, и bisect корректно сузит диапазон.

Пример: скрипт-проверка (сборка + smoke)

Часто нужна «обвязка»: подготовить окружение, поставить зависимости, собрать проект, прогреть кэш, а потом запустить проверку. Делайте это скриптом и возвращайте корректный exit code.

cat > ./bisect-test.sh <<'SH'
#!/bin/sh
set -eu

npm ci
npm run build
npm test
SH

chmod +x ./bisect-test.sh

git bisect start
git bisect bad
git bisect good v1.8.3
git bisect run ./bisect-test.sh

Как корректно «пропускать» коммиты (skip)

В ручном режиме:

git bisect skip

В автоматическом — возвращайте 125, если коммит объективно непроверяем (например, зависимость больше не доступна, сборка несовместима с текущим runtime):

cat > ./bisect-test.sh <<'SH'
#!/bin/sh
set -eu

if ! npm ci; then
  exit 125
fi

if ! npm run build; then
  exit 125
fi

npm test
SH

Важно: если вы пропускаете слишком много точек, bisect может завершиться не одним SHA, а несколькими кандидатами.

Ускоряем bisect: приёмы из практики

Сужайте диапазон заранее

Чем ближе выбран good к моменту поломки, тем меньше итераций. Если вы примерно знаете «после чего началось» (релиз, обновление зависимостей, конкретный PR) — ставьте good максимально близко.

Ищите по «магистрали» (first-parent), если много merge-коммитов

В репозиториях с крупными PR/merge иногда полезно получить ответ уровня «какой merge принёс regression»:

git bisect start --first-parent

Дальше вы либо бисектите уже внутри PR-ветки, либо просто смотрите список коммитов в составе мержа.

Фиксируйте окружение, иначе будете ловить не regression, а дрейф

Тяжёлые проверки (сборка фронтенда, интеграционные тесты) ускоряются и стабилизируются, если:

  • использовать кеши пакетных менеджеров (npm/pip/composer) в том окружении, где идёт bisect;
  • запускать bisect в контейнере/виртуалке с уже установленными системными пакетами;
  • закрепить версии runtime (Node/Python/PHP/Go), иначе часть «падений» окажется несовместимостью окружения.

Если вы делаете проверку в изолированном окружении, часто удобнее гонять её на отдельной машине или виртуалке. Для таких задач обычно берут VDS, чтобы не мешать рабочей станции и иметь воспроизводимое окружение под конкретный проект.

Вывод консоли CI с падающим тестом и запуском bisect-скрипта для автоматического поиска bad commit

Типовые ошибки и как их обходить

Ошибка 1: воспроизведение зависит от окружения

Классика: локально всё зелёное, а в CI — красное (или наоборот). Решение одно: приблизить bisect-окружение к тому, где проявляется regression. Тот же контейнер/образ, те же версии интерпретаторов, те же флаги сборки.

Ошибка 2: тест «плавающий» (flaky) и bisect обвиняет случайный коммит

Если тест иногда падает «сам по себе», git bisect может указать на невиновный SHA. Что помогает:

  • делать проверку более детерминированной (фиксированный seed, отключение параллелизма, стабилизация данных);
  • в bisect run запускать тест несколько раз и считать bad только при повторяемом падении.

Пример на три попытки: если прошёл хотя бы раз — считаем good; если упал трижды — bad.

cat > ./bisect-test.sh <<'SH'
#!/bin/sh
set -eu

i=1
while [ $i -le 3 ]; do
  if pytest -q; then
    exit 0
  fi
  i=$((i + 1))
done

exit 1
SH

Ошибка 3: «плохой» коммит — это merge, а истинный виновник внутри

Если bisect показал merge-коммит, это не всегда означает, что проблема «в самом merge». Часто виноват один из коммитов в составе PR. Дальше варианты такие:

  • посмотреть, какие коммиты принёс merge, и пробисектить диапазон внутри ветки;
  • повторить bisect без --first-parent (если вы его использовали), чтобы Git ходил по всем родителям.

Ошибка 4: старые коммиты не собираются

Для длинной истории это нормально: менялись компиляторы, lock-файлы, структура репозитория. В таких случаях обычно комбинируют:

  • skip для реально непроверяемых точек;
  • перенос good ближе (например, на первый релиз после обновления toolchain);
  • запуск bisect в «историческом» окружении (если оно у вас сохранено в CI как образ).
Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Как подружить git bisect с CI и разбором инцидентов

Самый прикладной сценарий: в CI появился regression, нужно быстро локализовать bad commit и открыть точный тикет/PR на фикс.

Что обычно работает:

  • Хранить воспроизводимую команду из CI. Не «где-то в пайплайне», а конкретный шаг, который можно выполнить локально или на раннере.
  • Иметь быстрый smoke test. Не e2e на 30 минут, а минимальную проверку на 1–3 минуты, которая ловит конкретную регрессию.
  • Держать “bisect script” в репозитории. Скрипт из CI удобно положить, например, в tools/ и использовать как команду для git bisect run.

Если у вас доступен тот же раннер, где падает CI, часто быстрее выполнить bisect прямо там: совпадёт окружение, и вы не потратите время на «почему локально не воспроизводится».

Чтобы ускорять такие разборы системно, помогает «приземлённый» пайплайн деплоя (в том числе с rsync и понятными шагами). Если у вас PHP-проекты, посмотрите статью про CI/CD для PHP на GitHub Actions с деплоем через rsync — оттуда удобно забирать идею «одна команда, один результат».

Мини-чеклист: когда git bisect — лучший выбор

  • есть regression и понятный критерий «good/bad»;
  • понятно, где находится «точно хорошо» и «точно плохо» (тег, релиз, коммит до мержа);
  • проверка достаточно быстрая, чтобы сделать 8–15 итераций.

Если критерий не формализуется или воспроизводимость слабая — сначала стабилизируйте симптом/тест. Bisect полезен тем, что заставляет договориться: что именно считается поломкой, и как это проверять без «магии».

Итог

git bisect сокращает время поиска причины regression и переводит разговор из «кто виноват» в «какой именно коммит сломал и почему». Начните с ручного режима, а как только появится автоматизируемый критерий — переходите на git bisect run. Это хорошо ложится на CI-процессы и экономит часы на каждом неприятном «вчера работало».

Поделиться статьей

Вам будет интересно

SSH зависает на Connecting и banner exchange: диагностика MTU, DNS, GSSAPI и KEX OpenAI Статья написана AI (GPT 5)

SSH зависает на Connecting и banner exchange: диагностика MTU, DNS, GSSAPI и KEX

Если SSH зависает на Connecting или banner exchange, причина обычно в конкретном шаге: проблемы TCP/фильтрации, MTU/PMTUD, задержк ...
TLS 1.3 и HTTP/2: что видно в ClientHello/ServerHello и как разбирать ошибки рукопожатия OpenAI Статья написана AI (GPT 5)

TLS 1.3 и HTTP/2: что видно в ClientHello/ServerHello и как разбирать ошибки рукопожатия

Практический разбор TLS 1.3 на уровне ClientHello/ServerHello: где увидеть SNI и ALPN, как сервер выбирает HTTP/2, почему возникае ...
Apache mod_remoteip: real IP клиента за reverse proxy без поломки логов OpenAI Статья написана AI (GPT 5)

Apache mod_remoteip: real IP клиента за reverse proxy без поломки логов

Когда Apache работает за reverse proxy, в логах и REMOTE_ADDR часто виден IP прокси, а не клиента. Разберём mod_remoteip: какой Re ...