Como usar git hooks para automatizar validações antes do commit

1. Introdução aos Git Hooks: O que são e por que usar

Git hooks são scripts executados automaticamente quando eventos específicos ocorrem no ciclo de vida do Git, como commit, push ou merge. Eles funcionam como gatilhos que permitem automatizar validações e ações antes ou depois dessas operações.

Os principais benefícios do uso de hooks incluem:
- Padronização de código: Garantir que todo código commitado siga as mesmas regras de estilo e linting
- Prevenção de erros: Bloquear commits que contenham erros de sintaxe, testes quebrados ou vulnerabilidades
- Economia de tempo em revisões: Reduzir correções manuais em code reviews, já que problemas comuns são resolvidos automaticamente

Os hooks são armazenados no diretório .git/hooks de cada repositório. Existem dois tipos principais:
- Client-side: Executados na máquina do desenvolvedor (pre-commit, commit-msg, pre-push)
- Server-side: Executados no servidor remoto (pre-receive, update, post-receive)

2. Configuração do Ambiente e Hooks Básicos

Para começar a usar hooks, acesse o diretório .git/hooks do seu repositório. Você encontrará arquivos com sufixo .sample, como pre-commit.sample. Para ativar um hook, remova o sufixo:

cd .git/hooks
cp pre-commit.sample pre-commit

Hook pre-commit

Este hook é executado antes do snapshot ser criado. É ideal para validações rápidas:

#!/bin/sh
# .git/hooks/pre-commit

echo "Executando validações pré-commit..."

# Verificar se há arquivos staged
if git diff --cached --name-only | grep -q .; then
    echo "Arquivos encontrados. Prosseguindo..."
else
    echo "Nenhum arquivo staged. Abortando commit."
    exit 1
fi

Hook commit-msg

Valida o formato da mensagem de commit. Exemplo usando Conventional Commits:

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

commit_message=$(cat "$1")

# Regex para validar Conventional Commits
pattern="^(feat|fix|docs|style|refactor|perf|test|chore|ci)(\(.+\))?: .{1,72}$"

if ! echo "$commit_message" | grep -qE "$pattern"; then
    echo "ERRO: Mensagem de commit inválida!"
    echo "Formato esperado: tipo(escopo): descrição"
    echo "Exemplo: feat(login): adiciona validação de email"
    exit 1
fi

3. Automatizando Validações com Scripts Customizados

Linting automático com ESLint (JavaScript/TypeScript)

#!/bin/sh
# .git/hooks/pre-commit

echo "Executando ESLint nos arquivos staged..."

staged_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|jsx|ts|tsx)$')

if [ -n "$staged_files" ]; then
    npx eslint $staged_files
    if [ $? -ne 0 ]; then
        echo "ERRO: ESLint encontrou problemas. Corrija antes de commitar."
        exit 1
    fi
fi

Formatação automática com Prettier

#!/bin/sh
# .git/hooks/pre-commit

echo "Formatando arquivos com Prettier..."

staged_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|jsx|ts|tsx|json|css|md)$')

if [ -n "$staged_files" ]; then
    npx prettier --write $staged_files
    git add $staged_files
fi

Testes unitários rápidos

#!/bin/sh
# .git/hooks/pre-commit

echo "Executando testes unitários..."

# Executa apenas testes relacionados aos arquivos modificados
npx jest --findRelatedTests $(git diff --cached --name-only --diff-filter=ACM)

if [ $? -ne 0 ]; then
    echo "ERRO: Testes falharam. Corrija antes de commitar."
    exit 1
fi

4. Gerenciamento de Hooks com Ferramentas Modernas

Husky (Node.js)

Husky simplifica o gerenciamento de hooks em projetos JavaScript:

# Instalação
npm install husky --save-dev

# Ativação
npx husky install

# Adicionar hook
npx husky add .husky/pre-commit "npx lint-staged"

Configuração no package.json:

{
  "scripts": {
    "prepare": "husky install"
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{json,css,md}": ["prettier --write"]
  }
}

Pre-commit Framework (Python/Multi-linguagem)

Ideal para projetos Python ou multi-linguagem:

# Instalação
pip install pre-commit

# Criar arquivo de configuração
touch .pre-commit-config.yaml

