modulo 07 / lezione 35

Casi reali, debugging della storia, recupero

Hook Git: pre-commit, pre-push, commit-msg

Automatizzare controlli locali (lint, test, formato commit) senza affidarsi alla disciplina pura.

60 minGit e controllo versione

Hook Git: pre-commit, pre-push, commit-msg

Gli hook Git sono script eseguiti automaticamente in momenti specifici: prima di un commit, durante la creazione del messaggio, prima di un push, dopo un merge. Servono a spostare controlli ripetitivi dal ricordo umano al flusso di lavoro.

Un hook buono è veloce, prevedibile e aiuta lo sviluppatore. Un hook cattivo è lento, fragile e porta tutti a usare --no-verify.

Perché questa lezione conta

Lint dimenticato, test non lanciati, messaggi commit incoerenti e file generati fuori posto sono problemi banali ma frequenti. Gli hook riducono il rumore prima che arrivi in PR.

Non sostituiscono la CI. La CI resta la fonte autorevole perché gira in ambiente controllato. Gli hook sono il primo filtro locale.

Obiettivo operativo

Alla fine della lezione devi saper distinguere hook nativi e versionati, configurare pre-commit, commit-msg e pre-push, decidere cosa eseguire localmente e integrare gli hook con la policy del team.

Hook nativi

Git cerca script in:

.git/hooks/

Esempio:

.git/hooks/pre-commit

Il problema è che .git/hooks non viene versionata nel repository. Ogni clone ha i suoi hook locali. Per un team non basta dire “metti questo file nella tua cartella hooks”.

Puoi usare:

git config core.hooksPath .githooks

Così Git legge hook da una cartella versionata. In alternativa usa strumenti come Husky, lefthook o pre-commit.

Pre-commit

Esegue controlli prima che Git crei il commit:

#!/bin/sh
npm run lint --silent || exit 1
npm run format:check --silent || exit 1

Deve essere rapido. Se impiega minuti, verrà aggirato.

Buoni candidati:

  1. lint veloce;
  2. format check;
  3. typecheck parziale se rapido;
  4. blocco di file vietati;
  5. controllo segreti accidentali.

Da evitare:

  1. test end-to-end lenti;
  2. build completa pesante;
  3. chiamate a servizi esterni;
  4. controlli non deterministici.

Commit-msg

commit-msg valida il messaggio:

#!/bin/sh
MSG_FILE="$1"
grep -Eq '^(feat|fix|docs|test|refactor|build|ci|chore)(\\(.+\\))?: .+' "$MSG_FILE" || {
  echo "Messaggio non valido. Usa type(scope): descrizione"
  exit 1
}

Per team che usano Conventional Commits, strumenti come commitlint sono più robusti:

npm install -D @commitlint/cli @commitlint/config-conventional

Pre-push

pre-push è l’ultimo filtro locale:

#!/bin/sh
npm test -- --runInBand || exit 1

Qui puoi permetterti controlli un po’ più costosi, ma resta pragmatico. Se il push richiede sempre dieci minuti, gli sviluppatori cercheranno scorciatoie.

Una buona regola:

pre-commit: controlli sotto 10-20 secondi
pre-push: test mirati sotto pochi minuti
CI: suite completa e build ufficiale

Strumenti condivisi

Husky è comune nei progetti Node:

npx husky init

lefthook è veloce e multi-linguaggio:

brew install lefthook
lefthook install

pre-commit è molto usato in ecosistemi Python e polyglot:

pipx install pre-commit
pre-commit install

Scegli in base allo stack già presente. Non introdurre tooling pesante solo per un controllo che la CI copre bene.

--no-verify

Git permette:

git commit --no-verify
git push --no-verify

Non è un crimine usarlo in emergenza, ma deve essere raro e motivato. Se il team lo usa spesso, il problema è l’hook: troppo lento, fragile o scollegato dalla realtà.

Laboratorio guidato

Su un repo demo:

  1. crea cartella .githooks;
  2. configura core.hooksPath;
  3. aggiungi un pre-commit che esegue lint o uno script fittizio;
  4. aggiungi un commit-msg per Conventional Commits;
  5. prova un commit valido e uno non valido;
  6. documenta come installare gli hook dopo clone;
  7. verifica che la CI esegua comunque i controlli importanti.

Output atteso

Una policy hook:

Tool scelto:
Hook attivi:
Tempo massimo atteso:
Controlli locali:
Controlli solo CI:
Uso accettabile di --no-verify:
Installazione dopo clone:

Errori comuni

Affidarsi solo agli hook. Un contributor può non averli installati.

Hook troppo lenti. Verranno saltati.

Duplicare male la CI. Locale e CI devono completarsi, non contraddirsi.

Non versionare la configurazione. Ogni macchina si comporta diversamente.

Checklist di verifica

  • so dove vivono gli hook nativi;
  • so rendere gli hook condivisi nel team;
  • so configurare pre-commit, commit-msg e pre-push;
  • so decidere cosa resta locale e cosa va in CI;
  • so riconoscere hook troppo costosi.

Esercizi

Base · facile

Crea un hook pre-commit che esegua un comando prima di ogni commit.

Soluzione
# .git/hooks/pre-commit  (reso eseguibile: chmod +x)
#!/bin/sh
npm run lint || exit 1   # se il lint fallisce, il commit viene bloccato

Un hook è uno script che Git esegue automaticamente in un certo momento. pre-commit gira prima che il commit venga creato: se esce con codice diverso da zero, il commit è annullato.

Intermedio · medio

Scrivi un hook commit-msg che imponga un formato minimo al messaggio. Spiega la differenza tra pre-commit, commit-msg e pre-push.

Soluzione
# .git/hooks/commit-msg
#!/bin/sh
grep -qE '^(feat|fix|docs|chore): ' "$1" || {
  echo "Messaggio non valido: usa 'tipo: descrizione'"; exit 1; }
  • pre-commit: prima di creare il commit → controlli su codice/stage (lint, test veloci).
  • commit-msg: riceve il file col messaggio → valida/normalizza il testo del commit.
  • pre-push: prima di inviare al remoto → controlli più pesanti (test completi).

Ognuno scatta in un momento diverso del ciclo, dal più locale (commit) al più condiviso (push).

Sfida · difficile

Spiega perché gli hook in .git/hooks non si condividono col team e come strumenti dedicati risolvono il problema.

Soluzione
# .git/hooks NON viene versionato → ogni clone parte senza hook.
# Soluzione: hook versionati nel repo + configurazione del path, es.
git config core.hooksPath .githooks   # cartella versionata
# oppure strumenti come Husky / pre-commit (framework) che li installano

La cartella .git/hooks è locale e fuori dal controllo di versione: gli hook che crei lì non arrivano ai colleghi. Per renderli condivisi si versiona una cartella di hook nel repo e si punta core.hooksPath lì, oppure si usa un framework (Husky per JS, pre-commit per Python) che, dopo un comando di setup, installa gli stessi hook per tutti. Così le regole (lint, formato dei messaggi, test) valgono per l’intero team, non solo per chi le ha configurate a mano.

Collegamenti

  • Modulo: Casi reali, debugging della storia, recupero - lezione 35 del corso.
  • Si collega a Conventional Commits, CI e qualità prima della PR.