pre-commit gerencia hooks que verificam arquivos antes de um commit. Ele oferece feedback local rápido para espaços finais, YAML inválido, formatação e lint, mas não substitui revisão, testes nem CI.

Comece com poucas verificações confiáveis. Hooks lentos ou instáveis incentivam bypass e deixam de cumprir seu objetivo.

Instalar e configurar

python -m pip install pre-commit
pre-commit install

Crie .pre-commit-config.yaml:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: check-yaml
      - id: end-of-file-fixer
      - id: trailing-whitespace

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.12.7
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format

Os números acima são exemplos de versões fixadas, não uma instrução para usar versões antigas. Consulte os repositórios oficiais, escolha versões compatíveis e deixe atualizações para uma mudança revisável. A documentação do pre-commit explica instalação, configuração e execução.

Execute a primeira validação em todo o projeto:

pre-commit run --all-files

Alguns hooks modificam arquivos. Revise o diff, execute novamente e só então faça commit. O primeiro uso pode baixar ambientes isolados e levar mais tempo; execuções seguintes reutilizam o cache.

Integrar Ruff e testes

O guia de Ruff para Python mostra como centralizar regras no pyproject.toml. Mantenha a mesma configuração para terminal, hook e CI. Testes completos podem ser lentos demais para cada commit; rode uma seleção rápida localmente e a suíte inteira na automação.

Na CI:

pre-commit run --all-files --show-diff-on-failure
pytest

Isso evita depender da instalação local do hook. Veja também o fluxo de GitHub Actions para Python.

Atualização e segurança

pre-commit autoupdate propõe revisões mais novas. Trate o resultado como atualização de dependência: leia changelog, rode em uma branch e revise mudanças produzidas. Hooks executam código no ambiente de desenvolvimento e na CI, por isso use repositórios confiáveis e revisões fixas.

  • Evite hooks que dependem de rede durante cada commit.
  • Exclua arquivos gerados apenas quando houver justificativa.
  • Não coloque correções funcionais complexas em hooks.
  • Documente como instalar e como executar manualmente.
  • Mantenha a CI como fonte final de validação.

Uma configuração pequena reduz ruído nas revisões e detecta problemas antes do push. O valor vem da consistência: mesmos comandos, mesmas versões e resultados previsíveis para toda a equipe.

Integrar Ruff sem duplicar tarefas

Cada hook deve ter uma responsabilidade clara. Deixe os hooks básicos cuidarem da higiene dos arquivos e use Ruff para lint e formatação de Python. Dois formatadores sobre o mesmo arquivo geram diffs ruidosos e regras conflitantes.

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.12.4
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format

A revisão é um exemplo fixo, não uma afirmação sobre a versão mais recente. Verifique o release atual e a compatibilidade antes de adotá-lo. Centralize as regras no pyproject.toml, lido também pelo editor e pela CI:

[tool.ruff]
target-version = "py311"
line-length = 88

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]

Execute correções de lint antes da formatação. Quando um hook altera um arquivo, o commit para para que você revise e adicione o novo diff. Rode novamente; uma segunda execução limpa confirma que o resultado ficou estável.

Controlar quais arquivos entram

O pre-commit trabalha normalmente sobre arquivos preparados para o commit. Use files, exclude e types para ignorar código gerado, dependências vendorizadas ou fixtures que precisam preservar espaços inválidos. Mantenha exceções pequenas e explique as menos óbvias.

      - id: check-yaml
        exclude: ^tests/fixtures/invalid/
      - id: trailing-whitespace
        types: [text]

Para investigar, rode pre-commit run check-yaml --all-files --verbose ou limite a arquivos com pre-commit run --files caminho/a.py caminho/b.py. Não exclua uma pasta inteira antes de confirmar se a falha não representa um defeito real.

Use outros estágios somente quando houver motivo. Formatação rápida combina com pre-commit; convenções de mensagem podem usar commit-msg; testes de integração demorados pertencem à CI.

Adotar em um repositório existente

A instalação do hook não verifica o histórico. A primeira execução em todos os arquivos pode produzir um diff grande. Faça essa limpeza em um commit isolado, separado de funcionalidades, para preservar revisão e git blame.

python -m pip install -r requirements-dev.txt
pre-commit install --install-hooks
pre-commit run --all-files

Fixe também a versão do pacote pre-commit nas dependências de desenvolvimento. git commit --no-verify permite ignorar o hook local, portanto a CI continua sendo a autoridade. Documente o bypass como saída excepcional, não como rotina.

Em monorepos, mantenha a configuração na raiz do Git e filtre cada pacote. Se um comando precisa rodar em outra pasta, crie um script explícito; não dependa do diretório em que o desenvolvedor abriu o terminal.

Hooks locais e segurança

Um hook repo: local pode executar uma ferramenta do próprio projeto:

  - repo: local
    hooks:
      - id: unit-tests-fast
        name: testes unitários rápidos
        entry: python -m pytest -q tests/unit
        language: system
        pass_filenames: false

language: system usa o ambiente ativo e oferece menos isolamento. Use essa opção apenas quando o projeto já controla as dependências. Se o comando aceita caminhos, permita que pre-commit envie os nomes; desativar isso pode transformar uma verificação incremental em uma suíte completa.

Hooks executam código de terceiros com acesso ao repositório. Prefira repositórios oficiais, revisões fixas e revisão cuidadosa de mudanças de mantenedor ou origem. Não disponibilize segredos de produção ao processo.

Repetir a política na CI

O resultado obrigatório deve existir na CI:

python -m pre_commit run --all-files --show-diff-on-failure

Execute em checkout limpo. Se um formatador modificar arquivos, o job falha e mostra o diff, sem tentar criar commit. Ao usar cache, inclua a versão do Python e o hash de .pre-commit-config.yaml na chave para evitar ambientes obsoletos.

Mantenha testes em etapa separada. Assim, o erro indica claramente se houve problema de estilo/configuração ou de comportamento, e os jobs podem rodar em paralelo. O guia de GitHub Actions para Python mostra como estruturar essa automação.

Atualizar e diagnosticar

Trate pre-commit autoupdate como atualização de dependência: rode em uma branch, leia notas de versão, revise o YAML e execute todos os arquivos e testes. Atualizações isoladas são mais fáceis de reverter.

Se a falha ocorre somente na CI, compare Python, sistema operacional, locale, finais de linha e revisões. pre-commit clean recria ambientes em cache e ajuda no diagnóstico; pre-commit gc remove ambientes sem uso. Nenhum deles deve mascarar uma configuração incorreta.

Uma configuração saudável é rápida, determinística e compreensível. Comece por problemas com correção objetiva, acompanhe o tempo local e envie verificações caras para a CI. Essa disciplina faz o hook ser útil de verdade.

Checklist de revisão

Antes do merge, clone o repositório em uma pasta limpa e siga a instalação documentada. Confirme que a execução completa passa duas vezes: a primeira pode corrigir arquivos, mas a segunda precisa ficar limpa. Crie temporariamente um YAML inválido e uma violação do Ruff para provar que os hooks selecionam os caminhos esperados.

Revise cada repo, a revisão fixa e a finalidade do hook. Confira se exclusões correspondem a fixtures ou arquivos gerados reais, se nenhum segredo aparece em args e se hooks locais funcionam a partir da raiz do Git. Compare o resultado local com o job de CI no mesmo commit.

Meça também uma execução comum apenas sobre arquivos preparados. Se ficar lenta, identifique o hook caro com saída detalhada, restrinja o escopo ou leve a verificação para CI. Não esconda lentidão desabilitando uma regra importante. Registre quem mantém a configuração e quando revisar versões.