Понять, как настроить хуки Git
Введение
Если вы используете Git и работаете в большой команде, вы, вероятно, обсуждали, как структурировать ваши коммиты и ветки/теги git:
- Избегайте больших заголовков коммитов.
- Ветки должны иметь префикс
feature
илиfix
. - Теги должны соответствовать шаблону
vX.X.X
. - …
Кроме того, вы можете использовать JIRA или любой другой инструмент для отслеживания проблем и быть заинтересованы в связывании заявок с коммитами и/или именами веток. Или, возможно, вы захотите запустить линтер перед отправкой на удаленный сервер. Это и многое другое можно сделать с помощью Git Hooks.
В этой статье я кратко объясню, что такое Git Hooks, и настрою 2 хука для:
- Предотвратить фиксацию
master
/main
- Запретить отправку сообщений, если журнал коммитов не связан с тикетом JIRA
Обзор: Git Hooks
Git Hooks — это просто скрипты, которые вызываются в ответ на определенное событие git
. Вот полный список всех доступных хуков.
Эти хуки обычно находятся в папке Git Hook, то есть <your_git_folder>/.git/hooks
(если не изменены с помощью core.hooksPath
). По умолчанию Git заполнит эту папку образцами:
Есть два типа Git Hooks:
- Перехватчики на стороне клиента, которые работают на вашем локальном компьютере.
- Перехватчики на стороне сервера, которые работают на сервере Git.
В идеале, чтобы применить определенную политику к вашему проекту, вы должны использовать перехватчики на стороне сервера. Однако вам потребуется учетная запись Enterprise в большинстве репозиториев, таких как Github, и это имеет свою цену.
Вот почему вы, вероятно, в конечном итоге будете использовать перехватчики на стороне клиента. Это не означает, что работа не может быть выполнена, но у нее есть недостатки, поскольку хуки не клонируются с остальной частью репозитория. На самом деле они не отслеживаются. Это означает, что у каждого члена команды потенциально может быть свой хук или вообще не быть! Тем не менее, этот сценарий можно как-то смягчить, как мы увидим позже.
Наш самый первый Git Hook
Начнем с создания нашего первого крючка. Мы будем стремиться избегать коммитов в ветке по умолчанию (master
или любой другой). Эта цель может показаться немного бессмысленной, поскольку большинство репозиториев git позволяют вам устанавливать правила для защиты ваших веток от прямого git push
. Например, GitHub позволяет вам «Требовать запрос на извлечение перед слиянием»:
Однако это не относится к администраторам. Вы по-прежнему можете заставить их соблюдать те же правила:
но вас может заинтересовать, по-прежнему запрещая администраторам нажимать на master
, позволяя им пропускать некоторые другие критерии (утверждения, проверки и т. д.). Это одна из ситуаций, когда Git Hooks может пригодиться.
Нам просто нужно зайти в папку hooks и переименовать pre-commit.sample
в pre-commit
, то есть избавиться от расширения sample
. Кроме того, убедитесь, что у файла есть права на выполнение:
cd <your_git_folder>/.git/hooks
mv pre-commit.sample pre-commit
chmod +x pre-commit
Есть и другие хуки, которые могут подойти для наших целей, но pre-commit
остановит процесс git на ранней стадии. Откройте файл, удалите его содержимое и вставьте следующее:
#!/bin/sh # 1 branch=`git branch --show-current` RED='\033[0;31m' NC='\033[0m' # No Color # 2 if [[ "$branch" =~ (master|main) ]]; then echo "${RED}ERROR: Commits to $branch are not allowed${NC}" >&2 echo "Please, create a new branch" >&2 exit 1 else exit 0 fi
Этот скрипт будет просто:
- Получите название вашей текущей ветки
- Проверьте, называется ли он
main
илиmaster
.
Давайте посмотрим на это в действии:
git checkout master git pull echo "Test pre-commit hook" > test.txt git add -A git commit -am "This commit will fail"
Вы должны получить что-то вроде этого:
Подробнее о крючках
Одна вещь, с которой я столкнулся в последнее время на работе, — это гарантировать, что ни одна ветка не будет объединена без билета JIRA, связанного хотя бы с одним коммитом. Причина в том, что затем мы используем этот идентификатор билета, чтобы творить «волшебство» на нашем сервере CICD (переходные билеты, добавить Fix version
и использовать JIRA в качестве источника правды).
Один из способов сделать это — использовать Git Hooks. Опять же, перехватчики на стороне клиента здесь будут работать намного лучше, но у нас есть свои ограничения. Крюк pre-push
будет работать нормально. Если мы повторим процесс, описанный в предыдущем разделе:
cd <your_git_folder>/.git/hooks
mv pre-push.sample pre-push
chmod +x pre-push
затем мы можем приступить к редактированию сценария. В этом случае мы хотим сравнить нашу текущую ветку с master
и получить новые коммиты. Затем проверьте commit-messages
и найдите определенный шаблон. Допустим, ваш проект JIRA называется Project
, а идентификаторы заявок соответствуют шаблону PROJ-XXX
. Мы можем легко убедиться, что commit-log
содержит хотя бы идентификатор билета:
#!/bin/sh RED='\033[0;31m' NC='\033[0m' # No Color remote=$1 # 1 tag_name=$(grep -E 'refs/tags/([^ ]*) ' </dev/stdin | cut -d ' ' -f 1 | sed 's/refs\/tags\///g') if [ ! -z $tag_name ]; then # A tag is being pushed, no need to make any checks exit 0 fi # 2 main_branch_name=`git symbolic-ref refs/remotes/$remote/HEAD | sed "s@^refs/remotes/$remote/@@"` # 3 current_branch_name=`git branch --show-current` # 4 commit_log=`git log --format=%B $remote/$main_branch_name..$current_branch_name` # 5 regex="(PROJ|proj)-[0-9]+" if [[ ! $commit_log =~ $regex ]]; then echo "${RED}ERROR: No JIRA ticket ID was found${NC}" >&2 echo "Please, include a ticket ID in one of your commits" >&2 exit 1 fi exit 0
Давайте разберем это:
- Проверьте, проталкивается ли тег. Если да, то
exit
тэги нас не интересуют. - Получите ветку нашего репо по умолчанию. Обратите внимание, что это зависит от команды, некоторые предпочитают
master
илиmain
, а некоторые другие выбирают другую ветку по умолчанию. - Получите имя текущей ветки, из которой вы отправляете.
- Сравните обе ветки и выберите
commit-message
. - Найдите шаблон
PROJ-XXX
и игнорируйте регистр.
Результат:
$ git push ERROR: No JIRA ticket ID was found Please, include a ticket ID in one of your commits
Кроме того, мы можем выполнить некоторые проверки имени tag
или branch
, сравнив их с другим регулярным выражением, но я оставлю это читателю в качестве упражнения.
Поделитесь крючками между членами команды
В начале статьи я упомянул, что локальные хуки Git не могут быть легко переданы остальной команде, поскольку Git не отслеживает эти файлы и, в конце концов, приходит к каждому члену команды, чтобы скопировать их в папку .git/hooks
.
Здесь может пригодиться одно из решений: создать новую папку githooks
в структуре вашего проекта <your_git_project>/githooks
и поместить ее во все ваши git-хуки. Затем ваше приложение скопирует все файлы внутри githooks
в .git/hooks
перед выполнением.
Например, как разработчик iOS я работаю с Xcode. Перед созданием приложения для iOS мы создали скрипт, который просто копирует все хуки в папку Git Hooks, чтобы поддерживать их в актуальном состоянии.
Однако, если у вас есть возможность использовать перехватчики на стороне сервера, я настоятельно рекомендую использовать их.
То же самое, что мы сделали здесь, можно сделать, например, с помощью хука update
(хотя некоторые вещи, возможно, придется изменить в сценарии).