Пошук уроків, статей та іншого контенту
Автоматизуєте бінарний пошук проблемного коміту між відомими справним і несправним станами.
git bisectgit bisect знаходить перший коміт, у якому з’явилася помилка. Він використовує бінарний пошук:
відомий коміт, де все працює;
відомий коміт, де помилка вже присутня;
Git перевіряє коміт приблизно посередині між ними;
після результату відкидає половину історії;
повторює процес, доки не залишиться проблемний коміт.
Якщо між справним і несправним станами є N комітів, зазвичай потрібно близько log₂(N) перевірок, а не перевіряти коміти послідовно.
Припустімо:
поточний коміт містить помилку;
коміт v1.4.0 відомо працює правильно.
Спочатку переконайтеся, що робоче дерево чисте:
git statusЗапустіть пошук:
git bisect start
git bisect bad
git bisect good v1.4.0Після цього Git перемкне робоче дерево на проміжний коміт. Стан HEAD зазвичай буде detached HEAD — це нормально для bisect.
Перевірте програму на поточному коміті. Якщо помилки немає:
git bisect goodЯкщо помилка є:
git bisect badGit вибере наступний коміт для перевірки. Повторюйте перевірку та команди good або bad, доки Git не виведе результат на кшталт:
abc1234 is the first bad commitПісля завершення поверніться до початкової гілки:
git bisect resetgit bisect reset не скасовує знайдений результат і не змінює історію. Він лише повертає HEAD до стану, у якому пошук було розпочато.
git bisect runЯкщо перевірку можна виразити командою з кодом завершення, пошук можна автоматизувати:
git bisect start HEAD v1.4.0
git bisect run ./check.sh
git bisect resetУ цьому прикладі:
HEAD — несправний коміт;
v1.4.0 — справний коміт;
./check.sh перевіряє поточний стан проєкту.
Git запускає скрипт для кожного вибраного коміту.
Для git bisect run важливий код завершення команди:
0 — поточний коміт справний, тобто good;
ненульовий код, крім 125, — поточний коміт несправний, тобто bad;
125 — цей коміт неможливо перевірити, його потрібно пропустити.
Код 125 потрібен, коли коміт не збирається через незалежну від помилки причину або тест не можна виконати в цьому стані.
Нехай у кожній версії проєкту є файл app.js, який має виводити число 42.
Створіть скрипт check.sh:
#!/usr/bin/env bash
set -u
# Запускаємо програму та вважаємо помилкою будь-яке падіння процесу.
actual="$(node app.js 2>/dev/null)" || exit 1
# Код 0 означає, що поточний коміт справний.
if [[ "$actual" == "42" ]]; then
exit 0
fi
# Будь-який інший результат означає наявність помилки.
exit 1Зробіть скрипт виконуваним:
chmod +x check.shТепер запустіть пошук:
git bisect start HEAD v1.4.0
git bisect run ./check.sh
git bisect resetЯкщо програма в одному з комітів почала повертати 41 замість 42, скрипт завершиться з кодом 1, і Git позначить цей коміт як несправний.
Скрипт має перевіряти саме поведінку, яка вважається правильною. Простого успішного завершення програми часто недостатньо: програма може не падати, але повертати неправильні дані.
Для проєкту з тестами команда може бути коротшою:
git bisect start HEAD v1.4.0
git bisect run npm test
git bisect resetЦе працює, якщо:
код 0 означає успішне проходження тестів;
ненульовий код означає провал тестів;
тестова команда не змінює файли в репозиторії;
результат тесту стабільний для одного й того самого коміту.
Перед запуском варто окремо перевірити команду:
npm test
echo $?Якщо тестовий раннер повертає ненульовий код через проблеми середовища, а не через дефект у коді, bisect отримає неправильний результат.
Іноді проміжний коміт неможливо перевірити:
залежність недоступна або має несумісну версію;
проєкт не збирається з причини, яка не пов’язана з досліджуваною помилкою;
тест залежить від зовнішнього сервісу;
потрібні дані або конфігурація відсутні.
У ручному режимі такий коміт можна пропустити:
git bisect skipGit продовжить пошук в іншій частині історії. Якщо пропущених комітів багато, результат може бути менш точним: Git іноді поверне діапазон можливих комітів замість одного гарантованого коміту.
В автоматичному скрипті використовуйте код 125:
#!/usr/bin/env bash
set -u
if [[ ! -f test-fixture.json ]]; then
# Коміт неможливо перевірити через відсутні тестові дані.
exit 125
fi
npm testНе використовуйте 125 для звичайного провалу тесту. Якщо тест показав помилку в програмі, потрібно повертати ненульовий код, наприклад 1.
Позначення good і bad стосуються не якості всього коміту, а конкретної перевірки.
Наприклад, коміт може бути:
good щодо помилки авторизації;
bad щодо витоку пам’яті;
непридатним для перевірки через відсутність потрібної інфраструктури.
Тому перед пошуком чітко сформулюйте умову:
Коміт є несправним, якщо після запуску
npm test -- authтестlogin with expired tokenзавершується з помилкою.
Такий тест дає bisect надійний критерій.
Можна використовувати більше одного справного коміту:
git bisect start HEAD v1.4.0 v1.3.5Це повідомляє Git, що перелічені коміти є справними, а HEAD — несправним. Додаткові опорні точки можуть звузити область пошуку.
Після завершення Git показує перший коміт, який вважає несправним. Подивіться його зміни:
git show --stat <commit>
git show <commit>Порівняйте його з батьківським комітом:
git diff <commit>^ <commit>Корисно також перевірити сусідні стани:
git checkout <commit>^
./check.sh
git checkout <commit>
./check.shПісля ручної перевірки поверніться до початкової гілки:
git bisect resetЗнайдений коміт є першим несправним лише за умови, що:
справний і несправний опорні коміти визначено правильно;
перевірка була однаковою для всіх комітів;
критерій good/bad не змінювався під час пошуку;
пропущених або непридатних комітів не було, або їхній вплив враховано.
git bisect працює з графом комітів, а не лише з лінійною історією. У репозиторії зі злиттями Git може перевірити merge-коміт або коміти з різних бічних гілок.
Якщо помилка пов’язана з конкретною гілкою, перевірте:
чи справний опорний коміт справді є предком несправного;
чи не було помилку внесено під час merge;
чи не залежить результат від порядку злиття;
чи тестується саме та конфігурація, у якій проявляється проблема.
Коли знайдений коміт є merge-комітом, git show може не відразу пояснити причину. Потрібно окремо порівняти merge-коміт з кожним із його батьків і перевірити зміни, які прийшли з відповідної гілки.
Перед запуском bisect збережіть або закомітьте локальні зміни:
git status
git stash push -u -m "temporary changes before bisect"git bisect перемикає робоче дерево між різними комітами. Незбережені зміни можуть:
перешкодити перемиканню;
залишитися поверх кожного перевірюваного коміту;
вплинути на результат тесту;
приховати різницю між версіями.
Після завершення пошуку відновіть зміни:
git stash popЯкщо скрипт створює файли, переконайтеся, що вони не впливають на наступні перевірки. За потреби очищайте тимчасові дані на початку або в кінці кожного запуску.
Бінарний пошук припускає, що властивість змінюється приблизно один раз:
good good good bad bad badЯкщо помилка з’явилася, потім зникла, а пізніше знову з’явилася:
good bad good badпростого пошуку першого bad може бути недостатньо. Так само проблеми виникають, якщо тест має випадковий результат, залежить від часу або звертається до нестабільного сервісу.
Щоб підвищити надійність:
запускайте тест у контрольованому середовищі;
вимикайте випадковість або фіксуйте seed;
не залежте від поточного часу;
використовуйте локальні тестові дані;
перевіряйте результат кілька разів, якщо тест нестабільний;
не змінюйте критерій під час одного сеансу bisect.
good і badЯкщо позначити справний коміт як bad, Git знайде неправильний результат.
Перед стартом перевірте обидві точки вручну:
git checkout v1.4.0
./check.sh
git checkout main
./check.shПісля цього поверніться до початкового стану та запустіть bisect.
git bisect resetПісля завершення пошуку HEAD може залишатися на знайденому коміті в detached HEAD. Виконайте:
git bisect resetІнакше можна випадково створити коміт не в потрібній гілці.
Скрипт може виводити повідомлення про помилку, але завершуватися з кодом 0. Для git bisect run має значення саме код завершення, а не текст у stdout або stderr.
Перевіряйте його явно:
./check.sh
echo $?Якщо однаковий коміт іноді позначається як good, а іноді як bad, бінарний пошук втрачає сенс. Спочатку стабілізуйте тест або повертайте 125, коли перевірка неможлива.
Широкий набір тестів може впасти через іншу несправність. Перевірка для bisect має бути якомога ближчою до конкретної помилки, яку потрібно локалізувати.
git bisect знаходить перший коміт, що змінив результат перевірки зі справного на несправний.
Пошук починається з одного good і одного bad коміту.
У ручному режимі використовуйте git bisect good, git bisect bad і git bisect skip.
Для автоматизації використовуйте git bisect run.
Код 0 означає good, ненульовий код — bad, а 125 — skip.
Перевірка має бути детермінованою, вузькою та незалежною від побічних факторів.
Після пошуку завжди виконуйте git bisect reset.