Добрый день, меня зовут Павел Поляков, я Principal Engineer в каршеринг компании SHARE NOW, в Гамбурге в ?? Германии. А еще я автор Telegram-канала Хороший разработчик знает, где рассказываю обо всем, что должен знать хороший разработчик.
Сегодня я хочу поговорить про git bisect
, инструмент, который помогает найти момент, когда появился баг. Считаю, что Middle+
разработчики должны иметь его в своем арсенале. Это перевод оригинальной статьи.
Git bisect: путешествие по времени и багам
Не важно насколько хорошо ваш проект покрыт тестами, вы не можете протестировать все.
Однажды вы создадите регрессию в вашем приложении и не заметите это сразу. А когда вы, наконец, заметите что какая-то фича не работает, то может быть сложно понять почему это произошло. Еще сложнее это сделать, когда над проектом вы работаете не одни.
Самое время познакомиться с git bisect
.
Готовы путешествовать во времени по своему коду?
Не эффективный метод дебага — проверять коммит за коммитом
Один раз ситуация, которую я описал выше, произошла со мной.
Моим первым инстинктом было просмотреть список коммитов и подумать какой из них поломал приложение. В течение часа я продолжал прикидывать что это было, а потом сдался.
Просматривать свои коммиты в обратном порядке, это реальный пример квадратичного поиска. Сложность квадратичного поиска 0(n^2)
, то есть, если количество данных увеличится в 10 раз, то мы на поиск мы потратим в 100 раз больше времени.
Это может сработать, если у вас пара коммитов. Но чем больше коммитов вам нужно проверить, те больше времени это займет, причем это экспоненциальный рост.
С другой стороны, git bisect
использует бинарный поиск для поиска среди ваших коммитов. И это быстрее!
Хорошо, а что такое бинарный поиск?
Бинарный поиск работает с отсортированной информацией. Вместо проверки элементов один за другим, от первого к последнему, алгоритм начинает сразу с элемента в середине.
В зависимости от условий вашего поиска, алгоритм сделает что-то из списка:
вернет текущий элемент
проверит все элементы слева о текущего
проверит все элементы справа от текущего
На каждой итерации алгоритм проверяет средний элемент в списке, который постоянно уменьшается.
TL;DR: бинарный поиск не проверяет каждый элемент в списке, он проверяет только часть. И все равно находит результат.
git bisect
работает аналогично.
Эффективный метод дебага: git bisect
Сейчас ваше приложение не работает как положено. Но мы знаем, что два месяца назад эта фича работала. Какой-то коммит, который мы добавили в течении этого времени внес регрессию.
В чем проблема? Мы не знаем когда это произошло.
Есть один коммит, который разделяет хронологию вашего приложения на до и после:
все что было ДО этого плохого коммита — ваше приложение работало отлично
все что было ПОСЛЕ этого плохого коммита — ваше приложение поломано
git bisect
использует бинарный поиск, чтобы найти коммит, который привел к регрессии.
Он использует самый свежий "плохой" коммит и последний известный "хороший" коммит как диапазон, который нужно проверить.
git bisect
выбирает коммит в середине этого списка и просит вас указать — это "хороший" или "плохой" коммит. Присутствует ли регрессия в этом коммите? Он продолжает сужать список, пока не найдет тот самый коммит, который привел к регрессии.
Давайте попробуем:
➜ my-app git:(main) ✗ git bisect start
➜ my-app git:(main|BISECTING) ✗ git bisect bad
➜ my-app git:(main|BISECTING) ✗ git bisect good ae998022
Bisecting: 4 revisions left to test after this (roughly 4 steps)
[02ca345f3e29217bb6553] Refactor the asset pipeline
Теперь разберем подробно:
git bisect start
запускает режимbisect
git bisect bad
— говоритbisect
, что в текущемHEAD
мы уже наблюдаем регрессиюgit bisect good <commit sha>
— говоритbisect
о последнем "хорошем" коммите, когда приложение работало правильноBisecting: 4 revisions left...
— примерное количество шагов, которые понадобятся[02ca345f3e29217bb6553] Refactor ...
: коммит на который ваш репозиторий указывает сейчас
Вместо того чтобы проверять коммиты один за другим, git bisect
сразу прыгает в середину вашего списка (здесь, коммит 02ca345f3e29217bb6553
) и вы можете проверить, как ваш код работал во время этого коммита. Круто!
Теперь вы можете запустить ваше приложение, запустить тесты. Может быть вручную проверить работает ли фича на локальном окружении. Я повторяю, проверьте вручную есть ли сейчас баг. Это важно!
Почему я настаиваю? Потому что, когда я использовал bisect
впервые, у меня в голове был кандидат на "плохой" коммит. Вместо того, чтобы проверить мое приложение вручную на каждом этапе, я просто говорил bisect
свое мнение, о том есть ли сейчас регрессия. В итоге git bisect
примел меня к тому самому коммиту, который я и подозревал.
Однако, когда я начал использовать git bisect
правильно, я понял, что ошибался. Тот коммит был невиновен. Я быстро нашел настоящего виновника.
Но вернемся к текущему bisect
.
➜ my-app git:(main) ✗ git bisect start
➜ my-app git:(main|BISECTING) ✗ git bisect bad
➜ my-app git:(main|BISECTING) ✗ git bisect good ae998022
Bisecting: 4 revisions left to test after this (roughly 4 steps)
[02ca345f3e29217bb6553] Refactor the asset pipeline
➜ my-app git:((02ca345f3...)|BISECTING) ✗ git bisect bad
Bisecting: 11 revisions left to test after this (roughly 3 steps)
[76c502e15dba8ac5b] Add new feature
Сейчас приложение на коммите 76c502e15dba8ac5b
.
Если сейчас приложение не работает, значит баг был внесен раньше. Когда мы пишем git bisect bad
, git besect
понимает, что сейчас приложение не работает и перемещается на средний коммит "слева" от текущего. То есть идет дальше в прошлое.

Если приложение работает, git bisect good
скажет git bisect
исследовать "правую" часть, коммиты в будущем.

➜ my-app git:(main) ✗ git bisect start
➜ my-app git:(main|BISECTING) ✗ git bisect bad
➜ my-app git:(main|BISECTING) ✗ git bisect good ae998022
Bisecting: 4 revisions left to test after this (roughly 4 steps)
[02ca345f3e29217bb6553] Refactor the asset pipeline
➜ my-app git:((02ca345f3...)|BISECTING) ✗ git bisect bad
Bisecting: 11 revisions left to test after this (roughly 3 steps)
[76c502e15dba8ac5b] Add new feature
➜ my-app git:((76c502e15...)|BISECTING) ✗ git bisect bad
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[e7e6f2ab20a7f9b] Merge branch 'new-payment-system' into 'main'
➜ my-app git:((e7e6f2ab2...)|BISECTING) ✗ git bisect bad
Bisecting: 1 revision left to test after this (roughly 1 step)
[4a6d8943db4e2d] Change CORS
➜ my-app git:((4a6d8943d...)|BISECTING) ✗ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 1 step)
[996e5a376c7b9] Update GEMFILE
➜ my-app git:((a7c40a681...)|BISECTING) ✗ git bisect bad
a7c40a6818c34f1ea1 is the first bad commit
commit a7c40a6818c34f1ea1
Merge: xxx xxx
Author: Remi Mercier
Date: Tue Aug 3 13:51:20 2021 +0000
Отступление: если вам необходимо вручную проверять приложение, то, скорее всего, лучше написать тест. В случае моего бага, я написал интеграционный тест с моками ответов от двух сервисов.
Все это намного быстрее чем проверять каждый коммит и искать иголку в стоге сена!
Если вы не можете проверить коммит который выбрал git bisect
, например в нем приложение вообще не компилировалось, то вы можете использовать git bisect skip
и git bisect
перейдет к другому коммиту.
Хотите узнать больше? Посмотрите официальную документацию по git bisect.
Использовали git bisect
раньше? Пишите в комментариях, мне интересно.
А еще...
Здесь говорю опять я, Павел. В конце еще раз приглашу вас в свой Telegram-канал. На канале Хороший разработчик знает я минимум три раза в неделю простым языком рассказываю про свой опыт, хард скиллы и софт скиллы. Я 15+ лет в IT, мне есть чем поделиться. Все это нужно разработчику, чтобы делать свою работу хорошо, быть востребованным на рынке и получать высокую компенсацию.
Спасибо ?