українська мова ▾ Topics ▾ Latest version ▾ git-bisect-lk2009 last updated in 2.40.0

Анотація

«git bisect» дозволяє користувачам та розробникам програмного забезпечення легко знаходити коміт, який вніс регресію. Ми показуємо, чому важливо мати хороші інструменти для боротьби з регресіями. Ми показуємо, як «git bisect» працює зовні та які алгоритми він використовує всередині. Потім ми пояснюємо, як скористатися перевагами «git bisect» для покращення поточних практик. І ми розглянемо, як «git bisect» може покращитися в майбутньому.

Вступ до "git bisect"

Git — це розподілена система контролю версій (DVCS), створена Лінусом Торвальдсом та підтримувана Джуніо Хамано.

У Git, як і в багатьох інших системах контролю версій (VCS), різні стани даних, якими керує система, називаються комітами. І, оскільки VCS здебільшого використовуються для керування вихідним кодом програмного забезпечення, іноді в деякі коміти вносяться «цікаві» зміни поведінки програмного забезпечення.

Насправді, людей особливо цікавлять коміти, які додають «погану» поведінку, яку називають помилкою або регресією. Їх цікавлять ці коміти, тому що коміт (сподіваємося) містить дуже невеликий набір змін вихідного коду. І набагато легше зрозуміти та правильно виправити проблему, коли потрібно перевірити лише дуже невеликий набір змін, ніж коли ви не знаєте, де шукати спочатку.

Отже, щоб допомогти людям знаходити коміти, які впроваджують «погану» поведінку, було винайдено набір команд «git bisect». І з цього, звичайно, випливає, що мовою «git bisect» коміти, де присутня «цікава поведінка», називаються «поганими» комітами, тоді як інші коміти називаються «хорошими» комітами. А коміт, який впроваджує поведінку, яка нас цікавить, називається «першим поганим комітом». Зверніть увагу, що в просторі комітів, який ми шукаємо, може бути більше одного «першого поганого коміту».

Отже, "git bisect" розроблений, щоб допомогти знайти "перший поганий коміт". І для максимальної ефективності він намагається виконати бінарний пошук.

Огляд боротьби з регресіями

Регресії: велика проблема

Регресії є великою проблемою в індустрії програмного забезпечення. Але важко підтвердити це твердження реальними цифрами.

Існують деякі статистичні дані щодо програмних помилок загалом, наприклад, дослідження NIST 2002 року [1], в якому зазначалося:

Програмні помилки, або помилки, настільки поширені та настільки згубні, що вони коштують економіці США приблизно 59,5 мільярда доларів щорічно, або близько 0,6 відсотка валового внутрішнього продукту, згідно з нещодавно опублікованим дослідженням, замовленим Національним інститутом стандартів і технологій (NIST) Міністерства торгівлі. На національному рівні понад половину витрат несуть користувачі програмного забезпечення, а решту — розробники/постачальники програмного забезпечення. Дослідження також показало, що, хоча всі помилки неможливо усунути, понад третину цих витрат, або приблизно 22,2 мільярда доларів, можна було б усунути завдяки вдосконаленій інфраструктурі тестування, яка дозволяє раніше та ефективніше виявляти та видаляти дефекти програмного забезпечення. Це економія, повʼязана зі збільшенням відсотка (але не 100 відсотків) помилок ближче до стадій розробки, на яких вони зʼявляються. Наразі понад половина всіх помилок не виявляється до "дальшого етапу" процесу розробки або під час використання програмного забезпечення після продажу.

А потім:

Розробники програмного забезпечення вже витрачають приблизно 80 відсотків витрат на розробку на виявлення та виправлення дефектів, і все ж мало які продукти будь-якого типу, окрім програмного забезпечення, постачаються з таким високим рівнем помилок.

Зрештою, висновок почався з таких слів:

Шлях до вищої якості програмного забезпечення  — це значне покращення тестування програмного забезпечення.

Існують й інші оцінки, які стверджують, що 80% витрат, повʼязаних з програмним забезпеченням, припадає на обслуговування [2].

Хоча, згідно з Вікіпедією [3]:

Поширеним уявленням про технічне обслуговування є те, що воно полягає виключно у виправленні помилок. Однак дослідження та опитування, проведені протягом багатьох років, показують, що більша частина (понад 80 %) зусиль, що витрачаються на технічне обслуговування, припадає на дії, які не мають виправного характеру (Pigosky 1997). Це уявлення підкріплюється тим, що користувачі надсилають повідомлення про проблеми, які насправді є пропозиціями щодо вдосконалення функціональних можливостей системи.

Але можна припустити, що вдосконалення наявного програмного забезпечення є дуже дорогим, оскільки потрібно стежити за регресіями. Принаймні, це зробило б вищезазначені дослідження узгодженими між собою.

Звичайно, певне програмне забезпечення розробляється, потім використовується протягом певного часу без значних покращень, а потім зрештою викидається. У цьому випадку, звичайно, регресії можуть не бути великою проблемою. Але з іншого боку, існує багато великого програмного забезпечення, яке постійно розробляється та підтримується протягом років або навіть десятків років багатьма людьми. І оскільки часто є багато людей, які залежать (іноді критично) від такого програмного забезпечення, регресії є справді великою проблемою.

Одним із таких програмних продуктів є ядро Linux. Якщо ж розглянути ядро Linux, то можна побачити, що на боротьбу з регресіями витрачається чимало часу та зусиль. Цикл випуску починається з двотижневого періоду злиття змін. Потім позначається перша версія-кандидат у випуск (rc). Після цього, до остаточного випуску, з’являється ще близько 7–8 версій rc з інтервалом приблизно в один тиждень між кожною з них.

Період між першим кандидатом на випуск та остаточним випуском повинен бути присвячений тестуванню кандидатів та усуненню помилок, а особливо регресій. Цей період становить понад 80 % тривалості циклу випуску. Але на цьому боротьба ще не закінчується, адже вона, звісно, триває й після випуску.

А ось що каже Інго Молнар (відомий розробник ядра Linux) про своє використання git bisect:

Я найактивніше використовую його під час вікна злиття (коли багато дерев обʼєднується в upstream та коли наплив помилок найвищий) — і так, були випадки, коли я використовував його кілька разів на день. В середньому я роблю це приблизно раз на день.

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

