Git hooks: автоматизация проверок перед коммитом и пушем

Пишем реальные bash-хуки, разбираем клиентские и серверные события Git, сравниваем Husky, pre-commit и lefthook — с готовыми примерами конфигов.

Обложка: Git hooks: автоматизация проверок перед коммитом и пушем

Вы когда-нибудь отправляли коммит с опечаткой в сообщении — или пушили сырой код, который ломал CI? Git hooks позволяют этого избежать: это скрипты, которые автоматически запускаются при определённых событиях в Git. Настроил один раз — и линтер, проверка формата, тесты выполняются сами, без участия разработчика.

В этой статье разберём клиентские и серверные хуки, напишем реальные bash-скрипты и сравним инструменты для управления хуками в команде: Husky, pre-commit framework и lefthook.

Ключевые выводы

— Git hooks — скрипты в .git/hooks/, выполняемые при событиях Git (коммит, пуш, мерж)

— Клиентские хуки: pre-commit, commit-msg, pre-push и другие — запускаются на машине разработчика

— Серверные хуки: pre-receive, update, post-receive — контролируют пуши на сервере

— Husky, pre-commit framework и lefthook упрощают управление хуками в команде

— Хуки — это локальная автоматизация; CI/CD — серверная: они дополняют друг друга

Эта статья — часть нашего полного путеводителя по Git. Там — весь маршрут: от первого коммита до продвинутых workflow.

Что такое Git hooks

Git hooks — это скрипты, которые Git запускает автоматически при определённых событиях: перед созданием коммита, после пуша, при мерже. Они живут в директории .git/hooks/ вашего репозитория.

По умолчанию там лежат примеры с расширением .sample — они неактивны. Чтобы активировать хук, достаточно убрать расширение и сделать файл исполняемым:

			# Пример: активировать pre-commit хук
cp .git/hooks/pre-commit.sample .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
		

Скрипт может быть написан на любом языке: bash, Python, Ruby, Node.js — лишь бы интерпретатор был доступен в системе. Хук завершается с кодом 0 — операция продолжается; с ненулевым — Git прерывает её.

Клиентские хуки

Клиентские хуки выполняются на машине разработчика. Они не копируются при клонировании репозитория — каждый разработчик настраивает их сам (или через инструменты вроде Husky).

  • pre-commit — запускается до того, как Git создаст коммит. Здесь запускают линтеры, форматтеры, статический анализ. Если хук вернул ненулевой код — коммит не создаётся.
  • prepare-commit-msg — запускается после создания сообщения коммита по умолчанию, но до его открытия в редакторе. Используется для автоматического добавления номера задачи из названия ветки.
  • commit-msg — получает путь к файлу с сообщением коммита. Здесь проверяют, соответствует ли сообщение формату (например, Conventional Commits).
  • post-commit — запускается после создания коммита. Используется для уведомлений; не влияет на сам коммит.
  • pre-push — запускается перед git push. Здесь запускают тесты или запрещают пуш в защищённые ветки.
  • post-checkout — вызывается после git checkout, git switch и git clone. Удобен для автоматической установки зависимостей после смены ветки.

Серверные хуки

Серверные хуки выполняются на стороне Git-сервера (GitLab, Gitea, собственный сервер). Они позволяют централизованно контролировать все пуши — независимо от настроек на машинах разработчиков.

  • pre-receive — вызывается при получении пуша, до обновления веток. Может отклонить весь пуш целиком: проверяет права, политику коммитов, размер файлов.
  • update — аналог pre-receive, но вызывается отдельно для каждой обновляемой ветки. Позволяет разрешить пуш в одни ветки и запретить в другие.
  • post-receive — запускается после успешного пуша. Используется для уведомлений, триггеров деплоя, синхронизации с внешними системами.

Практические примеры

pre-commit: запуск линтера перед коммитом

Хук проверяет только те файлы, которые добавлены в индекс. Это ускоряет проверку в больших проектах:

			#!/bin/sh
STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$')

if [ -z "$STAGED" ]; then
    exit 0
fi

echo "Running flake8 on staged Python files..."
flake8 $STAGED

if [ $? -ne 0 ]; then
    echo "flake8 check failed. Fix the errors before committing."
    exit 1
fi
		

commit-msg: проверка формата сообщения

Хук проверяет, соответствует ли сообщение коммита формату Conventional Commits (feat: ..., fix: ..., docs: ... и т. д.):

			#!/bin/bash
# .git/hooks/commit-msg

COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")

# Regex для Conventional Commits
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci)(\(.+\))?: .{1,100}$"

if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
    echo "ERROR: Invalid commit message format."
    echo "Expected: feat|fix|docs|style|refactor|test|chore|perf|ci: <message>"
    echo "Example:  feat(auth): add OAuth2 support"
    exit 1
