Setup and Config
Getting and Creating Projects
Basic Snapshotting
Branching and Merging
Sharing and Updating Projects
Inspection and Comparison
Patching
Debugging
External Systems
Server Admin
Guides
- gitattributes
- Command-line interface conventions
- Everyday Git
- Frequently Asked Questions (FAQ)
- Glossary
- Hooks
- gitignore
- gitmodules
- Revisions
- Submodules
- Tutorial
- Workflows
- All guides...
Administration
Plumbing Commands
- 2.40.1 → 2.54.0 no changes
-
2.40.0
2023-03-12
- 2.29.1 → 2.39.5 no changes
-
2.29.0
2020-10-19
- 2.25.1 → 2.28.1 no changes
-
2.25.0
2020-01-13
- 2.19.3 → 2.24.4 no changes
-
2.19.2
2018-11-21
- 2.19.1 no changes
-
2.19.0
2018-09-10
- 2.14.6 → 2.18.5 no changes
-
2.13.7
2018-05-22
- 2.10.5 → 2.12.5 no changes
-
2.9.5
2017-07-30
- 2.7.6 → 2.8.6 no changes
-
2.6.7
2017-05-05
- 2.3.10 → 2.5.6 no changes
-
2.2.3
2015-09-04
- 2.1.4 no changes
-
2.0.5
2014-12-17
Аннотация
«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, то увидим, что на борьбу с регрессиями тратится много времени и усилий. Цикл выпуска начинается с двухнедельного окна слияния (merge window). Затем помечается первая версия-кандидат на выпуск (rc). И после этого появится ещё около 7 или 8 версий rc с интервалом около одной недели между ними, перед финальным выпуском.
Время между первым выпуском rc и финальным выпуском должно использоваться для тестирования версий rc и борьбы с ошибками и особенно регрессиями. И это время составляет более 80% времени цикла выпуска. Но на этом борьба не заканчивается, так как она, конечно, продолжается и после выпуска.
И вот что говорит Инг Молнар (хорошо известный разработчик ядра Linux) о своём использовании git bisect:
Я наиболее активно использую его во время окна слияний (когда много деревьев сливается в вышестоящий репозиторий и поток ошибок максимален) — и да, были случаи, когда я использовал его несколько раз в день. В среднем примерно раз в день.
Таким образом, разработчики постоянно борются с регрессиями, и, действительно, хорошо известно, что ошибки следует исправлять как можно скорее, сразу после их обнаружения. Именно поэтому важно иметь хорошие инструменты для этой цели.
Другие инструменты для борьбы с регрессиями
Итак, какие инструменты используются для борьбы с регрессиями? Они почти те же, что и для борьбы с обычными ошибками. Единственными специфическими инструментами являются наборы тестов и инструменты, подобные «git bisect».
Наборы тестов очень хороши. Но когда они используются сами по себе, предполагается, что все тесты будут проверяться после каждого коммита. Это означает, что они не очень эффективны, потому что многие тесты запускаются без получения интересного результата, и они страдают от комбинаторного взрыва.
На самом деле проблема в том, что большое программное обеспечение часто имеет множество различных параметров конфигурации, и каждый тестовый пример должен проходить для каждой конфигурации после каждого коммита. Таким образом, если для каждого выпуска у вас есть: N конфигураций, M коммитов и T тестовых примеров, вы должны выполнить:
N * M * T тестов
где N, M и T — всё это растёт с размером вашего программного обеспечения.
Так что очень скоро станет невозможно полностью протестировать всё.
И если некоторые ошибки проскальзывают через ваш набор тестов, вы можете добавить тест в свой набор тестов. Но если вы хотите использовать свой новый улучшенный набор тестов, чтобы найти, где ошибка просочилась, то вам либо придётся эмулировать процесс двоичного поиска, либо, возможно, тупо тестировать каждый коммит в обратном порядке, начиная с имеющегося у вас «плохого» коммита, что может быть очень расточительно.
Обзор «git bisect»
Начало двоичного поиска
Первая подкоманда «git bisect», которую следует использовать — «git bisect start» для начала поиска. Затем должны быть установлены границы, чтобы ограничить пространство коммитов. Обычно это делается путём указания одного «плохого» и хотя бы одного «хорошего» коммита. Их можно передать в начальном вызове «git bisect start» следующим образом:
$ git bisect start [ПЛОХОЙ [ХОРОШИЙ...]]
или они могут быть установлены с помощью:
$ git bisect bad [КОММИТ]
и:
$ git bisect good [КОММИТ...]
где ПЛОХОЙ, ХОРОШИЙ и КОММИТ — это имена, которые могут быть преобразованы в коммит.
Затем «git bisect» переключится на выбранный им коммит и попросит пользователя протестировать его, например:
$ git bisect start v2.6.27 v2.6.25 Bisecting: осталось проверить 10928 редакций после этого (примерно 14 шагов) [2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: чистка с использованием max_low_pfn на 32-бит
Обратите внимание, что пример, который мы будем использовать, на самом деле является игрушечным примером. Мы будем искать первый коммит, который имеет версию типа «2.6.26-something», то есть коммит, который имеет строку «SUBLEVEL = 26» в корневом Makefile. Это игрушечный пример, потому что есть лучшие способы найти этот коммит с помощью Git, чем использовать «git bisect» (например, «git blame» или «git log -S<строка>»).
Управление двоичным поиском вручную
На этом этапе есть в основном 2 способа управления поиском. Он может управляться вручную пользователем или автоматически сценарием или командой.
Если пользователь управляет им вручную, то на каждом шаге поиска пользователь должен протестировать текущий коммит и сказать, является ли он «хорошим» или «плохим», используя соответственно команды «git bisect good» или «git bisect bad», описанные выше. Например:
$ git bisect bad Bisecting: осталось проверить 5480 редакций после этого (примерно 13 шагов) [66c0b394f08fd89236515c1c84485ea712a157be] KVM: устранение злоупотребления file->f_count в kvm
И после ещё нескольких подобных шагов «git bisect» в конце концов найдёт первый плохой коммит:
$ git bisect bad
2ddcca36c8bcfa251724fe342c8327451988be0d — первый плохой коммит
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 редакций после этого (примерно 14 шагов)
[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: чистка с использованием max_low_pfn на 32-бит
$
$ git bisect run grep '^SUBLEVEL = 25' Makefile
running grep ^SUBLEVEL = 25 Makefile
Bisecting: осталось проверить 5480 редакций после этого (примерно 13 шагов)
[66c0b394f08fd89236515c1c84485ea712a157be] KVM: устранение злоупотребления file->f_count в kvm
running grep ^SUBLEVEL = 25 Makefile
SUBLEVEL = 25
Bisecting: осталось проверить 2740 редакций после этого (примерно 12 шагов)
[671294719628f1671faefd4882764886f8ad08cb] V4L/DVB(7879): Добавление поддержки cx18 для mxl5005s
...
...
running grep ^SUBLEVEL = 25 Makefile
Bisecting: осталось проверить 0 редакций после этого (примерно 0 шагов)
[2ddcca36c8bcfa251724fe342c8327451988be0d] Linux 2.6.26-rc1
running grep ^SUBLEVEL = 25 Makefile
2ddcca36c8bcfa251724fe342c8327451988be0d — первый плохой коммит
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 успешно
В этом примере мы передали «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), вы можете получить результат, подобный этому:
Остались только 'пропущенные' коммиты для проверки. Первым плохим коммитом может быть любой из: 15722f2fa328eaba97022898a305ffc8172db6b1 78e86cf3e850bd755bb71831f42e200626fbd1e0 e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace 070eab2303024706f2924822bfec8b9847e4ac1b Мы не можем продолжить двоичный поиск!
Подробности «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 ПЛОХОЙ --not ХОРОШИЙ1 ХОРОШИЙ2...
Также обратите внимание, что мы не требуем, чтобы сохраняемые коммиты были потомками «хорошего» коммита. Таким образом, в следующем примере будут сохранены коммиты 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 ПЛОХОЙ --not ХОРОШИЙ1 ХОРОШИЙ2
выдаст что-то вроде:
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(информация_если_хороший(X), информация_если_плохой(X))
где информация_если_хороший(X) — это информация, которую мы получаем, если X хороший, а информация_если_плохой(X) — это информация, которую мы получаем, если X плохой.
Теперь мы предположим, что существует только один «первый плохой коммит». Это означает, что все его потомки «плохие», а все остальные коммиты «хорошие». И мы предположим, что все коммиты имеют равную вероятность быть хорошими или плохими, или быть первым плохим коммитом, поэтому знание состояния c коммитов всегда даёт одинаковое количество информации, независимо от того, где эти c коммитов находятся на графе и независимо от c. (Таким образом, мы предполагаем, что тот факт, что эти коммиты находятся, например, на ветке или рядом с хорошим или плохим коммитом, не даёт больше или меньше информации).
Предположим также, что у нас есть очищенный граф, как после шага 1) в алгоритме двоичного поиска выше. Это означает, что мы можем измерить получаемую информацию в терминах количества коммитов, которые мы можем удалить из графа.
И давайте возьмём коммит X в графе.
Если X оказывается «хорошим», то мы знаем, что все его предки «хорошие», поэтому мы хотим сказать, что:
информация_если_хороший(X) = количество_предков(X) (ВЕРНО)
И это верно, потому что на шаге 1) b) мы удаляем предков «хороших» коммитов.
Если X оказывается «плохим», то мы знаем, что все его потомки «плохие», поэтому мы хотим сказать, что:
информация_если_плохой(X) = количество_потомков(X) (НЕВЕРНО)
Но это неверно, потому что на шаге 1) a) мы сохраняем только предков плохого коммита. Таким образом, мы получаем больше информации, когда коммит помечается как «плохой», потому что мы также знаем, что предки предыдущего «плохого» коммита, которые не являются предками нового «плохого» коммита, не являются первым плохим коммитом. Мы не знаем, хорошие они или плохие, но мы знаем, что они не являются первым плохим коммитом, потому что они не являются предками нового «плохого» коммита.
Поэтому, когда коммит помечается как «плохой», мы знаем, что можем удалить все коммиты в графе, кроме тех, которые являются предками нового «плохого» коммита. Это означает, что:
информация_если_плохой(X) = N - количество_предков(X) (ВЕРНО)
где N — количество коммитов в (очищенном) графе.
Итак, в итоге это означает, что для поиска наилучших коммитов для двоичного поиска мы должны максимизировать функцию:
f(X) = min(количество_предков(X), N - количество_предков(X))
И это хорошо, потому что на шаге 2) мы вычисляем количество_предков(X), и, следовательно, на шаге 3) мы вычисляем f(X).
Давайте возьмём следующий граф в качестве примера:
G-H-I-J
/ \
A-B-C-D-E-F O
\ /
K-L-M-N
Если мы вычислим на нём следующую неоптимальную функцию:
g(X) = min(количество_предков(X), количество_потомков(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) использовать генератор псевдослучайных чисел (PRNG) для генерации случайного числа от 0 до 1
10) умножить это случайное число на его квадратный корень для смещения к 0
11) умножить результат на количество коммитов в отфильтрованном списке, чтобы получить индекс в этом списке
12) вернуть коммит по вычисленному индексу
Обсуждение алгоритма пропуска
После шага 7) (в алгоритме пропуска) мы могли бы проверить, был ли пропущен второй коммит, и вернуть его, если это не так. И на самом деле это был алгоритм, который мы использовали с момента разработки «git bisect skip» в Git версии 1.5.4 (выпущена 1 февраля 2008 г.) до Git версии 1.6.4 (выпущена 29 июля 2009 г.).
Но Инг Молнар и Х. Питер Анвин (ещё один известный разработчик ядра Linux) оба жаловались, что иногда наилучшие точки для двоичного поиска все оказывались в области, где все коммиты не тестируемы. И в этом случае пользователя просили протестировать много не тестируемых коммитов, что могло быть очень неэффективно.
Действительно, не тестируемые коммиты часто не тестируемы, потому что в какой-то момент была внесена поломка, и эта поломка была исправлена только после того, как было внесено много других коммитов.
Эта поломка, конечно, в большинстве случаев не связана с поломкой, которую мы пытаемся найти в графе коммитов. Но она мешает нам узнать, присутствует ли интересующее нас «плохое поведение» или нет.
Таким образом, факт состоит в том, что коммиты рядом с не тестируемым коммитом имеют высокую вероятность быть самими не тестируемыми. И наилучшие коммиты для двоичного поиска также часто встречаются вместе (из-за алгоритма двоичного поиска).
Вот почему выбирать следующий наилучший непропущенный коммит для двоичного поиска, когда первый был пропущен, — это плохая идея.
Мы обнаружили, что большинство коммитов на графе могут дать довольно много информации, когда они протестированы. А коммиты, которые в среднем не дают много информации, — это те, которые находятся рядом с хорошими и плохими коммитами.
Таким образом, использование PRNG со смещением в пользу коммитов, далёких от хороших и плохих коммитов, выглядело хорошим выбором.
Одним из очевидных улучшений этого алгоритма было бы поиск коммита, который имеет связанное значение, близкое к значению наилучшего коммита для двоичного поиска, и который находится в другой ветке, перед использованием PRNG. Потому что если такой коммит существует, то он вряд ли также будет не тестируемым, поэтому он, вероятно, даст больше информации, чем почти случайно выбранный.
Проверка баз слияния
Существует ещё одна настройка в алгоритме двоичного поиска, которая не была описана в разделе «Алгоритм двоичного поиска» выше.
В предыдущих примерах мы предполагали, что «хорошие» коммиты являются предками «плохого» коммита. Но это не является требованием «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-хешей хороших коммитов.
Если некоторые из баз слияния пропущены, то процесс двоичного поиска продолжается, но для каждой пропущенной базы слияния выводится следующее сообщение:
Предупреждение: база слияния между BBBBBB и [GGGGGG,...] должна быть пропущена. Поэтому мы не можем быть уверены, что первый плохой коммит находится между MMMMMM и BBBBBB. Тем не менее, продолжаем.
где 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) тестов
где c — количество раундов тестирования (то есть небольшая константа), а b — доля ошибок на коммит (надеюсь, тоже небольшая константа).
Таким образом, конечно, это намного лучше, так как это O(N * T) против O(N * T * M), если бы вы тестировали всё после каждого коммита.
Это означает, что наборы тестов хороши для предотвращения попадания некоторых ошибок в коммиты, и они также довольно хороши для того, чтобы сообщить вам, что у вас есть некоторые ошибки. Но они не очень хороши для того, чтобы сказать вам, где были внесены некоторые ошибки. Чтобы эффективно сообщить вам это, необходим git bisect.
Ещё одна приятная особенность наборов тестов заключается в том, что когда он у вас есть, вы уже знаете, как тестировать на наличие плохого поведения. Таким образом, вы можете использовать эти знания для создания нового тестового примера для «git bisect», когда обнаруживается регрессия. Так будет легче выполнить двоичный поиск ошибки и исправить её. А затем вы можете добавить только что созданный тестовый пример в свой набор тестов.
Таким образом, если вы знаете, как создавать тестовые примеры и как выполнять двоичный поиск, вы попадёте в благотворный круг:
больше тестов ⇒ легче создавать тесты ⇒ легче выполнять двоичный поиск ⇒ больше тестов
Таким образом, наборы тестов и «git bisect» являются взаимодополняющими инструментами, которые очень мощны и эффективны при совместном использовании.
Двоичный поиск ошибок сборки
Вы можете очень легко автоматически выполнять двоичный поиск сломанных сборок, используя что-то вроде:
$ git bisect start ПЛОХОЙ ХОРОШИЙ $ git bisect run make
Передача sh -c «некоторые команды» в «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», поскольку небольшие изменения легче проверять, даже если они проверяются только коммиттером.
Ещё одна хорошая идея — иметь хорошие сообщения коммитов. Они могут быть очень полезны для понимания того, почему были сделаны некоторые изменения.
Эти общие лучшие практики очень полезны, если вы часто выполняете двоичный поиск.
Избегание слияний, подверженных ошибкам
Во-первых, сами по себе слияния могут вносить некоторые регрессии, даже когда слияние не требует разрешения конфликтов в исходном коде. Это происходит потому, что семантическое изменение может произойти в одной ветке, в то время как другая ветка не знает о нём.
Например, одна ветка может изменить семантику функции, а другая ветка добавляет больше вызовов той же функции.
Это становится намного хуже, если для разрешения конфликтов приходится исправлять много файлов. Вот почему такие слияния называются «злыми слияниями» (evil merges). Они могут сделать регрессии очень трудными для отслеживания. Это даже может ввести в заблуждение при определении первого плохого коммита, если им окажется такое слияние, потому что люди могут подумать, что ошибка происходит из-за плохого разрешения конфликта, когда она исходит из семантического изменения в одной ветке.
В любом случае «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» теперь использует PRNG, чтобы попытаться избежать областей в графе коммитов, где коммиты не тестируемы. Проблема в том, что иногда первый плохой коммит может находиться в не тестируемой области.
Чтобы упростить обсуждение, мы предположим, что не тестируемая область представляет собой простую цепочку коммитов, и она была создана поломкой, внесённой одним коммитом (назовём его 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/replace/». Эти «ссылки» похожи на ветки (которые хранятся в «refs/heads/») или метки (которые хранятся в «refs/tags»), и это означает, что они могут автоматически совместно использоваться как ветки или метки между разработчиками.
«git replace» — очень мощный механизм. Он может использоваться для исправления коммитов в уже выпущенной истории, например, для изменения сообщения коммита или автора. И он также может использоваться вместо git «grafts» для связывания репозитория с другим старым репозиторием.
На самом деле именно эта последняя функция «продала» его сообществу Git, так что теперь она находится в ветке «master» Git-репозитория Git и должна быть выпущена в Git 1.6.5 в октябре или ноябре 2009 года.
Одна проблема с «git replace» заключается в том, что в настоящее время он хранит все ссылки замены в «refs/replace/», но, возможно, было бы лучше, если бы ссылки замены, которые полезны только для двоичного поиска, находились бы в «refs/replace/bisect/». Таким образом, ссылки замены могли бы использоваться только для двоичного поиска, в то время как другие ссылки непосредственно в «refs/replace/» использовались бы практически постоянно.
Двоичный поиск спорадических ошибок
Другим возможным улучшением «git bisect» было бы добавление некоторой избыточности в выполняемые тесты, чтобы он был более надёжным при отслеживании спорадических ошибок.
Это было запрошено некоторыми разработчиками ядра, потому что некоторые ошибки, называемые спорадическими, появляются не во всех сборках ядра, так как они очень зависят от вывода компилятора.
Идея в том, что, например, каждые 3 теста «git bisect» может попросить пользователя протестировать коммит, который уже был признан «хорошим» или «плохим» (потому что один из его потомков или один из его предков был признан соответственно «хорошим» или «плохим»). Если окажется, что коммит был ранее неправильно классифицирован, то двоичный поиск может быть прерван на ранней стадии, надеюсь, до того, как будет сделано слишком много ошибок. Затем пользователь должен будет посмотреть, что произошло, и затем перезапустить двоичный поиск, используя исправленный журнал двоичного поиска.
Уже существует проект под названием BBChop, созданный Ealdwulf Wuffinga на Github, который делает нечто подобное, используя теорию байесовского поиска [9]:
BBChop похож на git bisect (или эквивалент), но работает, когда ваша ошибка является прерывистой. То есть он работает при наличии ложноотрицательных результатов (когда версия на этот раз работает, даже если содержит ошибку). Он предполагает, что нет ложноположительных результатов (в принципе, тот же подход сработал бы, но его добавление может быть нетривиальным).
Но BBChop не зависит от какой-либо VCS, и для пользователей 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 за выбор автора для выступления с докладом и за публикацию этой статьи.
Ссылки
-
[[[1]]] Ошибки программного обеспечения обходятся экономике США в 59,5 миллиарда долларов ежегодно. Пресс-релиз Nist. См. также Экономические последствия неадекватной инфраструктуры для тестирования программного обеспечения. Отчёт о планировании Nist 02-3, Резюме для руководителей и Глава 8.
-
[[[2]]] Соглашения по оформлению кода для языка программирования Java: 1. Введение. Sun Microsystems.
-
[[[4]]] Джунио Хамано. История успеха автоматического двоичного поиска.
-
[[[5]]] Кристиан Кудер. Полностью автоматизированный двоичный поиск с помощью "git bisect run". LWN.net.
-
[[[6]]] Джонатан Корбет. Двоичный поиск разделяет пользователей и разработчиков. LWN.net.
-
[[[7]]] Инг Молнар. Re: BUG 2.6.23-rc3 не видит sd разделы на Alpha. Список рассылки Linux-kernel.
-
[[[8]]] Джунио Хамано и список рассылки git. Справочная страница git-bisect(1). Архивы ядра Linux.
-
[[[9]]] Ealdwulf. bbchop. GitHub.