Git hooks
Impara i Git hooks: script eseguiti automaticamente nel ciclo di vita di Git per linting, test e validazione. Include un esempio pre-commit.
Cosa sono i Git hooks
I Git hooks sono script che Git esegue automaticamente quando si verificano determinati eventi — commit, merge, push e altro ancora. Consentono di collegare azioni personalizzate al ciclo di vita di Git: eseguire un linter prima di ogni commit, validare il formato del messaggio di commit, o bloccare un push se i test falliscono. Gli hooks sono il modo in cui i team applicano controlli di qualità localmente, prima che il codice difettoso lasci la macchina.
Questa pagina illustra dove risiedono gli hooks, la differenza tra hooks lato client e lato server, gli hooks più utili con esempi funzionanti, come un hook interrompe un'operazione, come bypassare un hook e come condividere gli hooks con un team.
Dove risiedono gli hooks
Ogni repository ha una directory .git/hooks contenente script di esempio con suffisso .sample. Per attivare un hook, aggiungere uno script eseguibile con il nome esatto dell'hook e senza estensione:
ls .git/hooks
# pre-commit.sample commit-msg.sample pre-push.sample ...Rimuovere il suffisso .sample (o creare il file da zero) e renderlo eseguibile:
chmod +x .git/hooks/pre-commitUn hook può essere scritto in qualsiasi linguaggio, purché il file sia eseguibile e inizi con una riga shebang appropriata (#!/bin/sh, #!/usr/bin/env python3, #!/usr/bin/env node, ecc.). Git si preoccupa solo che il file sia denominato esattamente come un hook noto, sia eseguibile e restituisca un codice di uscita.
Hooks lato client vs lato server
- Gli hooks lato client vengono eseguiti sulla propria macchina intorno alle operazioni locali come commit e push. Sono ideali per linting e test.
- Gli hooks lato server (come
pre-receiveepost-receive) vengono eseguiti sul repository remoto quando riceve un push — utili per applicare policy centralmente.
Gli hooks più comunemente usati sono lato client:
| Hook | Si attiva | Uso tipico |
|---|---|---|
pre-commit | Prima che venga creato un commit | Lint e test dei file in staging; interrompe in caso di fallimento. |
prepare-commit-msg | Prima che si apra l'editor del messaggio | Inserisce un template o un numero di ticket. |
commit-msg | Dopo che il messaggio è stato scritto | Impone una convenzione per i messaggi. |
post-commit | Dopo il completamento di un commit | Invia una notifica; nessun effetto sul commit. |
pre-push | Prima che venga inviato un push | Esegue l'intera suite di test come controllo finale. |
Come un hook interrompe un'operazione
L'intero meccanismo di controllo è il codice di uscita. Per un hook "pre-" (pre-commit, pre-push, commit-msg, …):
- Uscita
0→ l'hook ha approvato l'operazione e Git continua. - Uscita diversa da zero → Git annulla l'operazione. Il commit non viene creato, o il push non viene inviato.
Gli hook "post-" (post-commit, post-merge, …) vengono eseguiti dopo che l'azione è già completata, quindi il loro codice di uscita viene ignorato — non possono annullare nulla. Usarli per le notifiche, non per la validazione.
Un esempio di pre-commit
Questo hook pre-commit esegue il linter del progetto e blocca il commit se rileva problemi. Poiché sh interrompe l'esecuzione al comando fallito grazie a set -e, non è necessario alcun controllo aggiuntivo di $?:
#!/bin/sh
set -e
echo "Running lint..."
npm run lintSe npm run lint esce con un valore diverso da zero, set -e propaga quel codice e il commit viene interrotto. Se si preferisce un messaggio personalizzato, controllare il risultato esplicitamente:
#!/bin/sh
if ! npm run lint; then
echo "Lint failed — commit aborted. Fix the issues and try again."
exit 1
fiSalvarlo come .git/hooks/pre-commit ed eseguire chmod +x .git/hooks/pre-commit.
Un esempio di commit-msg
L'hook commit-msg riceve un argomento: il percorso di un file temporaneo contenente il messaggio proposto. Leggere quel file, validarlo e uscire con un valore diverso da zero per rifiutarlo. Questo esempio impone un prefisso in stile 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
fiBypassare un hook
Un hook è una rete di sicurezza, non un muro. Quando si ha genuinamente bisogno di saltare gli hook pre-commit e commit-msg per un'operazione, usare --no-verify:
git commit --no-verify -m "WIP: skip checks"
git push --no-verifyUsare questa opzione con parsimonia — bypassare il linter è il modo in cui il codice difettoso si insinua nella cronologia.
Condividere gli hooks con un team
Poiché .git/hooks non viene committato, gli hooks non vengono trasferiti con un clone. I team risolvono questo problema memorizzando gli hooks in una directory tracciata e puntando Git ad essa:
git config core.hooksPath .githooksOra Git cerca nella directory tracciata .githooks/ invece di .git/hooks. Committare gli script lì, renderli eseguibili, e ogni membro del team li riceverà dopo aver eseguito lo stesso comando git config (o dopo che uno script di configurazione lo fa per loro). Consultare Git config per ulteriori informazioni sulla memorizzazione delle impostazioni per repository.
Strumenti come Husky automatizzano esattamente questo per i progetti JavaScript, configurando hooks condivisi durante l'installazione. Per le policy che non si può permettere a nessuno di bypassare con --no-verify, applicarle con hooks lato server o con la protezione dei branch della propria piattaforma di hosting, poiché gli hooks lato client risiedono sempre sulla macchina dello sviluppatore.
Argomenti correlati
- git commit — il comando intorno a cui si avvolgono gli hooks pre-commit e commit-msg.
- Firma dei commit — verifica dell'autorship, spesso abbinata agli hooks.
- Git config — dove vengono memorizzati
core.hooksPathe altre impostazioni. - Git alias — scorciatoie per i comandi eseguiti dagli hooks.