Інші інструменти для боротьби з регресіями

Отже, які ж інструменти використовуються для боротьби з регресіями? Вони майже такі ж, як і ті, що використовуються для боротьби зі звичайними помилками. Єдиними специфічними інструментами є набори тестів та інструменти, подібні до "git bisect".

Набори тестів — це чудова річ. Але коли їх використовують окремо, передбачається, що всі тести перевіряються після кожного коміту. Це означає, що вони не надто ефективні, оскільки багато тестів виконуються без будь-якого корисного результату, а також страждають від комбінаторного вибуху.

Насправді проблема полягає в тому, що великі розробники програмного забезпечення часто мають багато різних опцій конфігурації, і кожен тестовий випадок повинен бути успішним для кожної конфігурації після кожного коміту. Тож, якщо у вас є для кожного релізу: N конфігурацій, M комітів та T тестових випадків, вам слід виконати:

N * M * T тестів

де N, M та T зростають із розміром вашого програмного забезпечення.

Тож дуже скоро повністю все протестувати не вдасться.

А якщо деякі помилки прослизнуть крізь ваш набір тестів, ви можете додати до нього новий тест. Але якщо ви хочете використати свій новий, вдосконалений набір тестів, щоб виявити, де саме прослизнула помилка, вам доведеться або імітувати процес бісекції, або, можливо, просто перевіряти кожен коміт у зворотному порядку, починаючи з того «помилкового» коміту, що у вас є, що може виявитися дуже марнотратним.

Огляд "git bisect"

Початок бісекції

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

$ git bisect start [BAD [GOOD...]]

або їх можна встановити за допомогою:

$ git bisect bad [COMMIT]

і:

$ git bisect good [COMMIT...]

де BAD, GOOD та COMMIT — це імена, які можна перетворити на коміт.

Тоді "git bisect" перевірить вибраний коміт і попросить користувача протестувати його, ось так:

$ git bisect start v2.6.27 v2.6.25
Bisecting: 10928 revisions left to test after this (roughly 14 steps)
[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: clean up using max_low_pfn on 32-bit

Зверніть увагу, що приклад, який ми будемо використовувати, є насправді дуже простим: ми шукатимемо перший коміт із версією на кшталт «2.6.26-something», тобто той коміт, у Makefile найвищого рівня якого є рядок «SUBLEVEL = 26». Це простий приклад, оскільки існують кращі способи знайти цей комміт за допомогою Git, ніж використання «git bisect» (наприклад, «git blame» або «git log -S<string>»).

Виконання бісекції вручну

На даний момент існує два основних способи керування пошуком. Він може здійснюватися вручну користувачем або автоматично за допомогою скрипта чи команди.

Якщо користувач керує цим, то на кожному кроці пошуку йому доведеться перевіряти поточний коміт і визначати, чи він "хороший" чи "поганий", використовуючи команди "git bisect good" або "git bisect bad" відповідно, які були описані вище. Наприклад:

$ git bisect bad
Bisecting: 5480 revisions left to test after this (roughly 13 steps)
[66c0b394f08fd89236515c1c84485ea712a157be] KVM: kill file->f_count abuse in kvm

І після ще кількох таких кроків, "git bisect" врешті-решт знайде перший поганий коміт:

$ git bisect bad
2ddcca36c8bcfa251724fe342c8327451988be0d is the first bad commit
commit 2ddcca36c8bcfa251724fe342c8327451988be0d
Author: Linus Torvalds <torvalds@linux-foundation.org>
Date:   Sat May 3 11:59:44 2008 -0700

    Linux 2.6.26-rc1

:100644 100644 5cf82581... 4492984e... M      Makefile

На цьому етапі ми можемо побачити, що робить коміт, перевірити його (якщо він ще не активний) або погратися з ним, наприклад:

$ git show HEAD
commit 2ddcca36c8bcfa251724fe342c8327451988be0d
Author: Linus Torvalds <torvalds@linux-foundation.org>
Date:   Sat May 3 11:59:44 2008 -0700

    Linux 2.6.26-rc1

diff --git a/Makefile b/Makefile
index 5cf8258..4492984 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 VERSION = 2
 PATCHLEVEL = 6
-SUBLEVEL = 25
-EXTRAVERSION =
+SUBLEVEL = 26
+EXTRAVERSION = -rc1
 NAME = Funky Weasel is Jiggy wit it

 # *ДОКУМЕНТАЦІЯ*

А коли ми закінчимо, ми можемо використати "git bisect reset", щоб повернутися до гілки, в якій ми були перед початком бісекції:

$ git bisect reset
Checking out files: 100% (21549/21549), done.
Previous HEAD position was 2ddcca3... Linux 2.6.26-rc1
Switched to branch 'master'

Автоматичне проходження бісекції

Інший спосіб керувати процесом бісекції — це наказати "git bisect" запускати скрипт або команду на кожному кроці бісекції, щоб дізнатися, чи є поточний коміт "хорошим" чи "поганим". Для цього ми використовуємо команду "git bisect run". Наприклад:

$ git bisect start v2.6.27 v2.6.25
Bisecting: 10928 revisions left to test after this (roughly 14 steps)
[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: clean up using max_low_pfn on 32-bit
$
$ git bisect run grep '^SUBLEVEL = 25' Makefile
running grep ^SUBLEVEL = 25 Makefile
Bisecting: 5480 revisions left to test after this (roughly 13 steps)
[66c0b394f08fd89236515c1c84485ea712a157be] KVM: kill file->f_count abuse in kvm
running grep ^SUBLEVEL = 25 Makefile
SUBLEVEL = 25
Bisecting: 2740 revisions left to test after this (roughly 12 steps)
[671294719628f1671faefd4882764886f8ad08cb] V4L/DVB(7879): Adding cx18 Support for mxl5005s
...
...
running grep ^SUBLEVEL = 25 Makefile
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[2ddcca36c8bcfa251724fe342c8327451988be0d] Linux 2.6.26-rc1
running grep ^SUBLEVEL = 25 Makefile
2ddcca36c8bcfa251724fe342c8327451988be0d is the first bad commit
commit 2ddcca36c8bcfa251724fe342c8327451988be0d
Author: Linus Torvalds <torvalds@linux-foundation.org>
Date:   Sat May 3 11:59:44 2008 -0700

    Linux 2.6.26-rc1

:100644 100644 5cf82581... 4492984e... M      Makefile
bisect run success

У цьому прикладі ми передали "grep ^SUBLEVEL = 25 Makefile" як параметр для "git bisect run". Це означає, що на кожному кроці буде запущено передану нами команду grep. І якщо вона завершиться з кодом 0 (це означає успіх), то git bisect позначить поточний стан як "хороший". Якщо ж вона завершиться з кодом 1 (або будь-яким кодом від 1 до 127, включаючи, крім спеціального коду 125), то поточний стан буде позначено як "поганий".

Код виходу між 128 та 255 є спеціальним для "git bisect run". Він змушує його негайно зупинити процес бісекції. Це корисно, наприклад, якщо передана команда виконується занадто довго, оскільки ви можете завершити її сигналом, і це зупинить процес бісекції.

Це також може бути корисним у скриптах, що передаються команді "git bisect run" для "exit 255", якщо виявлено якусь дуже аномальну ситуацію.

Як уникнути комітів, які неможливо перевірити

Іноді трапляється, що поточний стан неможливо перевірити, наприклад, якщо він не компілюється через помилку, яка на той момент цьому перешкоджала. Саме для цього і призначений спеціальний код виходу 125. Він повідомляє команді "git bisect run", що поточний коміт слід позначити як нетестований, а також вибрати та перевірити інший.

Якщо процес бісекції виконується вручну, ви можете використати "git bisect skip" для виконання того ж завдання. (Фактично, спеціальний код виходу 125 змушує "git bisect run" використовувати "git bisect skip" у фоновому режимі.)

Або, якщо ви хочете мати більше контролю, ви можете перевірити поточний стан, використовуючи, наприклад, "git bisect visualize". Це запустить gitk (або "git log", якщо змінна середовища DISPLAY не встановлена), щоб допомогти вам знайти кращу точку бісекції.

У будь-якому разі, якщо у вас є рядок неперевірюваних комітів, може статися так, що регресія, яку ви шукаєте, була введена одним із цих неперевірюваних комітів. У цьому випадку неможливо точно сказати, який коміт вніс регресію.

Отже, якщо ви використали "git bisect skip" (або сценарій запуску завершився зі спеціальним кодом 125), ви могли отримати такий результат:

There are only 'skip'ped commits left to test.
The first bad commit could be any of:
15722f2fa328eaba97022898a305ffc8172db6b1
78e86cf3e850bd755bb71831f42e200626fbd1e0
e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace
070eab2303024706f2924822bfec8b9847e4ac1b
We cannot bisect more!

Збереження журналу та його відтворення

Якщо ви хочете показати іншим людям процес бісекції, ви можете отримати журнал, наприклад:

$ git bisect log > bisect_log.txt

І його можна відтворити за допомогою:

$ git bisect replay bisect_log.txt

Деталі "git bisect"

Алгоритм бісекції

Оскільки коміти Git утворюють спрямований ациклічний граф (DAG), знайти найкращий коміт з бісекцією для тестування на кожному кроці не так просто. У будь-якому разі, Лінус знайшов та реалізував «дійсно дурний» алгоритм, пізніше вдосконалений Джуніо Хамано, який працює досить добре.

Отже, алгоритм, який використовується "git bisect" для знаходження найкращого коміту з бісекцією, коли немає пропущених комітів, такий:

1) зберігати лише ті коміти, які:

a) є предком "поганого" коміту (включно з самим "поганим" комітом), b) не є предками "хорошого" коміту (за винятком "хороших" комітів).

