Статьи

Введение в Git Hooks

Git-хуки — это простые скрипты, которые запускаются до или после определенных действий. Они полезны для различных задач, но в первую очередь я нахожу их полезными для проверки на стороне клиента, чтобы можно было избежать простых ошибок. Например, вы можете проверить синтаксис для файлов, которые были приняты, вы можете даже запустить тесты. Я написал хуки, которые проверяют синтаксис Twig, запускают стандарт JSHint и многое другое.

Крючки Git также чрезвычайно просты по конструкции. Git будет запускать эти ловушки, если скрипт является исполняемым, и Git будет позволять выполнению действия (например, фиксации или push), пока ловушка завершается без ошибок (состояние 0). Крючки могут быть написаны на любом языке, с которым может работать среда.

Есть два типа крючков:

  • На стороне клиента — они работают в системе разработчика
  • На стороне сервера — они запускаются на сервере, на котором размещается Git-репозиторий

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

Файлы Крюка

Каждый репозиторий, включая те, которые вы клонируете по умолчанию, будет иметь примеры ловушек в каталоге .git/hooks :

 git clone [email protected]:symfony/symfony.git cd symfony ls .git/hooks 

В этом каталоге вы увидите что-то вроде:

 applypatch-msg.sample commit-msg.sample post-update.sample pre-applypatch.sample pre-commit pre-commit.sample prepare-commit-msg.sample pre-push.sample pre-rebase.sample update.sample 

Мы сосредоточимся на хуке pre-commit который запускается до разрешения фиксации.

Пример хука: проверка синтаксиса PHP

Мы начнем с очень простого хука, написанного на Bash, который проверяет, что принятый код PHP имеет правильный синтаксис. Это сделано для предотвращения «быстрого», но нарушенного коммита. Конечно, я не рекомендую «простые коммиты», которые практически не тестируются, но это не значит, что они не будут выполнены.

В .git/hooks мы можем запустить новый файл с именем pre-commit . Он должен иметь разрешения на выполнение:

 cd .git/hooks touch pre-commit chmod +x pre-commit 

Вы можете использовать свой любимый редактор, чтобы начать писать. Сначала нам нужен Шебанг. Мой любимый способ — использовать /usr/bin/env как он использует правильный путь к желаемому приложению, а не жестко заданный и, возможно, неверный путь. Пока что у нас будет постоянный сбой, поэтому мы можем легко проверить.

 #!/usr/bin/env bash # Hook that checks PHP syntax # Override IFS so that spaces do not count as delimiters old_ifs=$IFS IFS=$'\n' # Always fail exit 1 

PHP имеет полезную опцию для проверки синтаксиса: -l . Он принимает один аргумент файла, поэтому нам придется циклически перебирать любые изменяемые файлы PHP. Для простоты мы будем предполагать, что любые фиксируемые PHP-файлы всегда заканчиваются на .php . Поскольку ловушка запускается из корня репозитория, мы можем использовать стандартные команды Git для получения информации об изменениях, таких как git status .

Над #Always fail мы можем использовать следующее, чтобы получить все изменяемые файлы PHP:

 php_files=$(git status --short | grep -E '^(A|M)' | awk '{ print $2 }' | grep -E '\.php$') 

Объяснение:

  • php_files= In Bash присваивание выполняется без разделителя, но обратите внимание, что для ссылки на переменную требуется $ delimiter
  • $() — это синтаксис для ‘get output’. Цитаты не обязаны использовать это.
  • grep используется для проверки добавленных ( A ) и модифицированных файлов ( M )
  • Awk используется здесь, чтобы напечатать $2 . Полная git status --short содержит дополнительное пространство и дополнительные данные в начале, поэтому мы хотим удалить это. Awk также выполняет автоматическую зачистку.
  • grep снова используется, но теперь проверяет, чтобы строки заканчивались на .php

Теперь мы можем проверить каждый файл с помощью цикла for :

 for file in $php_files; do if ! php -l "$i"; then exit 1 fi done 

