Понять, как настроить хуки 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

Этот скрипт будет просто:

  1. Получите название вашей текущей ветки
  2. Проверьте, называется ли он 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

Давайте разберем это:

  1. Проверьте, проталкивается ли тег. Если да, то exit тэги нас не интересуют.
  2. Получите ветку нашего репо по умолчанию. Обратите внимание, что это зависит от команды, некоторые предпочитают master или main, а некоторые другие выбирают другую ветку по умолчанию.
  3. Получите имя текущей ветки, из которой вы отправляете.
  4. Сравните обе ветки и выберите commit-message .
  5. Найдите шаблон 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 (хотя некоторые вещи, возможно, придется изменить в сценарии).