Це означає, що ми позбавляємося нецікавих комітів у DAG.

Наприклад, якщо ми почнемо з такого графа:

G-Y-G-W-W-W-X-X-X-X
	   \ /
	    W-W-B
	   /
Y---G-W---W
 \ /   \
Y-Y     X-X-X-X

-> час рухається в цьому напрямку ->

де B — це «поганий» коміт, «G» — «хороші» коміти, а W, X та Y — інші коміти, після цього першого кроку ми отримаємо наступний граф:

W-W-W
     \
      W-W-B
     /
W---W

Отже, будуть збережені лише коміти W та B. Оскільки коміти X та Y будуть видалені за правилами a) та b) відповідно, а також оскільки коміти G також видаляються за правилом b).

Зверніть увагу для користувачів Git, що це еквівалентно збереженню лише коміту, заданого:

git rev-list BAD --not GOOD1 GOOD2...

Також зверніть увагу, що нам не потрібно, щоб коміти, які зберігаються, були нащадками "хорошого" коміту. Тому в наступному прикладі будуть збережені коміти W та Z:

G-W-W-W-B
   /
Z-Z

2) починаючи з "хороших" кінців графа, повʼязати з кожним комітом кількість його предків плюс один

Наприклад, з наступним графом, де H — це «поганий» коміт, а A та D — деякі батьки деяких «хороших» комітів:

A-B-C
     \
      F-G-H
     /
D---E

це дасть:

1 2 3
A-B-C
     \6 7 8
      F-G-H
1   2/
D---E

3) асоціювати з кожним комітом: min(X, N - X)

де X — це значення, повʼязане з комітом на кроці 2), а N — загальна кількість комітів у графі.

У наведеному вище прикладі ми маємо N = 8, тому це дасть:

1 2 3
A-B-C
     \2 1 0
      F-G-H
1   2/
D---E

4) Найкраща точка поділу — це коміт з найбільшим повʼязаним номером

Отже, у наведеному вище прикладі найкращою точкою бісекції є коміт C.

5) Зверніть увагу, що для пришвидшення роботи алгоритму реалізовано деякі скорочення

Оскільки N нам відоме з самого початку, ми знаємо, що min(X, N - X) не може бути більшим за N/2. Тому, під час кроків 2) та 3), якщо ми повʼяжемо N/2 з комітом, то знатимемо, що це найкраща точка бісекції. Тож у цьому випадку ми можемо просто зупинити обробку будь-якого іншого коміту та повернути поточний коміт.

Налагодження алгоритму бісекції

Для будь-якого графу комітів ви можете побачити кількість, повʼязану з кожним комітом, використовуючи "git rev-list --bisect-all".

Наприклад, для наведеного вище графу, команда типу:

$ git rev-list --bisect-all BAD --not GOOD1 GOOD2

виведе щось на кшталт:

e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace (dist=3)
15722f2fa328eaba97022898a305ffc8172db6b1 (dist=2)
78e86cf3e850bd755bb71831f42e200626fbd1e0 (dist=2)
a1939d9a142de972094af4dde9a544e577ddef0e (dist=2)
070eab2303024706f2924822bfec8b9847e4ac1b (dist=1)
a3864d4f32a3bf5ed177ddef598490a08760b70d (dist=1)
a41baa717dd74f1180abf55e9341bc7a0bb9d556 (dist=1)
9e622a6dad403b71c40979743bb9d5be17b16bd6 (dist=0)

Обговорення алгоритму бісекції

Спочатку визначимо "найкращу точку бісекції". Ми скажемо, що коміт X є найкращою точкою бісекції або найкращим комітом бісекції, якщо знання його стану ("добрий" чи "поганий") дає якомога більше інформації про те, чи є стан коміту "добрим" чи "поганим".

Це означає, що найкращими коммітами з бісекцією є ті, де наступна функція є максимальною:

f(X) = min(information_if_good(X), information_if_bad(X))

де information_if_good(X) — це інформація, яку ми отримуємо, якщо X є добрим, а information_if_bad(X) — це інформація, яку ми отримуємо, якщо X є поганим.

Тепер припустимо, що існує лише один «перший поганий коміт». Це означає, що всі його нащадки є «поганими», а всі інші коміти — «хорошими». Також припустимо, що всі коміти мають однакову ймовірність бути хорошими чи поганими або бути першим поганим комітом, тому знання стану c комітів завжди дає однакову кількість інформації незалежно від того, де саме на графі знаходяться ці c комітів і яким є значення c. (Отже, ми припускаємо, що те, що ці коміти, наприклад, знаходяться у гілці або поблизу хорошого чи поганого коміту, не дає більше чи менше інформації).

Також припустимо, що у нас є очищений граф, як той, що був після кроку 1) у вищезгаданому алгоритмі бісекції. Це означає, що ми можемо виміряти отриману інформацію з точки зору кількості комітів, які можна видалити з графа.

І візьмемо коміт X на графі.

Якщо X виявляється «хорошим», то ми знаємо, що всі його предки «хороші», тому ми хочемо сказати, що:

information_if_good(X) = number_of_ancestors(X)  (TRUE)

І це правда, тому що на кроці 1) b) ми видаляємо предків "хороших" комітів.

Якщо X виявляється «поганим», то ми знаємо, що всі його нащадки «погані», тому ми хочемо сказати, що:

information_if_bad(X) = number_of_descendants(X)  (WRONG)

Але це неправильно, тому що на кроці 1) a) ми зберігаємо лише предків поганого коміту. Тож ми отримуємо більше інформації, коли коміт позначено як "поганий", оскільки ми також знаємо, що предки попереднього "поганого" коміту, які не є предками нового "поганого" коміту, не є першим поганим комітом. Ми не знаємо, чи вони хороші, чи погані, але ми знаємо, що вони не є першим поганим комітом, тому що вони не є предками нового "поганого" коміту.

Отже, коли коміт позначено як "поганий", ми знаємо, що можемо видалити всі коміти в графі, окрім тих, що є предками нового "поганого" коміту. Це означає, що:

information_if_bad(X) = N - number_of_ancestors(X)  (TRUE)

де N — кількість комітів у (очищеному) графі.

Отже, зрештою це означає, що для знаходження найкращих комітів бісекції нам слід максимізувати функцію:

f(X) = min(number_of_ancestors(X), N - number_of_ancestors(X))

І це добре, тому що на кроці 2) ми обчислюємо number_of_ancestors(X) і тому на кроці 3) ми обчислюємо f(X).

Візьмемо для прикладу наступний граф:

            G-H-I-J
           /       \
A-B-C-D-E-F         O
           \       /
            K-L-M-N

Якщо ми обчислимо на ньому таку неоптимальну функцію:

g(X) = min(number_of_ancestors(X), number_of_descendants(X))

отримуємо:

            4 3 2 1
            G-H-I-J
1 2 3 4 5 6/       \0
A-B-C-D-E-F         O
           \       /
            K-L-M-N
            4 3 2 1

але за допомогою алгоритму, який використовується git bisect, ми отримуємо:

            7 7 6 5
            G-H-I-J
1 2 3 4 5 6/       \0
A-B-C-D-E-F         O
           \       /
            K-L-M-N
            7 7 6 5

Отже, ми обрали G, H, K або L як найкращу точку бісекції, що краще, ніж F. Тому що, якщо, наприклад, L поганий, то ми знатимемо не тільки те, що L, M та N погані, але й те, що G, H, I та J не є першим поганим комітом (оскільки ми припускаємо, що є лише один перший поганий коміт, і він має бути предком L).

Отже, поточний алгоритм видається найкращим з можливих, враховуючи те, що ми спочатку припускали.

Алгоритм пропуску

Коли деякі коміти пропущено (за допомогою "git bisect skip"), алгоритм бісекції однаковий для кроків 1) – 3). Але тоді ми використовуємо приблизно такі кроки:

6) сортувати коміт за зменшенням повʼязаного значення

7) якщо перший коміт не був пропущений, ми можемо повернути його та зупинитися тут

8) інакше відфільтрувати всі пропущені коміти у відсортованому списку

9) використовувати генератор псевдовипадкових чисел (ГПВЧ) для генерації випадкового числа від 0 до 1

10) помножте це випадкове число на його квадратний корінь, щоб змістити його до 0

11) помножте результат на кількість комітів у відфільтрованому списку, щоб отримати індекс у цьому списку

12) повернути коміт за обчисленим індексом

Обговорення алгоритму пропуску

Після кроку 7) (в алгоритмі пропуску), ми могли б перевірити, чи був пропущений другий коміт, і повернути його, якщо це не так. І насправді це був алгоритм, який ми використовували з моменту розробки "git bisect skip" у версії Git 1.5.4 (випущеній 1 лютого 2008 року) до версії Git 1.6.4 (випущеної 29 липня 2009 року).