Exemplo de .pre-commit-config.yaml:

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

  - repo: https://github.com/psf/black
    rev: 23.3.0
    hooks:
      - id: black

  - repo: https://github.com/PyCQA/flake8
    rev: 6.0.0
    hooks:
      - id: flake8

5. Hooks Avançados e Integração com Workflows

Hook prepare-commit-msg

Gera automaticamente mensagens de commit baseadas em issues:

#!/bin/sh
# .git/hooks/prepare-commit-msg

branch_name=$(git rev-parse --abbrev-ref HEAD)
issue_number=$(echo $branch_name | grep -oE 'issue-[0-9]+')

if [ -n "$issue_number" ]; then
    sed -i "1s/^/[$issue_number] /" "$1"
fi

Hook pre-push

Validações mais pesadas antes do push:

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

echo "Executando testes de integração..."
npm run test:integration

if [ $? -ne 0 ]; then
    echo "ERRO: Testes de integração falharam. Push bloqueado."
    exit 1
fi

echo "Verificando vulnerabilidades..."
npm audit

if [ $? -ne 0 ]; then
    echo "ERRO: Vulnerabilidades encontradas. Push bloqueado."
    exit 1
fi

6. Boas Práticas e Resolução de Problemas Comuns

Ignorar hooks temporariamente

Use a flag --no-verify para pular hooks em situações emergenciais:

git commit --no-verify -m "fix: correção crítica de segurança"

Tratamento de erros

Sempre forneça mensagens claras e use exit codes apropriados:

#!/bin/sh
# .git/hooks/pre-commit

run_linter() {
    echo "Verificando lint..."
    npx eslint src/
    return $?
}

run_tests() {
    echo "Executando testes..."
    npm test
    return $?
}

if ! run_linter; then
    echo "❌ Lint falhou. Execute: npm run lint:fix"
    exit 1
fi

if ! run_tests; then
    echo "❌ Testes falharam. Verifique os erros acima."
    exit 1
fi

echo "✅ Todas as verificações passaram!"

Versionamento de hooks

Mantenha os hooks em pasta dedicada e crie script de instalação:

# Estrutura de diretórios
scripts/
  git-hooks/
    pre-commit
    commit-msg
    pre-push
  install-hooks.sh

Script de instalação:

#!/bin/sh
# scripts/install-hooks.sh

echo "Instalando hooks do Git..."
cp scripts/git-hooks/* .git/hooks/
chmod +x .git/hooks/*
echo "✅ Hooks instalados com sucesso!"

7. Exemplo Prático Completo: Pipeline de Validação Pré-Commit

Configuração completa com Husky + ESLint + Prettier + Testes

# 1. Inicializar projeto Node.js
npm init -y

# 2. Instalar dependências
npm install --save-dev husky lint-staged eslint prettier jest

# 3. Configurar Husky
npx husky install
npm pkg set scripts.prepare="husky install"

# 4. Adicionar hook pre-commit
npx husky add .husky/pre-commit "npx lint-staged"

# 5. Configurar lint-staged no package.json

package.json:

{
  "scripts": {
    "prepare": "husky install",
    "test": "jest",
    "lint": "eslint src/",
    "format": "prettier --write src/"
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write",
      "jest --findRelatedTests"
    ],
    "*.{json,css,md}": ["prettier --write"]
  }
}

Demonstração de fluxo

Commit bem-sucedido:

$ git add src/index.js
$ git commit -m "feat: adiciona função de validação"

✔ lint-staged executou com sucesso
✔ ESLint verificou e corrigiu
✔ Prettier formatou
✔ Testes relacionados passaram
✅ Commit criado com sucesso!

Commit bloqueado por erro:

$ git add src/broken.js
$ git commit -m "fix: correção temporária"

❌ ESLint encontrou erros:
   src/broken.js:10:3 - Unexpected console statement (no-console)
❌ Testes falharam:
   FAIL src/broken.test.js
   ● Teste de validação › deve retornar true para input válido

✖ Commit abortado. Corrija os erros e tente novamente.

Este pipeline garante que apenas código limpo, formatado e testado seja commitado, mantendo a qualidade do repositório e economizando tempo em revisões.

Referências