fi

exit 0
		

pre-push: запрет пуша в main/master

Хук предотвращает случайный прямой пуш в защищённые ветки:

			#!/bin/bash
# .git/hooks/pre-push

PROTECTED_BRANCHES="main master"
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)

for BRANCH in $PROTECTED_BRANCHES; do
    if [ "$CURRENT_BRANCH" = "$BRANCH" ]; then
        echo "ERROR: Direct push to $BRANCH is not allowed."
        echo "Please create a feature branch and open a pull request."
        exit 1
    fi
done

exit 0
		

Инструменты: Husky, pre-commit framework, lefthook

Проблема «голых» хуков в том, что .git/hooks/ не попадает в репозиторий. Каждый разработчик должен настраивать их вручную. Инструменты решают эту проблему — хуки версионируются вместе с кодом и устанавливаются автоматически.

Husky — самый популярный выбор для JavaScript-проектов. Хуки описываются в файлах директории .husky/ и устанавливаются через npm install. Интегрируется с lint-staged для запуска проверок только на изменённых файлах.

			# Установка Husky
npm install --save-dev husky
npx husky init
		
			# .husky/pre-commit
npx lint-staged
		

pre-commit framework — мощный инструмент для Python-проектов (и не только). Хуки описываются в .pre-commit-config.yaml. Поддерживает большую экосистему готовых хуков — для Python, Go, Terraform, Docker и других технологий.

			# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.4.0
    hooks:
      - id: ruff-check
      - id: ruff-format
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
		

Lefthook — быстрый инструмент на Go, работает в любом проекте независимо от языка. Конфигурация в lefthook.yml. Поддерживает параллельный запуск хуков, что заметно ускоряет проверку в монорепозиториях.

			# lefthook.yml
pre-commit:
  parallel: true
  jobs:
    - name: lint
      glob: "*.py"
      run: flake8 {staged_files}
    - name: format
      glob: "*.py"
      run: black --check {staged_files}
		

Краткое сравнение инструментов:

  • Husky — лучший выбор для Node.js/фронтенд-проектов. Глубокая интеграция с npm-экосистемой.
  • pre-commit framework — богатая экосистема готовых хуков, идеален для Python и мультиязычных проектов.
  • lefthook — самый быстрый, не зависит от языка, хорошо работает в монорепозиториях с параллельными задачами.

Хуки и CI/CD: дополняют, а не заменяют

Git hooks и CI/CD решают одну задачу — автоматическую проверку кода — но на разных уровнях. Хуки работают локально, до отправки кода на сервер. Это быстрая первая линия обороны: разработчик получает обратную связь за секунды, не дожидаясь очереди в CI.

CI/CD — это серверная автоматизация: она запускается независимо от хуков и не может быть отключена разработчиком. CI выполняет более тяжёлые проверки: интеграционные тесты, сборку Docker-образов, деплой. О том, как выстроить полный пайплайн, читайте в roadmap DevOps-инженера.

Правило: хуки проверяют то, что можно проверить быстро (секунды) — линт, форматирование, формат сообщения. CI проверяет то, что требует инфраструктуры — тесты с базой данных, e2e, performance benchmarks. Дублировать медленные проверки в хуках не стоит — разработчики будут обходить их через git commit --no-verify.

Часто задаваемые вопросы
1
Можно ли обойти Git hook?

Да: флаг --no-verify (или -n) отключает хуки для конкретного коммита или пуша. Именно поэтому клиентские хуки не являются надёжной защитой — они страхуют от случайных ошибок, но не от намеренного обхода. Для строгого контроля используйте серверные хуки или CI/CD.

2
Почему хуки не копируются при git clone?

Директория .git/ полностью локальная и никогда не отправляется на сервер — это намеренное решение Git. Для совместного использования хуков в команде нужны инструменты: Husky, pre-commit framework или lefthook. Они хранят конфигурацию хуков в обычных файлах репозитория и устанавливают их автоматически.

3
На каком языке можно писать хуки?

На любом — bash, Python, Ruby, Node.js, Go и других. Главное, чтобы в первой строке файла был корректный shebang: #!/bin/bash или #!/usr/bin/env python3. Самый универсальный вариант — bash: он есть на любой Unix-подобной системе и не требует установки зависимостей.

Git hooks — мощный инструмент, который превращает локальную разработку в управляемый процесс. Пять минут на настройку pre-commit и commit-msg экономят часы ревью в будущем. Начните с одного хука, убедитесь, что он не замедляет работу, и постепенно добавляйте остальные. Подробнее о том, как hooks вписываются в общий рабочий процесс с ветками и пулл-реквестами, читайте в статье про Git Flow, GitHub Flow и Trunk-Based разработку.

Рекомендуем