Але Інго Молнар та Х. Пітер Анвін (ще один відомий розробник ядра Linux) скаржилися, що іноді найкращі точки поділу знаходяться в області, де всі коміти неможливо перевірити. І в цьому випадку користувача просили протестувати багато неперевірюваних комітів, що може бути дуже неефективно.

Дійсно, неперевірювані коміти часто є неперевірюваними, тому що поломка була внесена одного разу, і ця поломка була виправлена лише після того, як було введено багато інших комітів.

Звичайно, ця поломка здебільшого не повʼязана з поломкою, яку ми намагаємося знайти в графі комітів. Але вона заважає нам дізнатися, чи присутня цікава "погана поведінка", чи ні.

Отже, це факт, що коміти поруч із неперевірюваним комітом мають високу ймовірність того, що самі будуть неперевірюваними. І найкращі коміти з бісекцією також часто знаходяться разом (завдяки алгоритму бісекції).

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

Ми виявили, що більшість комітів на графі можуть давати досить багато інформації після тестування. А коміти, які в середньому не дають багато інформації, це ті, що знаходяться поруч із хорошими та поганими комітами.

Тож використання генератора випадкових чисел (ГВЧ) з упередженням на користь комітів, віддаляючи їх від хороших та поганих, виглядало гарним вибором.

Одним очевидним покращенням цього алгоритму було б пошук коміта, який має повʼязане значення поблизу одного з найкращих комітів з бісекцією, і який знаходиться на іншій гілці, перед використанням генератора випадкових чисел. Тому що, якщо такий коміт існує, то він навряд чи буде неперевірюваним, тому, ймовірно, надасть більше інформації, ніж майже випадково вибраний.

Перевірка баз злиття

В алгоритмі бесекції є ще одне налаштування, яке не було описано у вищезгаданому розділі "алгоритм бісекції".

У попередніх прикладах ми припускали, що "хороші" коміти є предками "поганого" коміту. Але це не є вимогою "git bisect".

Звісно, «поганий» коміт не може бути предком «хорошого» коміту, адже предки хороших комітів мають бути «хорошими». І всі «хороші» коміти повинні бути пов’язані з «поганим» комітом. Вони не можуть перебувати на гілці, яка не має зв’язку з гілкою «поганого» коміту. Але можливо, що хороший коміт пов’язаний з поганим комітом, але при цьому не є ані його предком, ані його нащадком.

Наприклад, може бути гілка "main" та гілка "dev", яка була відгалужена від гілки main у коміті з іменем "D", ось так:

A-B-C-D-E-F-G  <--main
       \
        H-I-J  <--dev

Коміт "D" називається "базою злиття" для гілок "main" та "dev", оскільки він є найкращим спільним предком для цих гілок при злиття.

Тепер припустимо, що коміт J поганий, а коміт G хороший, і що ми застосовуємо алгоритм бісекції, як це було описано раніше.

Як описано в кроці 1) b) алгоритму бісекції, ми видаляємо всіх предків хороших комітів, оскільки вони також повинні бути хорошими.

Тож у нас залишиться лише:

H-I-J

Але що станеться, якщо перший поганий коміт — це «B», і якщо його було виправлено в гілці «main» комітом «F»?

Результатом такої бісекції було б те, що ми виявили б, що H — це перший поганий коміт, хоча насправді це B. Тож це було б неправильно!

І так, на практиці може трапитися так, що люди, які працюють над однією гілкою, не знають, що люди, які працюють над іншою гілкою, виправили помилку! Також може статися так, що F виправила більше однієї помилки, або що це повернення якоїсь великої розробки, яка не була готова до випуску.

Насправді, команди розробників часто підтримують як гілку розробки, так і гілку обслуговування, і їм було б досить легко, якби "git bisect" працював, коли вони хочуть розділити регресію на гілці розробки, яка не знаходиться на гілці обслуговування. Вони повинні мати можливість розпочати бісекцію, використовуючи:

$ git bisect start dev main

Щоб увімкнути цю додаткову зручну функцію, коли починається бісекція, і деякі хороші коміти не є предками поганого коміта, ми спочатку обчислюємо бази злиття між поганим та хорошим комітами та обираємо ці бази злиття як перші коміти, які будуть перевірені та протестовані.

Якщо трапляється, що одна з баз злиття є поганою, то процес бісекції зупиняється з повідомленням типу:

База обʼєднання BBBBBB є поганою.
Це означає, що помилку між BBBBBB та [GGGGGG,...] було виправлено.

де BBBBBB — це хеш sha1 поганої бази злиття, а [GGGGGG,…​] — це список sha1 хороших комітів, розділений комами.

Якщо деякі з баз злиття пропущено, процес бісекції продовжується, але для кожної пропущеної бази злиття виводиться наступне повідомлення:

Warning: the merge base between BBBBBB and [GGGGGG,...] must be skipped.
So we cannot be sure the first bad commit is between MMMMMM and BBBBBB.
We continue anyway.

де BBBBBB — це хеш sha1 поганого коміту, MMMMMM — це хеш sha1 бази злиття, яка пропускається, а [GGGGGG,…​] — це список sha1 хороших комітів, розділений комами.

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

Найкращі практики бісекції

Використання тестових наборів та git bisect разом

Якщо у вас є набір тестів і ви використовуєте git bisect, то перевіряти, чи всі тести проходять успішно після кожного коміту, стає не так важливо. Хоча, звичайно, варто проводити певні перевірки, щоб уникнути надмірної кількості збіїв, оскільки це може ускладнити пошук інших помилок за допомогою bisect.

Ви можете зосередити свої зусилля на перевірці кількох моментів (наприклад, rc та бета-релізів), чи всі T-тести проходять успішно для всіх N-конфігурацій. А коли деякі тести не проходять успішно, ви можете використовувати "git bisect" (або краще "git bisect run"). Отже, вам слід виконати приблизно так:

c * N * T + b * M * log2(M) тестів

де c — кількість раундів тестування (тож це невелика константа), а та b — співвідношення кількості помилок на коміт (сподіваємося, що це також невелика константа).

Тож, звісно, набагато краще, оскільки це O(N * T) проти O(N * T * M), якщо ви тестуватимете все після кожного коміту.

Це означає, що тестові набори добре підходять для запобігання появі деяких помилок, а також вони досить добре підходять для того, щоб сказати вам, що у вас є деякі помилки. Але вони не настільки добре підходять для того, щоб сказати вам, де саме були впроваджені ті чи інші помилки. Щоб ефективно сказати вам про це, потрібен git bisect.

Ще одна приємна річ із наборами тестів полягає в тому, що коли у вас є один, ви вже знаєте, як тестувати на погану поведінку. Тож ви можете використовувати ці знання для створення нового тестового випадку для "git bisect", коли зʼявляється регресія. Таким чином, буде легше розділити помилку та виправити її. А потім ви можете додати щойно створений тестовий випадок до свого набору тестів.

Отже, якщо ви знаєте, як створювати тестові випадки та як застосовувати метод бісекції, ви потрапите у замкнуте коло:

більше тестів ⇒ легше створювати тести ⇒ легше робити бісекцію ⇒ більше тестів

Отже, набори тестів і «git bisect» — це взаємодоповнювальні інструменти, які при спільному використанні виявляються дуже потужними та ефективними.

Виявлення причин невдалих збірок

Ви можете дуже легко автоматично перевіряти непрацюючі збірки методом бісекції, використовуючи щось на зразок:

$ git bisect start BAD GOOD
$ git bisect run make

Передача sh -c "some commands" до "git bisect run"

Наприклад:

$ git bisect run sh -c "make || exit 125; ./my_app | grep 'good output'"

З іншого боку, якщо ви робите це часто, то варто мати скрипти, щоб уникнути надмірного набору тексту.

Пошук регресій продуктивності

Ось приклад сценарію, дещо зміненого з реального сценарію, який використовує Джуніо Хамано [4].

Цей скрипт можна передати команді "git bisect run", щоб знайти коміт, який вніс регресію продуктивності:

#!/bin/sh

# Мене не цікавлять помилки компіляції.
make my_app || exit 255

# Ми перевіряємо, чи зупиниться воно за прийнятний проміжок часу, тому
# дозвольте йому працювати у фоновому режимі...

./my_app >log 2>&1 &

# ... і отримати ідентифікатор цього процесу.
pid=$!

# ... а потім почекати достатньо довго.
sleep $NORMAL_TIME

# ... а потім перевірити, чи процес все ще працює.
if kill -0 $pid
then
	# Він все ще працює — це погано.
	kill $pid; sleep 1; kill $pid;
	exit 1
else
	# Все вже завершилося (процес із $pid більше не існує),
 	# і ми задоволені.
	exit 0
fi

Дотримання загальних найкращих практик

Зрозуміло, що гарною ідеєю не є створення комітів зі змінами, які свідомо ламають систему, навіть якщо деякі інші коміти пізніше виправлять ці поломки.

Також гарною ідеєю при використанні будь-якої системи контролю версій (VCS) є мати лише одну невелику логічну зміну в кожному коміті.

Чим менші зміни у вашому коміті, тим ефективнішим буде "git bisect". І вам, ймовірно, знадобиться "git bisect" рідше, оскільки невеликі зміни легше переглянути, навіть якщо їх переглядає лише комітер.

Ще одна гарна ідея — мати гарні повідомлення про коміти. Вони можуть бути дуже корисними для розуміння, чому були внесені деякі зміни.

Ці загальні рекомендації дуже корисні, якщо ви часто робите бісекцію.

Уникнення злиттів, схильних до помилок

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

Наприклад, одна гілка може змінити семантику функції, тоді як інша гілка додасть більше викликів до тієї ж функції.

Ситуація значно ускладнюється, якщо для усунення конфліктів доводиться виправляти багато файлів. Саме тому такі злиття називають «злиттями-лиходіями». Вони можуть значно ускладнити виявлення регресій. Навіть знання про перший помилковий коміт може ввести в оману, якщо це саме таке злиття, адже користувачі можуть помилково вважати, що помилка виникла через неправильне вирішення конфлікту, хоча насправді вона пов’язана із семантичною зміною в одній із гілок.

У будь-якому разі, команду «git rebase» можна використовувати для вирівнювання історії. Це можна зробити або для того, щоб взагалі уникнути злиття, або для того, щоб проводити бісекцію на лінійній історії замість нелінійної, оскільки це має надати більше інформації у разі семантичних змін в одній з гілок.

Злиття також можна спростити, використовуючи менші гілки або багато тематичних гілок замість лише довгих гілок, повʼязаних з версіями.

А тестування можна проводити частіше у спеціальних гілках інтеграції, таких як linux-next для ядра Linux.

Адаптація вашого робочого процесу

Спеціальний робочий процес для обробки регресій може дати чудові результати.

Ось приклад робочого процесу, який використовує Андреас Ерікссон:

  • написати в наборі тестів тестовий скрипт, який відображає регресію

  • використовуйте "git bisect run", щоб знайти коміт, який її запровадив

  • виправити помилку, яка часто стає очевидною на попередньому кроці

  • зафіксуйте як виправлення, так і тестовий скрипт (і, якщо потрібно, більше тестів)

А ось що Андреас сказав про цей робочий процес [5]:

Якщо говорити про конкретні цифри, раніше середній цикл від повідомлення про помилку до її виправлення становив 142,6 годин (за даними нашого дещо дивного системи відстеження помилок, яка вимірює лише реальний час). З моменту переходу на Git ми скоротили цей показник до 16,2 годин. Головним чином тому, що тепер ми можемо тримати виправлення багів під контролем, і тому, що всі змагаються за можливість виправити баги (ми досить пишаємося тим, як ліниві ми стали, дозволивши Git знаходити баги за нас). Кожен новий реліз приносить на ~40% менше багів (майже напевно завдяки тому, як ми тепер ставимося до написання тестів).

Очевидно, що цей робочий процес використовує замкнене коло між наборами тестів та "git bisect". Фактично, це робить його стандартною процедурою для роботи з регресією.

В інших повідомленнях Андреас каже, що вони також використовують "найкращі практики", описані вище: невеликі логічні коміти, тематичні розгалуження, відсутність злиття з метою покращення бісекційності графа комітів, роблячи його легшим та кориснішим.

Отже, хороший робочий процес має бути розроблений навколо вищезазначених пунктів. Це робить бісекцію простішою, кориснішою та стандартнішою.

Залучення фахівців з контролю якості та, якщо можливо, кінцевих користувачів

