Skip to main content

Що таке git bisect?

git bisect - Git-команда яка знаходить коміт що зламав код через бінарний пошук: перевіряє середини між відомим робочим та зламаним комітом, поки не залишається один.

Теорія

TL;DR

  • Бінарний пошук ділить діапазон комітів навпіл на кожному кроці: 1000 комітів = ~10 тестів
  • Позначаєш коміти як good (працює) або bad (зламаний), Git сам обирає наступну середину
  • Завжди починай з git bisect bad на зламаному стані, потім git bisect good <старий коміт>
  • git bisect run ./test.sh автоматизує все: Git запускає скрипт на кожній середині без твого втручання
  • Правило вибору: більше 30 комітів від останнього робочого стану? Використовуй bisect. Менше? git log -p буде швидше

Короткий приклад

bash
git bisect start git bisect bad # HEAD зламаний git bisect good v1.0 # v1.0 працював # Вивід: "Bisecting: 512 revisions left to test (roughly 9 steps)" # Git автоматично перемикається на середній коміт # Тестуєш, потім позначаєш результат: git bisect good # або: git bisect bad # Після ~9 кроків: # "abc123 is the first bad commit" git bisect reset # Повертає на оригінальну гілку

1000 комітів, 10 тестів. Ось і вся ідея.

Чому бінарний пошук змінює підрахунок

Перевірка комітів по черзі займе до N кроків. Бінарний пошук - log2(N). Для 1000 комітів це ~10. Для 100 000 - ~17. Різниця відчутна коли кожен запуск тестів займає дві хвилини.

Git зберігає тимчасові межі в refs/bisect/, сортує коміти топологічно і вибирає середину через експоненційний пошук у bisect--helper.c. Це не просте ділення кількості комітів навпіл. Алгоритм враховує форму DAG, тому вибрана середина іноді виглядає не зовсім посередині.

Коли використовувати git bisect

  • Баг з'явився після великого merge або PR: bisect між базою злиття та HEAD
  • Продакшн зламався без очевидних нещодавніх змін: bisect від останнього release-тегу
  • Тест падає і ти можеш відтворити це стабільно: bisect run зі скриптом
  • Хтось змінив поведінку без чіткого commit message: bisect знаходить коміт, git show показує diff

Одна ситуація де варто зупинитись: якщо інші розробники активно пушать у ту саму гілку під час сесії, історія може змінитись. Запускай bisect на локальній копії або окремій гілці.

Автоматизація через bisect run

bash
git bisect start git bisect bad git bisect good v1.0 git bisect run node test/auth.test.js # Коди виходу: 0 = good, 1-127 = bad, 125 = пропустити, 128+ = перервати сесію

Код виходу 125 каже Git пропустити коміт. Це потрібно для комітів що не компілюються, мають відсутні залежності або передують файлам яких очікує тест. Git їх пропускає і продовжує пошук.

Як це працює всередині

Git зберігає стан bisect у .git/refs/bisect/ та .git/BISECT_LOG. Лог фіксує кожне позначення, тому сесію можна відновити через git bisect replay bisect.log. Зручно коли треба зробити паузу або поділитись процесом дебагу з колегою.

Merge-коміти Git пропускає при виборі середини за замовчуванням, бо вони не вносять код напряму. Якщо підозрюєш merge-коміт, git bisect visualize покаже решту кандидатів як граф.

Типові помилки

bisect good HEAD коли HEAD вже зламаний. Git скасовує сесію через перетин меж. Завжди починай з bisect bad, потім позначай старіший good коміт.

Забути bisect reset. Після знаходження поганого коміту ти в стані detached HEAD. Коміти, пуші, більшість операцій з гілками поводяться несподівано. git bisect reset або git checkout main повертає назад.

Ручне тестування з непослідовними результатами. Bisect розраховує що твій тест детермінований. Якщо ти на око вирішуєш "виглядає ок?", рано чи пізно позначиш не той коміт. Пиши скрипт і використовуй bisect run.

Запуск з незакомміченими змінами. Git відмовляється перемикатись між комітами при брудному дереві.

bash
# Так не спрацює: echo "debug" >> index.js git bisect start # fatal: cannot bisect on dirty working tree # Правильно: git stash git bisect start # ... git bisect reset git stash pop

Bisect під час rebase. Переписування історії змінює SHA комітів. Якщо репозиторій перебазовують під час сесії, bisect втрачає орієнтири. Збережи прогрес через git bisect log > session.log, відновлюй через git bisect replay session.log.

