В этой серии я рассказываю о множестве волшебных инструментов, и git bisect, вероятно, является лучшим примером такого волшебства. Самое сложное в отладке — это знать общую область ошибки, а bisect буквально проливает свет на конкретный коммит, который ее вызвал!
Прежде чем мы начнем, давайте проясним одну вещь: bisect — это инструмент для отладки регрессий. Он ничего не делает для обычных ошибок. Когда у нас есть регрессия, мы обычно знаем, что проблема работала в определенном релизе, у нас обычно есть конкретная ревизия, в которой код работал. Мы обычно знаем, что он не работает в текущей версии, но какой коммит на этом пути вызывает сбой?
В старые добрые времена
В старые времена SVN (или CVS, SourceSafe и т.д.) мы проверяли старую версию репозитория и тестировали на ней. Если тест не удавался, мы возвращались назад, а если удавался, мы шли вперед, чтобы отточить конкретный коммит, который был неудачным. Те из нас, кому посчастливилось работать с компетентными отделами QA, часто могли передать эту задачу им. В конечном счете, работа выполнялась вручную.
При определении проблемы мы следовали разумной стратегии поиска: делили количество ревизий пополам и шли к середине набора, а не по одному шагу за раз. Это значительно сократило время, затрачиваемое на поиск проблемной ревизии. Тем не менее, все еще остается много ревизий для поиска.
В этот момент вы можете задаться вопросом, почему вы не автоматизировали эти вещи?
Иногда мы это делали, но поскольку ни одна система версионирования не была такой доминирующей, как git, эти автоматизации не были долговечными, и я не знаю о такой автоматизации, которая попала бы в какую-либо систему контроля версий. Но git bisect вошел и является автоматизацией этой эвристики.
Git Bisect: Найдите ошибку. Автоматически!
Именно так. Он делает именно это. Самое простое использование git bisect начинается с команды:
git bisect start
Это переключает нас в режим bisect. Теперь мы можем определить «хорошую» версию, в которой все работало правильно. Например, для https://github.com/codenameone/CodenameOne/ я могу использовать ревизию 79a8e37adb7dd48093779bd3657142e607bdd2d9
как хорошую ревизию. Таким образом, мы можем отметить ее с помощью команды:
git bisect good 79a8e37adb7dd48093779bd3657142e607bdd2d9
После этого мы можем активировать обход по биссектрисе, пометив плохую ревизию. В большинстве случаев это означает текущую ревизию головки, которая используется по умолчанию, но вы можете указать конкретную ревизию в качестве аргумента этой команды:
git bisect bad
После этого мы можем перемещаться между ревизиями, переопределяя хорошую или плохую ревизию.
Здесь мы выполнили эти команды для репозитория Codename One и получили следующий результат:
Обратите внимание на значение 68dabb4f70c8295887d2da5c466dbe89fc910408
внизу. Это текущая ревизия, до которой мы «допрыгнули». Теперь мы можем отметить эту ревизию как хорошую или плохую, основываясь на нашем ручном тестировании.
Я пометил ее как плохую, но мог бы пометить ее как «хорошую», и все бы работало нормально. Это переместило bisect к следующей ревизии, которую нам нужно протестировать:
Это отстой! Или нет?
Просматривать каждую ревизию вручную — это большая боль!
Конечно, это лучше, чем случайный просмотр журналов ревизий и перескакивание через ревизии, сохраняя при этом место в голове. Но едва ли лучше. К счастью, есть лучший способ: run
.
Bisect может выполнить произвольную команду для нас на каждой ревизии, с которой он сталкивается. Если команда возвращает ноль (как код завершения процесса), ревизия хороша. Если она возвращает что-то другое, то ревизия плохая. Таким образом, неисправная ревизия будет определена автоматически без участия человека.
Обычно это отлично работает для командной оболочки, но для Java-разработчика это немного хлопотно. Как правило, у меня есть модульный тест, который показывает сбой. К сожалению, этот тест не существует в старой версии проекта. Затем мне также нужно скомпилировать проект, чтобы это сработало. Итак, хотя git bisect выглядит круто с командой run, как использовать его для скомпилированного языка, такого как Java?
На самом деле это довольно просто. Мы можем создать сложную командную строку, но лично я предпочитаю что-то вроде:
git bisect run testMyJavaProject.sh
Затем я реализую сценарий оболочки с командами для сборки/тестирования. Но перед этим мне нужно создать модульный тест, который не сработает для данной конкретной ошибки. Я полагаю, что большинство из нас легко справится с этой задачей. Теперь, когда у нас есть юнит-тест, создание сценария оболочки является тривиальной задачей. Приведенный ниже код предполагает, что вы используете maven для сборки:
#!/bin/sh
mvn clean
mvn package -DskipTests
mvn test -Dtest=MyTestClass
Вот и все. Обратите внимание, что если компиляция не удастся из-за зависимостей внутри тестового класса, вы можете получить неправильную ревизию. Поэтому держите тест простым!
Когда вы закончите работу с git bisect или захотите остановиться по какой-либо причине, просто выполните команду:
git bisect reset
TL;DR
Git bisect — это, вероятно, самый простой инструмент, о котором я расскажу в этой серии. Но это также один из самых важных инструментов. Научившись использовать его эффективно, вы можете сэкономить дни утомительной работы и поиска проблемы. Несмотря на такие огромные преимущества, эта функция относительно малоизвестна. Причина этого в том, что в 98% случаев она нам не нужна. Но в этих 2% она нам действительно нужна…
Надеюсь, в следующий раз, когда вы столкнетесь с регрессией, вы вспомните, что она есть, и воспользуетесь этим постом для поиска проблемы.