Одна перевага "git bisect" полягає в тому, що це не лише інструмент для розробників. Його можуть ефективно використовувати фахівці з контролю якості або навіть кінцеві користувачі (якщо вони мають доступ до вихідного коду або до всіх збірок).

У списку розсилки ядра Linux в якийсь момент точилася дискусія про те, чи нормально завжди просити кінцевого користувача робити бісекцію, і були наведені дуже вагомі аргументи на підтримку точки зору, що це нормально.

Наприклад, Девід Міллер написав [6]:

Люди не розуміють, що це ситуація, де застосовується «принцип кінцевого вузла». Коли у вас обмежені ресурси (тут: розробники), ви не перекладаєте основну частину навантаження на них. Натомість ви перекладаєте завдання на ресурс, якого у вас багато, — кінцеві вузли (тут: користувачі), щоб ситуація фактично масштабувалася.

Це означає, що часто «дешевше», якщо це можуть зробити фахівці з контролю якості або кінцеві користувачі.

Цікаво також те, що кінцеві користувачі, які повідомляють про помилки (або фахівці з контролю якості, які відтворили помилку), мають доступ до середовища, де виникає помилка. Тому вони часто можуть легше відтворити регресію. А якщо вони можуть виконати бісекцію, то з середовища, де виникає помилка, буде отримано більше інформації, а це означає, що помилку буде легше зрозуміти, а потім виправити.

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

Використання складних скриптів

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

Ось що каже Інґо Молнар з цього приводу [7]:

У мене є повністю автоматизований скрипт для бісекції при зависанні системи під час завантаження. Він базується на команді «git-bisect run». Я запускаю скрипт, він повністю автоматично компілює та завантажує ядра, і коли завантаження завершується невдало (скрипт помічає це через послідовний журнал, який він постійно відстежує, або через тайм-аут: якщо система не запускається протягом 10 хвилин, це «погане» ядро), скрипт привертає мою увагу звуковим сигналом, і я перезавантажую тестовий комп’ютер. (так, мені слід використовувати керовану розетку, щоб автоматизувати процес на 100%)

Поєднання тестових наборів, git bisect та інших систем

Ми бачили, що тестові набори та git bisect є дуже потужними, коли використовуються разом. Вони можуть бути ще потужнішими, якщо ви зможете поєднати їх з іншими системами.

Наприклад, деякі набори тестів можуть запускатися автоматично вночі з деякими незвичайними (або навіть випадковими) конфігураціями. А якщо набір тестів виявляє регресію, то "git bisect" може бути автоматично запущений, а його результат може бути надісланий електронною поштою автору першого поганого коміту, знайденого "git bisect", і, можливо, іншим людям також. А також може бути автоматично створений новий запис у системі відстеження помилок.

Майбутнє бісекції

"git replace"

Раніше ми бачили, що "git bisect skip" тепер використовує генератор випадкових чисел (PNS), щоб спробувати уникнути областей у графі комітів, де коміти неможливо перевірити. Проблема полягає в тому, що іноді перший поганий коміт знаходиться в області, яку неможливо перевірити.

Для спрощення розмови припустимо, що неперевірена ділянка — це простий ланцюжок комітів, який утворився внаслідок збою, спричиненого одним комітом (назвемо його BBC — «бісекційний збійний коміт»), а згодом виправлений іншим (назвемо його BFC — «бісекційний виправний коміт»).

Наприклад:

...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z-...

де ми знаємо, що Y — хороший варіант, а BFC — поганий, і де BBC та X1–X6 неперевірювані.

У цьому випадку, якщо ви виконуєте бісекцію вручну, можна створити спеціальну гілку, яка починається безпосередньо перед BBC. Першим комітом у цій гілці має бути BBC, до якого було об’єднано BFC. А інші коміти в гілці мають бути комітами між BBC і BFC, перебазованими на перший коміт гілки, а також комітом після BFC, який також перебазується на ньому.

Наприклад:

      (BBC+BFC)-X1'-X2'-X3'-X4'-X5'-X6'-Z'
     /
...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z-...

де коміти, зазначені ', були перебазовані.

Ви можете легко створити таку гілку за допомогою Git, використовуючи інтерактивне перебазування.

Наприклад, використовуючи:

$ git rebase -i Y Z

а потім переміщення BFC після BBC та його знищення.

Після цього ви можете почати розбирати гілку як завжди в новій гілці, і врешті-решт ви повинні знайти перший поганий коміт.

Наприклад:

$ git bisect start Z' Y

Якщо ви використовуєте "git bisect run", ви можете використати те саме ручне виправлення, що й вище, а потім запустити ще один "git bisect run" у спеціальній гілці. Або, як зазначено на сторінці довідки "git bisect", скрипт, переданий до "git bisect run", може застосувати латку перед компіляцією та тестуванням програмного забезпечення [8]. Латка має перетворити поточні неперевірювані коміти на тестові. Таким чином, тестування призведе до "хороших" або "поганих", і "git bisect" зможе знайти перший поганий коміт. І скрипт не повинен забувати видалити латку після завершення тестування перед виходом зі скрипта.

(Зверніть увагу, що замість латки ви можете використовувати "git cherry-pick BFC" для застосування виправлення, і в цьому випадку вам слід використовувати "git reset --hard HEAD^", щоб скасувати cherry-pick після тестування та перед поверненням зі скрипта.)

Але вищезазначені способи обходу нетестованих областей трохи незграбні. Використання спеціальних гілок є зручним, оскільки розробники можуть використовувати ці гілки спільно, як звичайні гілки, але є ризик того, що люди отримають багато таких гілок. І це порушує нормальний робочий процес "git bisect". Отже, якщо ви хочете використовувати "git bisect run" повністю автоматично, вам потрібно додати спеціальний код у ваш скрипт, щоб перезапустити бісекцію у спеціальних гілках.

У будь-якому разі, у наведеному вище прикладі спеціальної гілки можна помітити, що коміти Z' та Z повинні вказувати на той самий стан вихідного коду (те саме "дерево" мовою git). Це тому, що Z' є результатом застосування тих самих змін, що й Z, просто в дещо іншому порядку.

Отже, якби ми могли просто "замінити" Z на Z' під час бісекції, то нам не потрібно було б нічого додавати до скрипта. Це б працювало для будь-кого в проєкті, хто спільно використовує спеціальні гілки та заміни.

