pre-commit
Автоматическая проверка кода перед каждым коммитом.
Что такое pre-commit
pre-commit — это инструмент для запуска хуков (проверок) перед каждым git commit. Если хук завершается с ошибкой, коммит не создаётся. Это гарантирует, что плохой код никогда не попадёт в репозиторий.
Ключевые возможности pre-commit:
- Запуск проверок только для изменённых файлов (быстро)
- Поддержка сотен готовых хуков (линтеры, форматтеры, проверки)
- Изолированное окружение для каждого хука
- Версионирование хуков через теги в Git
- Простая конфигурация через YAML
Установка pre-commit
# Через pip (глобально)
pip install pre-commit
# Через pipx (рекомендуется)
pipx install pre-commit
# Через Homebrew (macOS)
brew install pre-commit
# Через conda
conda install -c conda-forge pre-commit
pipx install pre-commit — это устанавливает pre-commit в изолированное окружение и делает его доступным глобально, без конфликтов с зависимостями проекта.
.pre-commit-config.yaml
Файл конфигурации .pre-commit-config.yaml создаётся в корне репозитория. Вот полный пример с Ruff:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0 # используй актуальную версию
hooks:
- id: ruff # линтинг + автоисправление
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format # форматирование
Строки конфигурации:
| Поле | Описание | Пример |
|---|---|---|
repo | URL репозитория с хуками | https://github.com/astral-sh/ruff-pre-commit |
rev | Версия (тег или коммит) | v0.8.0 |
hooks | Список хуков из этого репозитория | - id: ruff |
args | Аргументы командной строки для хука | [--fix] |
rev должна быть конкретным тегом (например, v0.8.0), а не main или master. Использование ветки может привести к неожиданным изменениям при обновлении.
Хуки ruff и ruff-format
ruff (линтинг)
Хук ruff запускает ruff check на изменённых файлах. С аргументом --fix он также применяет автоисправления. С аргументом --exit-non-zero-on-fix он завершается с ошибкой, если были исправления — это сигнал, что нужно сделать git add исправленных файлов и повторить коммит.
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
# или без автоисправления, только проверка:
# args: []
# можно указать конкретные правила:
# args: [--fix, --select, "I"]
# можно передать дополнительный конфиг:
# args: [--fix, --config, pyproject.toml]
ruff-format (форматирование)
Хук ruff-format запускает ruff format — форматирование кода по PEP 8. Он проверяет, что файлы отформатированы. Если нет — форматирует и сообщает об ошибке (коммит не проходит).
- id: ruff-format
# можно указать аргументы форматирования:
# args: [--line-length, "100", --quote-style, "double"]
Другие хуки ruff-pre-commit
| ID хука | Описание |
|---|---|
ruff | Линтинг (ruff check) |
ruff-format | Форматирование (ruff format) |
Порядок хуков важен
Всегда ставьте ruff (линтинг с --fix) перед ruff-format (форматирование). Порядок имеет значение:
- ruff --fix — сначала линтинг исправляет код (удаляет импорты, исправляет типы и т.д.)
- ruff-format — затем форматирование применяется к уже исправленному коду
Если поставить форматирование перед линтингом, то после исправлений Ruff'ом код может стать неотформатированным.
# Правильный порядок:
hooks:
- id: ruff # 1. линтинг + --fix
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format # 2. форматирование
# Неправильный порядок:
hooks:
- id: ruff-format # сначала форматирование
- id: ruff # потом линтинг — может переформатировать
ruff с --fix изменяет файлы, коммит будет отклонён (из-за --exit-non-zero-on-fix). Нужно сделать git add исправленных файлов и снова запустить коммит. Это нормальное поведение — pre-commit страхует от случайных изменений.
Установка и запуск хуков
Установка (однократно)
# Установить хуки в .git/hooks/
pre-commit install
# При каждом git commit будут запускаться хуки
Запуск вручную
# Запустить на всех файлах (первоначальная проверка)
pre-commit run --all-files
# Запустить только конкретный хук
pre-commit run ruff --all-files
pre-commit run ruff-format --all-files
# Запустить только на изменённых файлах (как при коммите)
pre-commit run
# Запустить с подробным выводом
pre-commit run --all-files --verbose
pre-commit run --all-files один раз, чтобы убедиться, что все хуки работают. Это также приведёт существующий код в соответствие со стандартами.
Расширенная конфигурация
Полный файл .pre-commit-config.yaml с дополнительными хуками:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
types_or: [python, pyi]
stages: [commit]
- id: ruff-format
types_or: [python, pyi]
# Дополнительные полезные хуки
- 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 # проверяет YAML файлы
- id: check-toml # проверяет TOML файлы
- id: check-added-large-files # проверяет большие файлы
args: [--maxkb=500]
- id: check-merge-conflict # проверяет на конфликты
- id: detect-private-key # проверяет на приватные ключи
- repo: https://github.com/python-poetry/poetry
rev: 1.8.0
hooks:
- id: poetry-check # проверяет pyproject.toml
- id: poetry-lock # обновляет poetry.lock
Опции хуков
| Опция | Описание | Пример |
|---|---|---|
args | Аргументы командной строки | [--fix] |
types_or | Типы файлов для проверки | [python, pyi] |
stages | На каком этапе запускать | [commit, push] |
exclude | Исключить файлы по шаблону | migrations/ |
files | Проверять только эти файлы | src/ |
# Пример с exclude — не проверять миграции
- id: ruff
args: [--fix]
exclude: ^.*migrations/.*\.py$
# Пример с files — проверять только src/
- id: ruff
args: [--fix]
files: ^src/
Обновление хуков (autoupdate)
Со временем выходят новые версии Ruff и других инструментов. pre-commit предоставляет команду autoupdate для автоматического обновления всех версий в .pre-commit-config.yaml:
# Обновить все хуки до последних стабильных версий
pre-commit autoupdate
# Обновить только Ruff
pre-commit autoupdate --repo https://github.com/astral-sh/ruff-pre-commit
# Обновить с dry-run (посмотреть что будет)
pre-commit autoupdate --dry-run
После autoupdate:
- Проверьте изменения в
.pre-commit-config.yaml - Запустите
pre-commit run --all-files— убедитесь, что новые версии работают - Закоммитьте обновление конфига
pre-commit autoupdate. Новые версии Ruff содержат больше правил, исправления багов и улучшения производительности. После обновления прогоните все хуки на всём проекте.
Стратегии использования pre-commit
Стратегия 1: Только проверка (без автофикса)
Подходит для больших команд, где нужен строгий контроль:
hooks:
- id: ruff # только проверка
- id: ruff-format # только проверка форматирования
Стратегия 2: Автофикс + проверка (рекомендуется)
Подходит для небольших и средних команд:
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
Стратегия 3: Поэтапное внедрение
Для legacy проектов — сначала только предупреждения, потом строгие проверки:
# Этап 1: только предупреждения, не блокировать коммит
- id: ruff
args: [--quiet]
always_run: false
# Этап 2: блокировать коммит
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
# Этап 3: добавить форматирование
- id: ruff-format
Стратегия 4: Разные правила для разных папок
Используйте несколько хуков с разными аргументами для разных частей проекта:
hooks:
# Строгие правила для production кода
- id: ruff
name: ruff (production)
args: [--fix, --select, "ALL"]
files: ^src/
# Мягкие правила для тестов
- id: ruff
name: ruff (tests)
args: [--fix, --ignore, "D,ANN,S101"]
files: ^tests/
Обход pre-commit (с осторожностью!)
Иногда нужно сделать коммит, даже если хуки не проходят. Для этого есть флаг --no-verify:
# Пропустить pre-commit хуки (НЕ РЕКОМЕНДУЕТСЯ)
git commit --no-verify -m "fix: urgent bug"
--no-verify только в крайних случаях (например, срочный фикс на production сломанного CI). После обхода обязательно исправьте код и сделайте нормальный коммит. В хорошей команде обход pre-commit — это исключение, а не правило.
Лучший способ — исправить код или добавить # noqa для конкретной строки:
# Добавить игнорирование для конкретного случая (в коде):
Pre-commit в CI
Вы можете использовать pre-commit и в CI, чтобы гарантировать, что все хуки проходят на сервере. Это полезно, если кто-то в команде забыл установить pre-commit локально.
# .github/workflows/pre-commit.yml
name: Pre-commit
on: [push, pull_request]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install pre-commit
- run: pre-commit run --all-files
rev в .pre-commit-config.yaml.
Экосистема pre-commit хуков
Кроме Ruff, существует множество полезных pre-commit хуков. Вот наиболее популярные:
| Репозиторий | ID хука | Описание |
|---|---|---|
pre-commit-hooks | trailing-whitespace | Удаляет пробелы в конце строк |
pre-commit-hooks | end-of-file-fixer | Добавляет пустую строку в конце файла |
pre-commit-hooks | check-yaml | Проверяет корректность YAML |
pre-commit-hooks | check-added-large-files | Проверяет, что не добавляются большие файлы |
pre-commit-hooks | detect-private-key | Проверяет на наличие приватных ключей |
pre-commit-hooks | check-merge-conflict | Проверяет на оставленные конфликты слияния |
pre-commit-hooks | mixed-line-ending | Приводит окончания строк к единому стилю |
mypy | mypy | Статическая типизация |
check-json | check-json | Проверяет корректность JSON |
check-toml | check-toml | Проверяет корректность TOML |
# Пример с дополнительными хуками
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=500]
- id: detect-private-key
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
Стадии Git hooks
Pre-commit поддерживает разные стадии Git hooks. По умолчанию хуки запускаются на стадии commit, но вы можете настроить их для других стадий:
| Стадия | Git команда | Описание |
|---|---|---|
commit | git commit | Перед созданием коммита (по умолчанию) |
push | git push | Перед отправкой изменений на сервер |
merge-commit | git merge | Перед созданием merge-commit |
pre-push | git push | Специальная стадия push-хука |
manual | — | Только ручной запуск через pre-commit run |
# Пример: запускать Ruff только при push, а базовые проверки при commit
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
stages: [commit] # только при commit
- id: end-of-file-fixer
stages: [commit] # только при commit
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix]
stages: [push] # только при push
- id: ruff-format
stages: [push] # только при push
Чтобы использовать только push-хуки:
pre-commit install --hook-type pre-push
Управление кэшем pre-commit
pre-commit кэширует скачанные репозитории и установленные зависимости. Кэш обычно находится в ~/.cache/pre-commit. Вот основные команды для управления кэшем:
# Очистить кэш (если проблемы с установкой хуков)
pre-commit clean
# Показать использование кэша
pre-commit clean --all
# Показать информацию о текущих хуках
pre-commit validate-config # проверить .pre-commit-config.yaml
pre-commit gc # сборка мусора в кэше
# Где находится кэш
echo $PRE_COMMIT_HOME # по умолчанию ~/.cache/pre-commit
Если у вас несколько проектов с pre-commit, кэш разделяется между ними. Это экономит место и время.
Шаблоны конфигурации для разных проектов
Шаблон: Новый проект (строгие правила)
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix, --select, "ALL", --exit-non-zero-on-fix]
- id: ruff-format
- 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-toml
Шаблон: Django проект
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
exclude: ^.*migrations/.*$
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
Шаблон: Библиотека с mypy
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.12.0
hooks:
- id: mypy
args: [--strict]
additional_dependencies: [types-requests, types-python-dateutil]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Шаблон: Poetry проект
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/python-poetry/poetry
rev: 1.8.0
hooks:
- id: poetry-check
- id: poetry-lock
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Лучшие практики
- Версионируйте хуки. Всегда используйте конкретные теги (
v0.8.0), а не ветки (main). Это гарантирует воспроизводимость. - Документируйте pre-commit. Добавьте раздел в README проекта о том, как установить и настроить pre-commit.
- Не отключайте хуки без причины.
--no-verify— это запах, который говорит о проблемах в процессе. - Добавьте CI проверку. GitHub Actions workflow с
pre-commit run --all-filesгарантирует, что хуки проходят даже если кто-то их отключил локально. - Регулярно обновляйте. Раз в месяц запускайте
pre-commit autoupdate. Новые версии Ruff содержат новые правила и улучшения. - Начинайте с малого. Не добавляйте сразу 20 хуков. Начните с Ruff и базовых pre-commit-hooks, потом добавляйте остальные.
- Используйте stages. Тяжёлые проверки (mypy, pytest) лучше запускать только при push, а быстрые (trailing-whitespace, ruff) — при каждом commit.
- Установите pre-commit в CI. Это страховка на случай, если кто-то забыл выполнить
pre-commit installлокально.
Частые проблемы и их решение
pre-commit run --all-files вылетает с ошибкой "ruff: not found".
pip install ruff или убедитесь, что ruff есть в PATH.
pre-commit clean для очистки кэша, затем снова pre-commit install. В корпоративных сетях может понадобиться настроить proxy.
--exit-non-zero-on-fix завершается с ошибкой, если были исправления. Сделайте git add . (чтобы добавить исправленные файлы) и снова git commit.
--all-files). Если у вас очень большой проект, добавьте exclude для папок, которые не нужно проверять (migrations, vendor). Первый запуск после установки всегда медленный — pre-commit кэширует окружения.
pre-commit autoupdate не находит новые версии.
v0.8.0 (как ruff-pre-commit), autoupdate найдёт последний тег. Проверьте теги на GitHub: git ls-remote --tags https://github.com/astral-sh/ruff-pre-commit.
Итоги
- Установи pre-commit глобально:
pipx install pre-commit. - Создай
.pre-commit-config.yamlс хукамиruffиruff-format. - Запусти
pre-commit install— теперь хуки работают при каждом коммите. - Порядок важен: сначала
ruff --fix, потомruff-format. - После
--fixсделайgit addи повтори коммит (это нормально). - Регулярно обновляй хуки через
pre-commit autoupdate. - Добавь
pre-commit run --all-files --verboseв CI для гарантии прохождения. git commit --no-verify— только для экстренных случаев.
Урок 6.2: pre-commit
5 вопросов