Git hooks
Aprenda Git hooks — scripts que rodam automaticamente no ciclo de vida do Git para linting, testes e validação. Inclui exemplo de pre-commit.
O que são Git hooks
Git hooks são scripts que o Git executa automaticamente quando determinados eventos ocorrem — commit, merge, push e outros. Eles permitem adicionar ações personalizadas ao ciclo de vida do Git: executar um linter antes de cada commit, validar o formato de uma mensagem de commit ou bloquear um push se os testes falharem. Hooks são a forma como as equipes impõem portões de qualidade localmente, antes que código ruim saia de uma máquina.
Esta página aborda onde os hooks ficam armazenados, a diferença entre hooks client-side e server-side, os hooks mais úteis com exemplos funcionais, como um hook aborta uma operação, como contornar um hook e como compartilhar hooks com uma equipe.
Onde os hooks ficam armazenados
Todo repositório possui um diretório .git/hooks com scripts de exemplo com o sufixo .sample. Para ativar um hook, adicione um script executável com o nome exato do hook e sem extensão:
ls .git/hooks
# pre-commit.sample commit-msg.sample pre-push.sample ...Remova o sufixo .sample (ou crie o arquivo do zero) e torne-o executável:
chmod +x .git/hooks/pre-commitUm hook pode ser escrito em qualquer linguagem, desde que o arquivo seja executável e comece com uma linha shebang adequada (#!/bin/sh, #!/usr/bin/env python3, #!/usr/bin/env node, etc.). O Git só se importa que o arquivo tenha o nome exato de um hook conhecido, seja executável e retorne um código de saída.
Hooks client-side vs server-side
- Hooks client-side rodam na sua máquina em operações locais como commit e push. São ótimos para linting e testes.
- Hooks server-side (como
pre-receiveepost-receive) rodam no repositório remoto quando ele recebe um push — úteis para impor políticas de forma centralizada.
Os hooks mais usados são client-side:
| Hook | Dispara | Uso típico |
|---|---|---|
pre-commit | Antes de um commit ser criado | Lint e teste de arquivos staged; abortar em caso de falha. |
prepare-commit-msg | Antes do editor de mensagem abrir | Inserir um template ou número de ticket. |
commit-msg | Após a mensagem ser escrita | Impor uma convenção de mensagem. |
post-commit | Após um commit ser concluído | Enviar uma notificação; sem efeito sobre o commit. |
pre-push | Antes de um push ser enviado | Executar a suite de testes completa como portão final. |
Como um hook aborta uma operação
Todo o mecanismo de controle é o código de saída. Para um hook "pre-" (pre-commit, pre-push, commit-msg, …):
- Saída
0→ o hook aprovou a operação e o Git continua. - Saída diferente de zero → o Git cancela a operação. O commit não é criado ou o push não é enviado.
Hooks "post-" (post-commit, post-merge, …) rodam após a ação já ter sido concluída, então seu código de saída é ignorado — eles não podem desfazer nada. Use-os para notificações, não para validação.
Um exemplo de pre-commit
Este hook pre-commit executa o linter do projeto e bloqueia o commit se ele reportar problemas. Como o sh aborta no comando com falha graças ao set -e, não é necessário verificar $? manualmente:
#!/bin/sh
set -e
echo "Running lint..."
npm run lintSe npm run lint sair com código diferente de zero, o set -e propagará esse código e o commit será abortado. Se preferir uma mensagem personalizada, verifique o resultado explicitamente:
#!/bin/sh
if ! npm run lint; then
echo "Lint failed — commit aborted. Fix the issues and try again."
exit 1
fiSalve isso como .git/hooks/pre-commit e execute chmod +x .git/hooks/pre-commit.
Um exemplo de commit-msg
O hook commit-msg recebe um argumento: o caminho para um arquivo temporário contendo a mensagem proposta. Leia esse arquivo, valide-o e saia com código diferente de zero para rejeitar. Este exemplo impõe um prefixo no estilo Conventional Commits:
#!/bin/sh
# $1 is the path to the file containing the commit message
message=$(head -n1 "$1")
pattern='^(feat|fix|docs|style|refactor|test|chore): .+'
if ! echo "$message" | grep -Eq "$pattern"; then
echo "Commit message must start with feat:, fix:, docs:, etc."
exit 1
fiContornando um hook
Um hook é uma rede de segurança, não uma barreira. Quando você genuinamente precisa pular os hooks pre-commit e commit-msg em uma operação, use --no-verify:
git commit --no-verify -m "WIP: skip checks"
git push --no-verifyUse isso com moderação — contornar o linter é como código quebrado escorrega para o histórico.
Compartilhando hooks com uma equipe
Como .git/hooks não é commitado, os hooks não viajam com um clone. As equipes resolvem isso armazenando hooks em um diretório rastreado e apontando o Git para ele:
git config core.hooksPath .githooksAgora o Git procura no diretório rastreado .githooks/ em vez de .git/hooks. Faça commit dos seus scripts lá, marque-os como executáveis e todos os membros da equipe os receberão após executar o mesmo comando git config (ou após um script de configuração fazer isso por eles). Veja Git config para mais informações sobre como armazenar configurações por repositório.
Ferramentas como Husky automatizam exatamente isso para projetos JavaScript, conectando hooks compartilhados durante a instalação. Para políticas que você não pode deixar ninguém contornar com --no-verify, imponha-as com hooks server-side ou a proteção de branches da sua plataforma de hospedagem, pois hooks client-side sempre residem na máquina do desenvolvedor.
Tópicos relacionados
- git commit — o comando em torno do qual os hooks pre-commit e commit-msg funcionam.
- Assinando commits — verificar autoria, frequentemente combinado com hooks.
- Git config — onde
core.hooksPathe outras configurações são armazenadas. - Git alias — atalhos para os comandos que seus hooks executam.