українська мова ▾ 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, то побачимо, що багато часу та зусиль витрачається на боротьбу з регресіями. Цикл випусків починається з 2-тижневого вікна злиття. Потім позначається перша версія-кандидат релізу (rc). А після цього з’являється ще приблизно 7 або 8 rc-версій з інтервалом приблизно один тиждень між кожною з них, до остаточного випуску.

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

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

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

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

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

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

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

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

N * M * T tests

де 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-щось", тобто коміт, у якого є рядок "SUBLEVEL = 26" у Makefile верхнього рівня. Це іграшковий приклад, оскільки є кращі способи знайти цей коміт за допомогою Git, ніж використання "git bisect" (наприклад, "git blame" або "git log -S<рядок>").

Ручне перетинання бісекції

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

Якщо користувач керує цим, то на кожному кроці пошуку йому доведеться перевіряти поточний коміт і визначати, чи він "хороший" чи "поганий", використовуючи команди "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

-> time goes this way ->

де 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) скаржилися, що іноді найкращі точки поділу знаходяться в області, де всі коміти неможливо перевірити. І в цьому випадку користувача просили протестувати багато неперевірюваних комітів, що може бути дуже неефективно.

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

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

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

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

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

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

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

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

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

У попередніх прикладах ми припускали, що "хороші" коміти є предками "поганого" коміту. Але це не є вимогою "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, тоді перевірка успішності всіх тестів після кожного коміту стає менш важливою. Хоча, звісно, ймовірно, гарною ідеєю буде мати деякі перевірки, щоб уникнути порушень надто великої кількості речей, оскільки це може ускладнити розділення інших помилок.

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

c * N * T + b * M * log2(M) tests

де 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

# Build errors are not what I am interested in.
make my_app || exit 255

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

./my_app >log 2>&1 &

# ... and grab its process ID.
pid=$!

# ... and then wait for sufficiently long.
sleep $NORMAL_TIME

# ... and then see if the process is still there.
if kill -0 $pid
then
	# It is still running -- that is bad.
	kill $pid; sleep 1; kill $pid;
	exit 1
else
	# It has already finished (the $pid process was no more),
	# and we are happy.
	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 від bisect breaking commit — коміт з розривом пополам), а пізніше виправлений іншим (назвемо його BFC від bisect fixing commit — коміт з розривом пополам).

Наприклад:

...-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" економить йому, коли він його використовує:

a lot.

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

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

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

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

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

So git bisect is unconditional goodness - and feel free to quote that ;-)

Подяки

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

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

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

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

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

Посилання