З наведеним вище прикладом це дасть:

      (BBC+BFC)-X1'-X2'-X3'-X4'-X5'-X6'-Z'-...
     /
...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z

Ось чому була створена команда "git replace". Технічно вона зберігає заміни "refs" в ієрархії "refs/replace/". Ці "refs" схожі на гілки (які зберігаються в "refs/heads/") або теги (які зберігаються в "refs/tags"), а це означає, що їх можна автоматично використовувати як гілки або теги між розробниками.

"git replace" — це дуже потужний механізм. Його можна використовувати для виправлення комітів у вже випущеній історії, наприклад, для зміни повідомлення коміта або автора. А також його можна використовувати замість git "grafts" для звʼязування репозиторію з іншим старим репозиторієм.

Фактично, саме ця остання функція «продала» його спільноті Git, тому зараз він знаходиться в «головній» гілці репозиторію Git і має бути випущений у Git 1.6.5 у жовтні або листопаді 2009 року.

Одна з проблем з "git replace" полягає в тому, що наразі він зберігає всі посилання на заміни в "refs/replace/", але, можливо, було б краще, якби посилання на заміни, корисні лише для бісекції, знаходилися в "refs/replace/bisect/". Таким чином, посилання на заміни можна було б використовувати лише для бісекції, тоді як інші посилання безпосередньо в "refs/replace/" використовувалися б майже постійно.

Виявлення поодиноких помилок

Ще одним можливим покращенням "git bisect" було б додавання певної надмірності до виконаних тестів, щоб зробити відстеження спорадичних помилок надійнішим.

Це було запропоновано деякими розробниками ядра, оскільки деякі помилки, які називаються спорадичними помилками, не зʼявляються у всіх збірках ядра, оскільки вони дуже залежать від виводу компілятора.

Ідея полягає в тому, що кожні 3 тести, наприклад, "git bisect", можуть попросити користувача протестувати коміт, який вже був визнаний "хорошим" або "поганим" (оскільки один з його нащадків або один з його предків був визнаний "хорошим" або "поганим" відповідно). Якщо трапляється, що коміт раніше був неправильно класифікований, то бісекцію можна перервати раніше, сподіваємося, перш ніж буде допущено забагато помилок. Тоді користувачеві доведеться подивитися, що сталося, а потім перезапустити бісекцію, використовуючи фіксований журнал бісекції.

На Github вже існує проєкт під назвою BBChop, створений Еальдвульфом Вуффінгою, який робить щось подібне, використовуючи баєсівську теорію пошуку [9]:

BBChop схожий на «git bisect» (або еквівалент), але працює, коли ваша помилка виникає періодично. Тобто, він працює за наявності хибнонегативних результатів (коли версія цього разу працює, навіть якщо вона містить помилку). Він припускає, що хибнопозитивних результатів немає (в принципі, той самий підхід спрацює, але його додавання може бути нетривіальним).

Але BBChop незалежний від будь-якої системи керування версіями, і користувачам Git було б простіше мати щось інтегроване в Git.

Висновок

Ми бачили, що регресії є важливою проблемою, і що "git bisect" має чудові функції, які дуже добре доповнюють практики та інші інструменти, особливо набори тестів, які зазвичай використовуються для боротьби з регресіями. Але, можливо, знадобиться змінити деякі робочі процеси та (погані) звички, щоб отримати від цього максимум користі.

Деякі покращення алгоритмів всередині "git bisect" можливі, а деякі нові функції можуть допомогти в деяких випадках, але загалом "git bisect" вже працює дуже добре, часто використовується та вже дуже корисний. Щоб підтвердити це останнє твердження, давайте надамо останнє слово Інго Молнару, коли автор запитав його, скільки часу, на його думку, "git bisect" економить йому, коли він його використовує:

багато.

Близько десяти років тому я вперше «розбив» чергу латок Linux. Це було ще до появи Git (і навіть до появи BitKeeper). Я буквально днями сортував латки, створюючи, по суті, окремі коміти, які, як я здогадувався, повʼязані з цією помилкою.

Це був крайній захід. Я краще кілька днів просиджу, переглядаючи вивід printk, ніж займатися ручним «бісекційним пошуком».

З Git bisect це дуже просто: у найкращому випадку я можу виконати приблизно 15-крокове розбиття ядра на дві частини за 20-30 хвилин автоматизованим способом. Навіть з ручною допомогою або при розбиття кількох помилок, що перекриваються, це рідко займає більше години.

Насправді, це безцінно, бо є помилки, які я б ніколи не намагався налагоджувати, якби не git bisect. У минулому були шаблони помилок, які я одразу ж не міг налагодити — у кращому випадку я міг надіслати сигнатуру збою/помилки до lkml і сподіватися, що хтось інший щось придумає.

І навіть якщо сьогодні метод бісекції не спрацює, це все одно дає нам цінну інформацію про помилку: вона є недетермінованою — тобто залежить від часу виконання або структури образу ядра.

Отже, git bisect — це беззаперечна користь — і сміливо цитуйте це ;-)

Подяки

Велика подяка Джуніо Хамано за допомогу в рецензуванні цієї статті, за рецензування латок, які я надіслав до списку розсилки Git, за обговорення деяких ідей та допомогу в їх покращенні, за значне покращення "git bisect" та за його чудову роботу з підтримки та розвитку Git.

Велика подяка Інґо Молнару за надану мені дуже корисну інформацію, яка зʼявляється в цій статті, за коментарі до неї, за його пропозиції щодо покращення "git bisect" та за поширення "git bisect" у списках розсилки ядра Linux.

Велика подяка Лінусу Торвальдсу за винахід, розробку та поширення "git bisect", Git та Linux.

Велика подяка багатьом іншим чудовим людям, які так чи інакше допомогли мені, коли я працював над Git, особливо Андреасу Еріксону, Йоганнесу Шінделіну, Х. Пітеру Анвіну, Даніелю Баркалоу, Біллу Ліру, Джону Хоулі, Шону О. Пірсу, Джеффу Кінгу, Сему Вілену, Джону Сеймуру.

Велика подяка програмному комітету Linux-Kongress за вибір автора для виступу з доповіддю та за публікацію цієї статті.