pre-commit — небольшой инструмент, который запускает проверки автоматически перед каждым коммитом. Он не заменяет CI, а переносит дешёвые ошибки на самое раннее место: разработчик получает замечание через секунду, а не через десять минут в конвейере. В статье — установка, рабочий конфиг для Python, shell и инфраструктурных файлов, локальные хуки, обновление версий и подключение к CI.

Зачем нужен pre-commit, если есть CI

Хуки Git сами по себе неудобны: их нужно хранить в репозитории, устанавливать в каждый клон и обновлять вручную. pre-commit решает три задачи: хранит конфигурацию в .pre-commit-config.yaml, изолирует окружение каждого хука и запускает проверки только по изменённым файлам. В результате пробелы, сломанный YAML, забытый отладочный вывод и секреты отсекаются до того, как попадут в историю.

Инструмент написан на Python, но проверяет любые файлы. Хуки для Go, Rust, JavaScript и shell работают через собственные окружения и не требуют устанавливать всё в системный Python. Это важно в смешанных репозиториях, где рядом лежат скрипты и фронтенд.

Установка и первый запуск

Ставьте pre-commit через pipx, чтобы не смешивать его с зависимостями проекта. В Debian и Ubuntu пакет также доступен, но версия в репозитории может отставать. После установки выполните pre-commit install в корне клона: будет создан файл .git/hooks/pre-commit, который вызывает проверки.

pipx install pre-commit
cd ~/projects/myapp
pre-commit install
pre-commit --version

Проверить работу до первого коммита можно вручную. Ключ —all-files прогоняет хуки по всему репозиторию, а —files ограничивает список конкретными путями. Если конфигурации ещё нет, образец выводит команда pre-commit sample-config, но лучше сразу написать свою. Ключ —install-hooks заранее скачивает окружения, а —hook-type commit-msg пригодится, если вы проверяете формат сообщений коммита. В репозитории с субмодулями установите хук и в них.

pre-commit run --all-files
pre-commit run --files src/app.py scripts/deploy.sh
pre-commit validate-config .pre-commit-config.yaml

Базовый конфиг

Каждый репозиторий описывается блоком repo и rev. Значение rev — тег или коммит, а не ветка main: ветка делает сборку невоспроизводимой. В примере ниже собраны безопасные проверки, которые почти не дают ложных срабатываний.

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
        args: [--maxkb=1024]
      - id: detect-private-key
      - id: mixed-line-ending
        args: [--fix=lf]

Не включайте всё подряд. Каждый лишний хук увеличивает время коммита и раздражает команду. Начните с пяти-семи проверок, затем добавляйте те, что ловят реальные ошибки в вашем проекте.

Python, shell и форматирование

Для Python удобна связка Ruff и pre-commit-hooks: линтер и форматтер запускаются в изолированном окружении, а версия фиксируется в конфиге. Shell-скрипты проверяет shellcheck, а для Dockerfile и Markdown есть отдельные хуки. Не смешивайте линтер и форматтер в одном проходе, если форматтер меняет файлы: pre-commit сам сообщит, что файлы исправлены, и попросит повторить коммит.

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.9.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format
  - repo: https://github.com/shellcheck-py/shellcheck-py
    rev: v0.10.0.1
    hooks:
      - id: shellcheck
        args: [--severity=warning]

Хуки форматирования по умолчанию меняют файлы и возвращают ненулевой код. Это нормальное поведение: просмотрите diff, добавьте изменения через git add и повторите коммит. Не используйте git commit —no-verify как привычку — так вы обходите и действительно полезные проверки.

Локальные хуки и тесты

Если готового хука нет, опишите локальный. language: system означает, что команда уже установлена в окружении разработчика; pass_filenames: false запрещает передавать имена файлов, что нужно для тестов и сборки. Тип files помогает не запускать проверку на каждом коммите в документацию.

  - repo: local
    hooks:
      - id: unit-tests
        name: fast unit tests
        entry: pytest -q tests/unit
        language: system
        pass_filenames: false
        types: [python]
      - id: compose-config
        name: validate compose file
        entry: docker compose config -q
        language: system
        files: ^docker-compose\.ya?ml$

Медленные проверки выносите в отдельный этап. pre-commit поддерживает stages: укажите manual и запускайте такой хук командой pre-commit run —hook-stage manual. Это удобно для интеграционных тестов, которые не должны тормозить каждый коммит.

pre-commit run --hook-stage manual --all-files
pre-commit autoupdate
pre-commit clean
pre-commit gc

autoupdate поднимает rev до последних тегов, но не проверяет совместимость. После обновления прогоните все хуки и просмотрите diff конфига: иногда новая версия меняет правила и требует правок в коде. Команда clean удаляет окружения хуков, а gc чистит неиспользуемые репозитории.

pre-commit в CI

В конвейере достаточно установить инструмент и запустить проверку по всем файлам. Кэш окружений хуков ускоряет повторные сборки, но не должен скрывать изменения конфигурации. Если хук требует Docker, убедитесь, что в runner есть демон, иначе проверка упадёт не из-за кода.

python -m pip install pre-commit
pre-commit run --all-files --show-diff-on-failure
git diff --exit-code

Флаг —show-diff-on-failure печатает изменения, которые внесли автоисправляющие хуки. Последняя команда страхует от случая, когда хук поправил файл, но вернул нулевой код. В GitLab CI те же команды помещаются в job с образом python и кэшем каталога ~/.cache/pre-commit.

Типичные ошибки

  • rev указывает на ветку, и сборка ломается без изменений в коде;
  • хуки запускаются по всей файловой системе из-за отсутствия exclude;
  • локальный хук требует программу, которой нет у коллег;
  • секреты уже попали в историю — pre-commit не чистит прошлые коммиты;
  • проверка занимает минуты, и разработчики начинают обходить её.

Для исключений используйте exclude с якорем на каталог, например ^vendor/. Секреты, найденные detect-private-key, нужно не просто удалить из файла, а отозвать и заменить: история Git остаётся доступной.

Итог

pre-commit окупается в командах любого размера, но особенно в небольших: он делает стиль и базовые проверки частью процесса, а не поводом для споров на ревью. Настройте минимальный набор хуков, зафиксируйте версии, добавьте быстрые тесты и продублируйте запуск в CI. Тогда локальные проверки экономят время, а конвейер остаётся последней линией защиты. Начните с check-yaml и trailing-whitespace, а расширяйте конфиг по мере появления реальных ошибок.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте