Git Hooks. Автоматизируем запуск тестов перед коммитом в Clojure, Java и везде

История полностью выдуманная. Придумана для того, чтобы поддать жару и эмоций в скучную статью про Git.

Рассмотрим сферическую команду в вакууме. Пусть команда будет новая, программистов только поместили в одно помещение, поставили столы друг к дугу, все сидят дружненько, трутся бочками друг об друга. Члены команды в меру адекватные, стремятся к развитию. Проект считают интересным и в этот раз каждом хочется сделать "правильно, а не как в прошлый раз". Команда начинает работать.

Все стараются и полны энтузиазма, за окном весна, все цветет.

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

Наступает осень, идут дожди, гниют листья, периодически солнце радует светом нашу команду. Люди, которые раньше "случайно" забывали запустить тесты перед коммитом, сейчас просто этого не делают, все чаще проект в репозитории находится в покалеченном, больном состоянии. Ситуация раздражает людей, которые следят за тем, что бы не коммитить код непроходящий тесты. Начались конфликты.

Зима. Проект мучительно страдает. Команда в подавленном состоянии. На тесты уже все забили, действует негласное правило "скомпилировалось - можно отдавать тестить!". Те, кто сопротивлялся, либо забили и стали делать как все, либо ушли. Про "правильно, а не как в прошлый раз" вспоминают со смехом на кухне.

Конец.

Не лирическая часть

Возможно, если бы команда из нашей истории приняла решение автоматически запускать тесты перед коммитом, и запрещать коммитить код в репозиторий, который не проходит тесты, - у них бы все было веселее!

И тут нам на помощь приходит Git. У Git есть механизм хуков, с помощью которого можно автоматизировать запуск тестов перед коммитом и не коммитить изминения, если тесты не проходят.

Добавляем hook

Заходим в корень проекта и создаем ссылку на скрипт.

ln -s ../../pre-commit.sh .git/hooks/pre-commit 

Создаем скрипт vim pre-commit.sh.

Скрипт должен запустить тесты для тех изминений, которые должны попасть в коммит. Для этого ложим в stash все изминения, кроме тех, которые в стейджинге git stash --keep-index. Для того, что бы не выводить результат выполнения, используем опцию -q, получаем git stash --keep-index -q. После этого нужно запустить тесты и вернуть изминения из стэша, в зависимости от того, прошли тесты или нет, вернуть код ошибки или сказать, что все ок.

# pre-commit.sh
git stash -q --keep-index

# Запускаем тесты
...

git stash pop -q

Запускаем тесты

Запуск тестов делегируем скрипту run_tests.sh, в зависимости от его рузультата будет возарщать код ошибки.

git stash -q --keep-index
./run_tests.sh
RESULT=$?
git stash pop -q
[ $RESULT -ne 0 ] && exit 1
exit 0

Для Clojure проекта run_tests.sh может иметь такое содержание:

lein test

Для типичного Java приложения нужно будет вызвать mvn test.

Добавляем права на запуск скриптов:

chmod +x run_tests.sh                                                                                                                  
chmod +x pre-commit.sh

Все, теперь перед коммтитом Git будет автоматически запускать тесты, если они не пройдут изминения не закоммитятся.

[ggenikus:~/Tmp/hook]$ git commit -am "test"                                                                                                                  (master✱)

lein test hook.core-test

lein test :only hook.core-test/a-test

FAIL in (a-test) (core_test.clj:7)
FIXME, I fail.
expected: (= 0 1)
  actual: (not (= 0 1))

Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
Tests failed.
[ggenikus:~/Tmp/hook]$

В экстримальных ситуациях

Если все таки по каким то причинам нужно закоммитить изминения без запуска тестов, то просто используем опцию --no-verify git commit --no-verify. И все будет как раньше.

Ресурсы

Written on January 1, 2014