open atlas
↑ К треку
Git: от нуля до сеньора GIT · 08 · 04

Хуки git — скрипты, охраняющие твой процесс

Хуки git — скрипты в .git/hooks, срабатывающие на событиях: pre-commit (линт), commit-msg (формат сообщения), pre-push (тесты) и серверный pre-receive. Ненулевой код выхода прерывает операцию. Хуки не коммитятся, поэтому команды делят их через husky или lefthook.

GIT Middle ◷ 16 min
Уровень
ОсновыJuniorMiddleSenior

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

К концу этого урока ты сможешь назвать ключевые клиентские хуки, написать pre-commit-хук и объяснить, почему команды берут husky или lefthook, чтобы делить хуки, которые сам git не коммитит.

Цель

После этого урока ты сможешь объяснить, что такое хук git, положить скрипт в .git/hooks, назвать роли pre-commit, commit-msg и pre-push, объяснить, почему хуки по умолчанию не разделяются, и описать, как это решают husky/lefthook и core.hooksPath.

1

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

ls .git/hooks                       # pre-commit.sample, commit-msg.sample, pre-push.sample, ...
mv .git/hooks/pre-commit.sample .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit      # должен быть исполняемым, чтобы запуститься

Скрипт может быть любым исполняемым — shell, Node, Python — лишь бы у него было правильное имя и корректный shebang.

2

Ненулевой код выхода прерывает операцию — это и есть весь механизм принуждения. Git запускает хук и проверяет его код выхода. Выход 0 означает «продолжай»; любой ненулевой выход означает «стоп, не выполняй это действие». pre-commit, который находит ошибку линтинга и выходит с 1, отменяет коммит до того, как тот создан:

#!/bin/sh
# .git/hooks/pre-commit — заблокировать коммит, если линтинг провалился
npm run lint || {
  echo "Линтинг провалился — коммит прерван. Исправь ошибки и попробуй снова." >&2
  exit 1
}

Поэтому хук — это шлюз, а не просто уведомление: плохой коммит вообще не появляется на свет.

3

Три клиентских хука, которые ты будешь использовать чаще всего. Они срабатывают в разных точках потока коммита/push:

  • pre-commit — выполняется перед созданием коммита. Используй для линта, форматирования или быстрых проверок. Прерывание здесь означает, что ничего не закоммичено.
  • commit-msg — получает путь к файлу сообщения; используй для навязывания формата вроде Conventional Commits (feat:, fix:). Отклоняй неправильные сообщения.
  • pre-push — выполняется перед отправкой коммитов на удалённый репозиторий. Используй для более медленных шлюзов вроде набора тестов, чтобы сломанный код никогда не попадал в общую ветку.
#!/bin/sh
# .git/hooks/commit-msg — требовать префикс Conventional Commits
grep -qE '^(feat|fix|docs|refactor|test|chore)(\(.+\))?: ' "$1" || {
  echo "Сообщение коммита должно начинаться с типа, напр. 'feat: ...'." >&2
  exit 1
}
Почему это работает

Зачем и pre-commit, и pre-push? Они меняют скорость на тщательность. pre-commit запускается на каждом коммите, поэтому должен быть быстрым — линтинг нескольких staged-файлов занимает секунду. pre-push запускается куда реже (только когда ты реально делишься работой), поэтому может позволить себе медленный, но решающий шлюз вроде полного набора тестов. Поставить набор тестов в pre-commit сделало бы каждый коммит мучительным и подтолкнуло бы всех его обходить; разделение быстрых/медленных проверок по двум событиям держит каждую терпимой.

4

Хуки живут в .git, поэтому по умолчанию они НЕ коммитятся и не разделяются. Каталог .git/hooks — это часть метаданных твоего локального репозитория, а не отслеживаемых файлов — клонирование репозитория не приносит его хуки. Это значит, что хук, который ты настроил, защищает только твою машину; коллега, клонирующий проект, не получает ничего из этого. Для командного правила локальный хук бесполезен.

5

Команды делят хуки через менеджер вроде husky или lefthook, посредством core.hooksPath. Решение — держать определения хуков в отслеживаемом каталоге и направить git на него. Настройка git core.hooksPath перенаправляет хуки из .git/hooks в версионируемую папку:

git config core.hooksPath .githooks   # использовать закоммиченный каталог

Инструменты вроде husky и lefthook автоматизируют ровно это: они хранят хуки в репозитории, а шаг установки (часто на npm install) прописывает core.hooksPath, так что каждый клон получает те же шлюзы. Теперь правило путешествует вместе с проектом, а не живёт на одном ноутбуке.

Разбор примера

Добавляем линт-шлюз, который получает вся команда.

Ты хочешь, чтобы каждый коммит линтился, на машине каждого коллеги. Сырой .git/hooks/pre-commit защитил бы только тебя, поэтому ты используешь отслеживаемый каталог.

mkdir .githooks
cat > .githooks/pre-commit <<'EOF'
#!/bin/sh
npm run lint:staged || {
  echo "Линтинг провалился — исправь проблемы перед коммитом." >&2
  exit 1
}
EOF
chmod +x .githooks/pre-commit
git config core.hooksPath .githooks
git add .githooks/pre-commit && git commit -m "chore: add shared pre-commit lint hook"

Теперь хук — отслеживаемый файл. Но есть подвох: core.hooksPath — это локальная конфигурация, не коммитится, поэтому свежий клон всё равно требует той самой строки git config, чтобы запуститься. Именно этот разрыв закрывают husky и lefthook — их шаг установки прописывает core.hooksPath автоматически (например, через prepare-скрипт на npm install), так что новый участник выполняет npm install и мгновенно получает шлюз без ручной настройки. Проверь шлюз, добавив в staging файл с ошибкой линтинга и закоммитив: git запускает твой скрипт, тот выходит с 1, и коммит отклоняется с твоим сообщением.

Частая ошибка

Хуки — это удобство, а не граница безопасности. Любой может пропустить клиентский хук через git commit --no-verify (или git push --no-verify), а участник без установленного хука не запускает его вообще. Поэтому никогда не полагайся на клиентский хук как на единственную гарантию — подкрепляй критические правила серверным принуждением (хук pre-receive или проверки CI на удалённом репозитории), которое участник не может обойти. Клиентские хуки ловят ошибки рано; серверные шлюзы делают их обязательными.

Проверь себя
Викторина

Ты пишешь хук pre-commit в .git/hooks/pre-commit, который линтит твой код. Почему коллега, клонирующий репозиторий, НЕ получает эту проверку?

Итог

Хук git — это скрипт в .git/hooks, который git автоматически запускает на событии; ненулевой выход прерывает операцию, делая хуки настоящим шлюзом. Клиентские рабочие лошадки — pre-commit (быстрые проверки вроде линтинга), commit-msg (навязать формат сообщения) и pre-push (более медленные шлюзы вроде тестов), а серверные pre-receive/update — обязательный запасной рубеж. Поскольку хуки живут в .git, они по умолчанию не коммитятся и не разделяются, поэтому команды используют husky или lefthook плюс core.hooksPath, чтобы версионировать хуки и устанавливать их на каждом клоне — помня, что --no-verify делает клиентские хуки рекомендательными, а не принудительными. Дальше ты увидишь встроенного помощника, который делает повторное разрешение конфликтов автоматическим: rerere.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.