Добрый день, меня зовут Павел Поляков, я 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)


  1. php7
    25.11.2021 13:41

    А как оно работает со слияниями?

    Оно найдет коммит, который входит в слияние?

    Но это ненадежный показатель работы-неработы.

    Или оно будет смотреть на коммиты слияний?

    А потом уже как-то искать среди коммитов данного слияния.

    То есть вопрос в том, как сортируются коммиты.


    1. fougasse
      25.11.2021 14:06
      +1

      Как минимум, у bisect есть опции:

      --first-parent 

      Follow only the first parent commit upon seeing a merge commit.

      In detecting regressions introduced through the merging of a branch, the merge commit will be identified as introduction of the bug and its ancestors will be ignored.

      This option is particularly useful in avoiding false positives when a merged branch contained broken or non-buildable commits, but the merge itself was OK.


  1. warhamster
    26.11.2021 00:17

    Все это намного быстрее чем проверять каждый коммит и искать иголку в стоге сена!

    Никто в здравом уме не будет проверять каждый, деление пополам еще в школе же проходят.

    Как по мне, весь смысл существования бисекта исключительно в опции run, как раз вот чтобы руками ничего не гонять. Если до нее дело не доходит — то обычно не то, что бисект не нужен, а даже и в консоль идти не надо, проще пару-тройку коммитов в идешечке переключить.


    1. KvanTTT
      26.11.2021 05:28

      К тому же в ide их можно повыбирать умнее, но все равно бинарно.


    1. PavloPoliakov Автор
      26.11.2021 09:17

      Я думаю это смелое предположение. Куча людей подходят к куче вещей бессистемно. Поиск бага вряд ли является исключением.


  1. KvanTTT
    26.11.2021 05:30
    +1

    Просматривать свои коммиты в обратном порядке, это реальный пример квадратичного поиска. Сложность квадратичного поиска 0(n^2)

    Откуда взялся квадрат? Это линейный поиск, а с бисектом - логарифмический.


    1. PavloPoliakov Автор
      26.11.2021 09:13

      Я думал автор имел в виду просмотр коммита + запуск тестов + накапливающуюся когнитивную сложность. Но гарантий дать не могу, это перевод.


      1. KvanTTT
        26.11.2021 13:48

        Звучит странно, что такое в этом случае N? А при бинарном поиске тогда какая сложность?


        1. PavloPoliakov Автор
          26.11.2021 13:59

          N все еще количество коммитов. Просто если взять 15 коммитов и все вручную проверить, то на 5-ом начнешь уставать, а к 10-му совсем будешь фрустрирован ????.

          А если взять бинарный вариант, то знаешь что за 4 раза справишься ????.

          Я так себе объясняю идею автора.


          1. KvanTTT
            26.11.2021 14:45

            В среднем все равно нужно будет проверить N / 2 коммитов, т.е. зависимость линейная, автор походу не разбирается в математике.