Hooks do Git: automatizando ações locais
1. Introdução aos Hooks do Git
Hooks do Git são scripts personalizados que o Git executa automaticamente quando determinados eventos ocorrem no repositório. Eles permitem automatizar ações locais como validações, notificações, formatação de código e muito mais, sem depender de ferramentas externas.
O ciclo de vida de um hook é simples: ao realizar uma operação como git commit ou git push, o Git verifica se existe um script correspondente no diretório de hooks. Se existir e for executável, ele o executa antes (ou depois) da operação principal. Dependendo do código de saída do script, o Git pode permitir ou bloquear a operação.
É importante distinguir entre hooks locais (lado do cliente) e hooks remotos (lado do servidor). Os hooks locais residem em cada repositório do desenvolvedor e não são enviados para o servidor. Já os hooks remotos são configurados no servidor Git e afetam todos que enviam alterações. Este artigo foca exclusivamente nos hooks locais.
2. Anatomia de um Hook no Git
Cada repositório Git contém um diretório oculto chamado .git/hooks/. Dentro dele, existem arquivos de exemplo com extensão .sample que demonstram a estrutura básica. Para ativar um hook, basta criar um arquivo sem extensão com o nome do evento desejado e torná-lo executável.
A nomenclatura segue o padrão do evento: pre-commit, post-commit, pre-push, commit-msg, prepare-commit-msg, pre-rebase, post-checkout, post-merge, entre outros. Qualquer linguagem de script pode ser usada — shell script, Python, Ruby, Node.js — desde que o arquivo seja executável e tenha um shebang apropriado.
.git/
└── hooks/
├── pre-commit.sample
├── pre-commit
├── commit-msg
├── post-commit
└── pre-push
3. Hooks do Lado do Cliente: Principais Eventos
pre-commit
Executado antes de criar o commit. Ideal para validações como lint, verificação de código, testes unitários rápidos ou prevenção de espaços em branco à direita. Se o script retornar código diferente de zero, o commit é abortado.
prepare-commit-msg
Executado após o editor de mensagens de commit ser aberto. Permite modificar automaticamente a mensagem padrão, como adicionar um template ou número de issue.
commit-msg
Executado depois que o usuário digita a mensagem de commit, mas antes do commit ser finalizado. Excelente para validar o formato da mensagem (convenções como Conventional Commits).
post-commit
Executado após o commit ser criado. Não pode abortar a operação, mas é útil para notificações, logs ou acionar CI local.
pre-push
Executado antes de enviar commits para o remoto. Ideal para rodar testes de integração ou verificar se o branch está atualizado com o remoto.
4. Hooks do Lado do Cliente: Eventos Avançados
pre-rebase
Executado antes de um git rebase. Permite verificar se há alterações não commitadas ou se o branch está em condições seguras para rebase.
post-checkout
Executado após git checkout ou git switch. Útil para configurar o ambiente: instalar dependências, recarregar configurações ou limpar arquivos temporários.
post-merge
Executado após um git merge bem-sucedido. Pode ser usado para atualizar automaticamente dependências com npm install ou composer install.
pre-auto-gc
Executado antes da coleta de lixo automática do Git. Permite cancelar a operação se o desenvolvedor estiver no meio de uma tarefa que não deve ser interrompida.
5. Criando e Gerenciando Hooks Manualmente
Vamos criar um hook pre-commit que impede commits com espaços em branco à direita.
Passo 1: Acesse o diretório de hooks do repositório:
cd .git/hooks
Passo 2: Crie o arquivo pre-commit com o seguinte conteúdo:
#!/bin/sh
# Hook pre-commit: rejeita commits com espaços em branco à direita
if git diff --cached --check | grep -q "trailing whitespace"; then
echo "Erro: Existem espaços em branco à direita nos arquivos staged."
echo "Execute 'git diff --cached --check' para ver os detalhes."
exit 1
fi
Passo 3: Torne o script executável:
chmod +x pre-commit
Agora, ao tentar commitar com espaços em branco à direita, o Git exibirá a mensagem de erro e abortará a operação.
A principal limitação dessa abordagem manual é que os hooks não são versionados nem compartilhados automaticamente com a equipe. Cada desenvolvedor precisa configurar manualmente.
6. Compartilhando Hooks com a Equipe
Para versionar e compartilhar hooks, use git config core.hooksPath para apontar para um diretório dentro do repositório.
Estrutura de projeto:
meu-repositorio/
├── githooks/
│ ├── pre-commit
│ └── commit-msg
├── src/
└── README.md
Configure o caminho dos hooks (cada desenvolvedor precisa executar uma vez):
git config core.hooksPath githooks
Exemplo prático: hook commit-msg que valida mensagens no formato Conventional Commits usando Commitlint.
#!/usr/bin/env node
// githooks/commit-msg
const fs = require('fs');
const commitMsg = fs.readFileSync(process.argv[2], 'utf8').trim();
const pattern = /^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?:\s.+$/;
if (!pattern.test(commitMsg)) {
console.error('Erro: Mensagem de commit não segue o padrão Conventional Commits.');
console.error('Exemplo: feat(api): adiciona novo endpoint de login');
process.exit(1);
}
Essa abordagem permite versionar os hooks e compartilhá-los via git push. A desvantagem é que cada desenvolvedor precisa configurar core.hooksPath manualmente.
7. Boas Práticas e Cuidados com Hooks Locais
Performance: hooks não devem executar processos pesados como testes completos ou builds. Eles devem ser rápidos (menos de 1 segundo) para não frustrar o desenvolvedor. Testes longos devem ficar no CI.
Saída amigável: mensagens de erro claras e específicas ajudam o desenvolvedor a corrigir rapidamente. Use cores ou formatação para destacar erros.
Ignorando hooks temporariamente: use --no-verify no commit ou -n para pular hooks em situações emergenciais. Exemplo:
git commit -m "fix: correção urgente" --no-verify
Evitando efeitos colaterais: hooks não devem modificar arquivos sem aviso prévio. Se um hook alterar arquivos, o desenvolvedor pode perder alterações não commitadas.
8. Depuração e Testes de Hooks
Para testar um hook manualmente, execute o script diretamente no terminal, passando os argumentos esperados. Por exemplo, para testar commit-msg:
echo "minha mensagem" > /tmp/msg.txt
./.git/hooks/commit-msg /tmp/msg.txt
Para depuração, redirecione a saída para um arquivo de log:
#!/bin/sh
exec >> /tmp/hook-debug.log 2>&1
echo "=== pre-commit executado em $(date) ==="
Use variáveis de ambiente para simular diferentes eventos. Por exemplo, GIT_EDITOR pode ser usado para testar prepare-commit-msg.
Exemplo de hook que falha intencionalmente para diagnóstico:
#!/bin/sh
echo "Hook pre-commit falhou intencionalmente para teste"
exit 1
Para diagnosticar, execute o script manualmente e verifique a saída. Se o Git rejeitar a operação, a mensagem de erro do hook aparecerá no terminal.
Referências
- Documentação Oficial do Git: Hooks — Guia completo sobre hooks do Git, incluindo lista de todos os eventos disponíveis.
- Atlassian: Git Hooks — Tutorial prático com exemplos de hooks para automatizar fluxos de trabalho.
- Conventional Commits — Especificação para mensagens de commit padronizadas, frequentemente usada com hooks commit-msg.
- Commitlint — Ferramenta para validar mensagens de commit, integrável com hooks do Git.
- Git Hooks no GitHub — Como usar hooks do Git em workflows do GitHub Actions.
- Husky: Git Hooks Moderno — Ferramenta popular para gerenciar hooks do Git com configuração simplificada.
- Git SCM: Customizando Git — Seção do livro Pro Git sobre hooks e personalização do Git.