Де зустрічається в реальних проектах

  • Linux kernel: контрибютори використовують git bisect run make test для пошуку регресій у драйверах; задокументовано в офіційному гайді на kernel.org
  • Node.js: git bisect run node test/parallel/test-http.js для регресій у HTTP-модулі ядра
  • Chromium: автоматизований bisect-бот спрацьовує на кожен збій збірки в CI
  • React: bisect між release-тегами при появі регресії рендерера між версіями

На практиці найважча частина не сам запуск bisect, а написання надійного тестового скрипта. Якщо баг проявляється тільки під навантаженням або в конкретному середовищі, простий npm test його не спіймає. Скрипт має відтворювати саме ту умову що ламається.

Питання для поглиблення

Q: Як робити bisect якщо баг з'явився всередині merge-коміту?
A: Git пропускає merge-коміти за замовчуванням бо вони не вносять код напряму. Перевір через git bisect visualize чи є merge-коміт серед кандидатів. Якщо є, протестуй вручну через git checkout -b test-merge <sha>, потім позначай результат у сесії bisect.

Q: Як Git вибирає "середній" коміт?
A: Не ділить кількість комітів навпіл. bisect--helper.c шукає коміт що мінімізує кількість кроків у найгіршому випадку, враховуючи реальну топологію DAG. У репозиторіях з багатьма merge-ами середина може виглядати неочевидно.

Q: Які коди виходу очікує bisect run від скрипта?
A: 0 означає good, від 1 до 127 (крім 125) означає bad, 125 означає пропустити коміт, 128 і вище перериває всю сесію. Код 125 використовуй коли коміт взагалі не можна протестувати.

Q: Яка часова складність git bisect?
A: O(log N) в середньому. Для мільйона комітів це ~20 тестів. Git жадібно вибирає середину кожного разу щоб мінімізувати кількість кроків що залишились, тому навіть нестандартна форма графу не погіршує результат суттєво.

Q: Як звузити bisect до конкретного файлу?
A: git bisect працює на рівні комітів, а не файлів. Спочатку знайди коміти що торкались файлу через git log --follow -- path/to/file, потім використовуй їх як межі good/bad. Для перегляду змін конкретної функції підійде git log -L :functionName:file.js.

Приклади

Просте сховище: пошук поганого коміту крок за кроком

bash
git init bisect-demo && cd bisect-demo # 10 чистих комітів, потім один що ламає все for i in {1..10}; do echo "v$i" > file.txt; git add .; git commit -m "commit $i"; done echo "BUG" >> file.txt && git commit -am "commit 11" git bisect start git bisect bad # commit 11 зламаний git bisect good HEAD~5 # 5 комітів назад - все ок # Git перемикається на ~commit 8 cat file.txt # Немає рядка BUG - добре git bisect good # Git перемикається на ~commit 10 cat file.txt # Немає рядка BUG - добре git bisect good # Git перемикається на commit 11 git bisect bad # Вивід: "commit 11 is the first bad commit" git bisect reset

4 перевірки для 11 комітів. Без bisect перевіряв би кожен.

Продакшн-баг: автоматизований пошук регресії в авторизації

Express-застосунок перестав встановлювати req.user десь між v4.18.0 та v4.19.2. Пишеш тест що виходить з кодом 0 при успіху і 1 при помилці, потім bisect запускає його сам:

bash
cd express git bisect start git bisect bad # v4.19.2 падає на тесті авторизації git bisect good v4.18.0 # v4.18.0 проходив git bisect run node test/auth.test.js # Git запускає тест на кожній середині # Після ~7 ітерацій: "8f4d2a1 is the first bad commit" # git show 8f4d2a1 показує PR що змінив обробку роутів git bisect reset

Не потрібно читати 50 diff-ів вручну. Git звужує, ти читаєш один.

Просунутий рівень: пропуск комітів що не можна протестувати

У довгій історії репозиторію є коміти що не зберуться або мають відсутні залежності. Для них використовуй код виходу 125:

bash
#!/bin/bash # test.sh npm install 2>/dev/null if [ $? -ne 0 ]; then exit 125 # Не можна протестувати цей коміт, пропускаємо fi npm test exit $? # 0 = good, не нуль = bad
bash
git bisect start git bisect bad git bisect good v2.0.0 git bisect run ./test.sh # Пропускає проблемні коміти, все одно знаходить винуватця # "a1b2c3 is the first bad commit" git bisect reset

Цей патерн добре працює в монорепозиторіях де старі коміти передують реструктуризації package.json.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?