Это может показаться немного странным, но ! php -l "$i" ! php -l "$i" (обратите внимание на кавычки, чтобы избежать проблем с пробелами) на самом деле проверяет возвращаемое значение 0 , а не true или любое из значений, которые мы обычно ожидаем в других языках. Просто для справки, примерно эквивалентный код PHP будет:

 foreach ($php_files as $file) { $retval = 0; $escapedFile = escapeshellarg($file); exec('php -l ' . $escapedFile, $retval); // $retval passed in as out parameter reference if ($retval !== 0) { exit(1); } } 

Я сделал плохое изменение в src/Symfony/Component/Finder/Glob.php с целью проверить это, и вывод из git commit -m 'Test' выглядит так:

 PHP Parse error: syntax error, unexpected 'namespace' (T_NAMESPACE) in src/Symfony/Component/Finder/Glob.php on line 12 Parse error: syntax error, unexpected 'namespace' (T_NAMESPACE) in src/Symfony/Component/Finder/Glob.php on line 12 Errors parsing src/Symfony/Component/Finder/Glob.php 

Я сделал цикл выхода из всего скрипта как можно раньше, и в конечном итоге это может оказаться не тем, что мы хотим. На самом деле, мы можем захотеть, чтобы сводка вещей была исправлена, а не пытаться совершить коммит. В конечном итоге любой может легко разочароваться и даже научиться использовать git commit --no-verify чтобы вообще обойти хук.

Вместо этого давайте не будем останавливаться на ошибке с php -l но я все же хотел бы, чтобы все было легко читать:

 for file in $php_files; do php_out=$(php -l "$file" 2>&1) if ! [ $? -eq 0 ]; then echo "Syntax error with ${file}:" echo "$php_out" | grep -E '^Parse error' echo fi done 

Здесь мы фиксируем выходные данные для php -l (и принудительно выводим стандартную ошибку в стандартный вывод). Мы проверяем состояние выхода php -l используя специальную переменную $? (который является кодом состояния выхода) и оператор- -eq . Мы утверждаем, что произошла синтаксическая ошибка (обратите внимание на использование ${} для переменной в строке). Наконец, мы даем соответствующую строку для ошибки, чтобы вывод был немного более кратким (сокращение для '^Parse error' ), и мы даем одну пустую строку, чтобы сделать это немного более читабельным.

Я сделал две неудачные модификации, и вывод для попытки фиксации выглядит так:

 Syntax error with src/Symfony/Component/Finder/Finder.php: Parse error: syntax error, unexpected '}' in src/Symfony/Component/Finder/Finder.php on line 118 Syntax error with src/Symfony/Component/Finder/Glob.php: Parse error: syntax error, unexpected 'namespace' (T_NAMESPACE) in src/Symfony/Component/Finder/Glob.php on line 12 

Теперь нужно решить эти проблемы, протестировать и повторить попытку.

Чтобы завершить сценарий подключения, удалите exit 1 в нижней части сценария. Попробуйте зафиксировать действительные файлы PHP, и все должно работать как обычно.

Совместное использование крючков

Хуки не распространяются вместе с вашим проектом и не могут быть установлены автоматически. Таким образом, ваш лучший способ действий — создать место для ваших хуков, которые будут жить (могут быть в том же хранилище), и попросить ваших соавторов использовать их. Если вы сделаете это для них легче, они, скорее всего, сделают это.

Один простой способ сделать это — создать каталог hooks и простой установщик install-hooks.sh который связывает их (а не копирует):

 #!/usr/bin/env bash for i in hooks/*; do ln -s "${i}" ".git/hooks/${i}"; done 

Любой, кто клонирует ваш проект, может просто запустить bash install-hooks.sh после клонирования.

Это также имеет то преимущество, что ваши хуки находятся под контролем версий.

Другие крючки

  • prepare-commit-msg — Предоставить сообщение о фиксации по умолчанию, если оно не указано.
  • commit-msg — Подтвердить подтверждение сообщения.
  • post-commit — Запускается после успешного коммита.
  • pre-push — запускается перед git push после проверки работоспособности пульта. Требуется 2 аргумента: имя удаленного и URL к нему.
  • pre-rebase — запускается перед git rebase .
  • post-checkout — запускается после успешной проверки.
  • post-merge — запускается после успешного слияния.

Эти хуки обычно работают так же, как и pre-commit хотя они принимают аргументы. Один из вариантов использования для post-checkout — убедиться, что файл всегда получает надлежащие разрешения (поскольку Git отслеживает только исполняемый файл, а не исполняемый файл и символическую ссылку):

 #!/usr/bin/env bash # Make sure only I can read this file chmod 0600 my-file-with-secrets 

Для commit-msg вы можете убедиться, что все сообщения о коммитах соответствуют стандарту, например, [subproject] Message . Вот один в PHP:

 #!/usr/bin/env php <?php // The message is passed as the the last argument. $message = file_get_contents($argv[count($argv) - 1]); if (!preg_match('/^\[[a-z_\-]+\]\s[AZ]/', $message)) { echo 'Message must be of format: [subproject] Message'; exit(1); } ?> 

Вывод

Git-хуки — мощное средство для автоматизации рабочего процесса вашего проекта. Вы можете проверить код, зафиксировать сообщения, убедиться в правильности среды и многое другое. Есть ли что-то интересное, для чего вы используете Git-хуки? Дайте нам знать об этом в комментариях!