Pre-commit hook
Введение
Git hooks — это механизм в git, который запускает выполнение операций перед действием в git (например, перед коммитом или ребейзом).
В отличие от запуска линтеров с помощью ci/cd платформ этот механизм имеет преимущество — действия происходят на стороне клиента, а значит, сервер о них не узнает. Это может быть полезно, например, для проверки приватных данных (кредлов или приватных ключей), которые мог добавить пользователь. Мы же не хотим, чтобы они оказались в гитхабе, а их увидели другие контрибьюторы.
Также удобно переносить часть линтеров на сторону клиента: они запускаются на машине пользователя, поэтому ему не нужно ждать, когда дойдет очередь до его прогона ci/cd.
Еще один пример использования hooks — смена названия коммита. Например, в системе контроля версиями Gerrit нужно добавлять в конце коммита уникальный Change id, что обычно делается через hook.
Hook — это обычный shell-скрипт, который должен иметь специальное имя: по нему git сможет определить, при каком действии его запускать.
Hook должен находиться в .git\hooks. В этой папке уже есть примеры на каждое возможное действие (например, commit-msg.sample). Если убрать расширение — hook заработает.
Pre-commit
Один из минусов этого подхода: версионировать папку .git обычно не очень хочется, более того, читать shell-скрипты неудобно.
Самый популярный способ решить эту проблему — утилита pre-commit. Она позволяет конфигурировать hook’и с помощью yaml-файла.
Для этого необходимо просто установить ее через pip install pre-commit
и настроить yaml-файлик с названием .pre-commit-config.yaml, в котором нужно указать hook’и для использования. Есть огромное количество уже готовых hook’ов почти на все случаи жизни. Их можно загружать из гитхаба. Например, есть hook’и для поиска кредлов, приватных ключей, исправления опечаток и много чего другого. Для разных библиотек существуют свои репозитории с готовыми hook’ами (например, mypy, pylint, black).
После настройки yaml-файла нужно ввести команду pre-commit install
, которая создаст нужные файлики в .git\hooks. Также можно запустить все hook’и с помощью pre-commit run
.
Давайте рассмотрим pre-commit, который будет детектить приватные ключи и запускать линтер flake8:
repos:
- repo: <https://github.com/pre-commit/pre-commit-hooks> #ссылка на репо
rev: v2.3.0 #версия репозитория
hooks:
- id: detect-private-key #какие hook’и использовать из репо
- repo: <https://github.com/PyCQA/flake8>
rev: 6.1.0
hooks:
- id: flake8
Мы использовали репозиторий pre-commit-hooks для того, чтобы достать оттуда detect-private-key и репозиторий flake8. Теперь каждый раз, когда мы будем делать коммит, у нас будут выполняться эти проверки.
🔥 Существует мнение, что если у разработчика загорится квартира, то он должен иметь возможность быстро закоммитить свою работу перед тем, как убежать из нее. В таком случае, чтобы не ждать выполнение hook’ов, нужно добавить в конце команды --no-verify
, например, git commit -m "The roof is on fire" --no-verify
и тогда hook’и не будут выполняться.
В yaml-файле настраивается очень много параметров: какие файлы исключать, куда перенаправлять вывод логов. Также, хоть библиотека и называется pre-commit, в ней можно настроить выполнение действия и при других git-действиях с помощью поля default_stages
. Например, pre-push, pre-merge-commit и многих других.
А еще можно добавить свой этап. Кроме того, библиотека поддерживает огромное количество языков (и не только), на которых можно писать. Например, можно написать скрипт на rust или python или даже запустить докер образ.
Давайте теперь добавим в репозиторий скрипт — он проверит, что файлов и папок в корне проекта у нас меньше 100.
import os
if len(os.listdir()) > 100:
raise RuntimeError("Too many folders and files!")
else:
print("Everything is ok")
И затем добавим в yaml следующие поля:
Ура! Теперь при запуске hook’ов у нас запустится и наш.
hooks:
- id: script
name: script #название stage
entry: python script.py # наш скрипт
language: python #язык, на котором написан скрипт
pass_filenames: false # нужно ли запускать этот скрипт на каждом файле
Заключение
Итак, если мы хотим автоматически запускать какое-нибудь действие перед git-действием — мы можем воспользоваться библиотекой pre-commit. Ее мы устанавливаем через pip install pre-commit
.
Мы можем либо применить готовые hook’и, либо написать свой. Что, как и перед какими действиями запускать — это все настраивается с помощью yaml. После конфигурирования нам остается установить hook’и через pre-commit install
. Вручную это можно запустить с помощью команды pre-commit run
. И все готово!
А здесь можно посмотреть очень подробную документацию.