$ sudo teach IT
Модуль 6 · Интеграция

pre-commit

Автоматическая проверка кода перед каждым коммитом.

🔌 Интеграция 🕓 ~20 минут
🔒

Что такое 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       # форматирование

Строки конфигурации:

Поле Описание Пример
repoURL репозитория с хуками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 (форматирование). Порядок имеет значение:

  1. ruff --fix — сначала линтинг исправляет код (удаляет импорты, исправляет типы и т.д.)
  2. 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 всегда запускайте 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:

  1. Проверьте изменения в .pre-commit-config.yaml
  2. Запустите pre-commit run --all-files — убедитесь, что новые версии работают
  3. Закоммитьте обновление конфига
💡 Совет: Регулярно (раз в месяц) запускайте 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
💡 Совет: Если в CI pre-commit проходит, а локально нет — вероятно, проблема в разных версиях инструментов. Убедитесь, что все используют одинаковые версии через rev в .pre-commit-config.yaml.
🔄

Экосистема pre-commit хуков

Кроме Ruff, существует множество полезных pre-commit хуков. Вот наиболее популярные:

Репозиторий ID хука Описание
pre-commit-hookstrailing-whitespaceУдаляет пробелы в конце строк
pre-commit-hooksend-of-file-fixerДобавляет пустую строку в конце файла
pre-commit-hookscheck-yamlПроверяет корректность YAML
pre-commit-hookscheck-added-large-filesПроверяет, что не добавляются большие файлы
pre-commit-hooksdetect-private-keyПроверяет на наличие приватных ключей
pre-commit-hookscheck-merge-conflictПроверяет на оставленные конфликты слияния
pre-commit-hooksmixed-line-endingПриводит окончания строк к единому стилю
mypymypyСтатическая типизация
check-jsoncheck-jsonПроверяет корректность JSON
check-tomlcheck-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 команда Описание
commitgit commitПеред созданием коммита (по умолчанию)
pushgit pushПеред отправкой изменений на сервер
merge-commitgit mergeПеред созданием merge-commit
pre-pushgit 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
✅

Лучшие практики

  1. Версионируйте хуки. Всегда используйте конкретные теги (v0.8.0), а не ветки (main). Это гарантирует воспроизводимость.
  2. Документируйте pre-commit. Добавьте раздел в README проекта о том, как установить и настроить pre-commit.
  3. Не отключайте хуки без причины. --no-verify — это запах, который говорит о проблемах в процессе.
  4. Добавьте CI проверку. GitHub Actions workflow с pre-commit run --all-files гарантирует, что хуки проходят даже если кто-то их отключил локально.
  5. Регулярно обновляйте. Раз в месяц запускайте pre-commit autoupdate. Новые версии Ruff содержат новые правила и улучшения.
  6. Начинайте с малого. Не добавляйте сразу 20 хуков. Начните с Ruff и базовых pre-commit-hooks, потом добавляйте остальные.
  7. Используйте stages. Тяжёлые проверки (mypy, pytest) лучше запускать только при push, а быстрые (trailing-whitespace, ruff) — при каждом commit.
  8. Установите pre-commit в CI. Это страховка на случай, если кто-то забыл выполнить pre-commit install локально.
⚠

Частые проблемы и их решение

❌ Проблема: pre-commit run --all-files вылетает с ошибкой "ruff: not found".
✅ Решение: pre-commit сам устанавливает хуки в изолированное окружение. Но если Ruff не установлен в системе, некоторые хуки могут не работать. Установите pip install ruff или убедитесь, что ruff есть в PATH.
❌ Проблема: Pre-commit зависает при установке хуков.
✅ Решение: Это может быть связано с медленным интернетом или блокировкой GitHub. Попробуйте pre-commit clean для очистки кэша, затем снова pre-commit install. В корпоративных сетях может понадобиться настроить proxy.
❌ Проблема: Ruff исправил файлы, но коммит всё равно не проходит.
✅ Решение: Это нормально! --exit-non-zero-on-fix завершается с ошибкой, если были исправления. Сделайте git add . (чтобы добавить исправленные файлы) и снова git commit.
❌ Проблема: Pre-commit работает слишком медленно.
✅ Решение: Pre-commit проверяет только изменённые файлы (если не указано --all-files). Если у вас очень большой проект, добавьте exclude для папок, которые не нужно проверять (migrations, vendor). Первый запуск после установки всегда медленный — pre-commit кэширует окружения.
❌ Проблема: pre-commit autoupdate не находит новые версии.
✅ Решение: Autoupdate ищет теги в репозитории хука. Если репозиторий использует теги вида v0.8.0 (как ruff-pre-commit), autoupdate найдёт последний тег. Проверьте теги на GitHub: git ls-remote --tags https://github.com/astral-sh/ruff-pre-commit.
📝

Итоги

💡 Что нужно запомнить:
  1. Установи pre-commit глобально: pipx install pre-commit.
  2. Создай .pre-commit-config.yaml с хуками ruff и ruff-format.
  3. Запусти pre-commit install — теперь хуки работают при каждом коммите.
  4. Порядок важен: сначала ruff --fix, потом ruff-format.
  5. После --fix сделай git add и повтори коммит (это нормально).
  6. Регулярно обновляй хуки через pre-commit autoupdate.
  7. Добавь pre-commit run --all-files --verbose в CI для гарантии прохождения.
  8. git commit --no-verify — только для экстренных случаев.

Урок 6.2: pre-commit

5 вопросов