Добрый день, меня зовут Павел Поляков, я 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, мне есть чем поделиться. Все это нужно разработчику, чтобы делать свою работу хорошо, быть востребованным на рынке и получать высокую компенсацию.
Спасибо ????
Комментарии (10)
warhamster
26.11.2021 00:17Все это намного быстрее чем проверять каждый коммит и искать иголку в стоге сена!
Никто в здравом уме не будет проверять каждый, деление пополам еще в школе же проходят.
Как по мне, весь смысл существования бисекта исключительно в опции run, как раз вот чтобы руками ничего не гонять. Если до нее дело не доходит — то обычно не то, что бисект не нужен, а даже и в консоль идти не надо, проще пару-тройку коммитов в идешечке переключить.PavloPoliakov Автор
26.11.2021 09:17Я думаю это смелое предположение. Куча людей подходят к куче вещей бессистемно. Поиск бага вряд ли является исключением.
KvanTTT
26.11.2021 05:30+1Просматривать свои коммиты в обратном порядке, это реальный пример квадратичного поиска. Сложность квадратичного поиска 0(n^2)
Откуда взялся квадрат? Это линейный поиск, а с бисектом - логарифмический.
PavloPoliakov Автор
26.11.2021 09:13Я думал автор имел в виду просмотр коммита + запуск тестов + накапливающуюся когнитивную сложность. Но гарантий дать не могу, это перевод.
KvanTTT
26.11.2021 13:48Звучит странно, что такое в этом случае N? А при бинарном поиске тогда какая сложность?
PavloPoliakov Автор
26.11.2021 13:59N
все еще количество коммитов. Просто если взять 15 коммитов и все вручную проверить, то на 5-ом начнешь уставать, а к 10-му совсем будешь фрустрирован ????.А если взять бинарный вариант, то знаешь что за 4 раза справишься ????.
Я так себе объясняю идею автора.
KvanTTT
26.11.2021 14:45В среднем все равно нужно будет проверить N / 2 коммитов, т.е. зависимость линейная, автор походу не разбирается в математике.
php7
А как оно работает со слияниями?
Оно найдет коммит, который входит в слияние?
Но это ненадежный показатель работы-неработы.
Или оно будет смотреть на коммиты слияний?
А потом уже как-то искать среди коммитов данного слияния.
То есть вопрос в том, как сортируются коммиты.
fougasse
Как минимум, у bisect есть опции: