Хуки Git: что это и какие бывают

Хук git — это программа, которую git запускает в определённый момент операции: до того как создан коммит, после того как push дошёл до сервера, после переключения ветки. Это не плагин и не команда git. Это исполняемый файл с известным именем в папке, куда смотрит git, а код возврата — ответ, которого git ждёт: ноль значит «продолжай», всё остальное останавливает операцию там, где git позволяет хуку её остановить.

Хуками репозиторий поддерживает то, о чём договорились его авторы. Хук pre-commit не пропускает коммит с кодом, который не разбирается, хук post-commit пересобирает файл после того, как коммит уже существует, а хук pre-receive на сервере отклоняет push, который нарушает те же правила у всех. Как два локальных хука работают в паре и что пошло не так, когда они не сработались, — в статье pre-commit и post-commit вместе.

Что такое хук git

Git не разбирает содержимое хуков. Он ищет в папке хуков файл с известным именем и, если файл существует и исполняемый, запускает его с теми аргументами, стандартным вводом и окружением, которые относятся к этому моменту операции. Всё остальное — дело самого хука: bash, Python, PHP или собранный бинарник. Имя файла решает, когда хук запустится, а код возврата — что git сделает дальше.

Где git ищет хуки

По умолчанию это папка .git/hooks внутри репозитория. git init наполняет её файлами .sample: по одному примеру на каждый хук, и ни один из них не включён. Достаточно переименовать файл и сделать его исполняемым, чтобы хук заработал.

ls .git/hooks

applypatch-msg.sample commit-msg.sample fsmonitor-watchman.sample post-update.sample pre-applypatch.sample pre-commit.sample pre-merge-commit.sample prepare-commit-msg.sample pre-push.sample pre-rebase.sample pre-receive.sample push-to-checkout.sample sendemail-validate.sample update.sample

Папку можно перенести настройкой core.hooksPath — так репозиторий держит хуки в отслеживаемой папке, а не внутри .git:

git config core.hooksPath .githooks

Настройка хранится в локальной конфигурации, поэтому в свежем клоне команду всё равно нужно выполнить. Отслеживаемая папка плюс одна команда настройки — самый простой способ поделиться хуками. Фреймворк — способ крупнее: фреймворк pre-commit ставит два файла хуков, которые вызывают его самого, а проверки хранит в YAML-файле рядом с кодом.

Включить, пропустить, отследить

Три команды покрывают почти всё ежедневное использование. Первая включает хук. Вторая пропускает хуки, которые охраняют коммит, — pre-commit, pre-merge-commit и commit-msg, — но не prepare-commit-msg и не хуки post-*, которые всё равно запустятся. Третья печатает каждый хук, который запускает git, с путём и аргументами, и отвечает на вопрос «а он вообще запускался?» раньше, чем вы начнёте отлаживать сам хук.

chmod +x .git/hooks/pre-commit

git commit --no-verify -m "WIP"

GIT_TRACE=1 git commit -m "Test"

Три строки хуков одного коммита, без отметок времени:

run-command.c:659 trace: run_command: GIT_EDITOR=: GIT_INDEX_FILE=.git/index .git/hooks/pre-commit run-command.c:659 trace: run_command: GIT_EDITOR=: GIT_INDEX_FILE=.git/index .git/hooks/commit-msg .git/COMMIT_EDITMSG run-command.c:659 trace: run_command: GIT_EDITOR=: GIT_INDEX_FILE=.git/index .git/hooks/post-commit

Хуки Git 2.43

В Git 2.43 двадцать восемь хуков. Читать их проще в том порядке, в котором git их запускает, а не по алфавиту, поэтому таблицы ниже идут за одним коммитом, одним push и одним обслуживанием репозитория.

Вокруг коммита.

Хуки коммита и слияния
ХукКогда запускаетсяМожет остановить коммит
pre-commitпри git commit, до того как составлено сообщениеда, --no-verify его пропускает
pre-merge-commitпосле удачного слияния, до коммита слиянияда, --no-verify его пропускает
prepare-commit-msgпосле того как появилось сообщение по умолчанию, до открытия редакторада, и --no-verify его не пропускает
commit-msgпосле того как сообщение написано, получает свой файл аргументомда, --no-verify его пропускает
post-commitпосле того как объект коммита создан и HEAD сдвинулсянет
pre-rebaseдо начала git rebaseда, отклоняет rebase

Вокруг рабочего дерева и ссылок.

Хуки переключения, результата слияния и ссылок
ХукКогда запускаетсяМожет остановить команду
post-checkoutпосле git checkout, git switch, git clone и git worktree addнет, его код возврата становится кодом возврата команды
post-mergeпосле слияния или pull, но не после слияния с конфликтаминет
post-rewriteпосле того как git commit --amend и git rebase переписали коммитынет
reference-transactionвокруг каждого обновления ссылки: prepared, committed, abortedда, в состоянии prepared
post-index-changeпосле записи файла индексанет

Вокруг патчей и почты.

Хуки патчей и почты
ХукКогда запускаетсяМожет остановить команду
applypatch-msgпри git am, на предложенном сообщениида
pre-applypatchпри git am, после применения патча, до коммитада
post-applypatchпри git am, после коммитанет
sendemail-validateдо того как git send-email отправит патчда
p4-changelist, p4-prepare-changelist, p4-post-changelist, p4-pre-submitмост в Perforce: правка, подготовка, после отправки, до отправкида, кроме p4-post-changelist

Вокруг push. Один хук на клиенте, шесть на сервере.

Хуки push
ХукГде запускаетсяЧто может
pre-pushклиент, до отправки объектов; ссылки приходят на стандартный вводотклонить весь push
pre-receiveсервер, один раз на push, до обновления любой ссылкиотклонить все ссылки в push
updateсервер, один раз на ссылку, до её обновленияотклонить эту одну ссылку
proc-receiveсервер, когда ссылку обрабатывает вспомогательная командаотклонить или переписать эту ссылку
post-receiveсервер, после обновления всех ссылокуведомить; отклонить не может
post-updateсервер, после post-receive, получает обновлённые ссылки аргументамиуведомить; отклонить не может
push-to-checkoutсервер, когда push нацелен на ветку, развёрнутую в непустом репозиторииотклонить или обновить дерево самому

Обслуживание.

Фоновые хуки
ХукКогда запускаетсяЧто может
pre-auto-gcдо автоматической сборки мусораотложить её
fsmonitor-watchmanсвязка между демоном fsmonitor и watchmanсообщить об изменённых путях
post-index-changeпосле записи файла индекса, получает два флага аргументамиуведомить

Чего хук не может

Клиент и сервер

Важное разделение — не pre и post, а локальное и удалённое. Всё до pre-push включительно работает на машине, где делают коммит: быстро, приватно и необязательно. Всё начиная с pre-receive работает на сервере, после того как объекты туда доехали, и тот, кто отправляет, пропустить это не может. Правило, которое должно выполняться для каждого коммита, живёт на сервере; правило, которое экономит автору лишний круг, — на клиенте. Большинство репозиториев запускают одну и ту же проверку в обоих местах: локальный хук ради скорости, серверный ради гарантии.

Фреймворк pre-commit

Писать одни и те же проверки в каждом репозитории быстро надоедает, поэтому большинство проектов берут фреймворк: он держит проверки в YAML-файле, ставит файлы хуков и запускает инструменты в изолированных окружениях. pre-commit — тот фреймворк, который использует этот сайт. Он ставит и .git/hooks/pre-commit, и .git/hooks/post-commit, а проверки перечислены в .pre-commit-config.yaml в корне репозитория.

У фреймворка есть поведение, которого у обычных хуков нет, и именно оно стало причиной случая, описанного в статье про рабочий процесс: перед каждой стадией он сохраняет все незакоммиченные изменения в ~/.cache/pre-commit/patch<timestamp>-<pid>, выполняет git checkout -- ., чтобы проверки видели чистое рабочее дерево, и после них накладывает патч обратно. Если наложить не удалось, работа не потеряна — файл патча остаётся на диске, — но в рабочем дереве её уже нет.

Статьи раздела

Хуки Git
Хук pre-commit: что он проверяет, что видит и как его пропустить
Хук post-commit: реакции, которые не могут отменить коммит
pre-commit и post-commit вместе: проверка и реакция