Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке git bisect?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**git bisect** знаходить коміт що зламав код через бінарний пошук. Позначаєш один коміт як `good` (працював) і один як `bad` (зламаний), Git сам перевіряє середини поки не залишиться один. ```bash git bisect start git bisect bad # поточний зламаний git bisect good v1.0 # цей працював # ~10 кроків для 1000 комітів git bisect reset ``` **Ключове:** log2(N) тестів замість N.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.