Retour au cours

infra / git-github

Hooks Git

Leçon 141 exercice

Explication

Ce que vous allez apprendre

  • Comprendre le rôle des hooks pre-commit et commit-msg dans le cycle de vie d'un commit
  • Écrire un hook simple qui bloque un commit si le linter échoue
  • Réaliser pourquoi .git/hooks/ n'est jamais partagé automatiquement avec l'équipe
  • Rendre les hooks versionnés et partagés avec Husky et lint-staged
  • Utiliser --no-verify avec parcimonie, sans en faire une habitude

Dans quel contexte ?

Sur le dépôt boutique-api, un développeur committe régulièrement du code avec des erreurs ESLint, découvertes seulement plus tard en intégration continue, ce qui ralentit toute l'équipe. Un hook pre-commit, configuré avec Husky pour être partagé automatiquement par toute l'équipe dès npm install, bloque désormais ce type de commit directement sur la machine du développeur, avant même qu'il n'atteigne GitHub.

Le problème d'une règle qui repose sur la mémoire

D'abord, dire à toute une équipe "pensez bien à lancer le linter avant de committer" fonctionne rarement dans la durée : quelqu'un finit toujours par l'oublier, surtout sous pression.

La solution : automatiser plutôt que demander

Les hooks Git déplacent cette responsabilité de la mémoire humaine vers l'automatisation : ce sont des scripts que Git exécute lui-même, automatiquement, à des moments précis du cycle de vie d'un commit ou d'un push.

Un premier moment d'intervention

Le hook pre-commit s'exécute juste avant que le commit soit finalisé : c'est le bon endroit pour vérifier la qualité du code, comme un linter ou un formatage, et bloquer l'opération si quelque chose ne va pas.

Un deuxième moment, sur le message lui-même

Le hook commit-msg intervient un peu plus tard, sur le message du commit : il peut imposer un format précis, par exemple la convention "type(scope): description".

Ce que ce format apporte à l'équipe

Cela garantit un historique de messages cohérent dans tout le projet, ce qui facilite ensuite la génération automatique de changelogs.

HookMoment d'exécutionUsage typique
pre-commitJuste avant la finalisation du commitLinter, formatage automatique
commit-msgSur le message du commitImposer un format (type(scope): description)
pre-pushAvant l'envoi vers le dépôt distantLancer la suite de tests complète

Un piège que beaucoup découvrent trop tard

Les hooks classiques vivent dans un dossier local, .git/hooks/, qui n'est JAMAIS inclus dans les commits ni partagé via GitHub.

Ce que ça signifie pour l'équipe

Chaque personne qui clone le dépôt doit configurer ses propres hooks manuellement, ce qui rend la règle facilement contournable par oubli.

La solution moderne à ce piège

Des outils comme Husky résolvent précisément ce problème en rendant la configuration des hooks elle-même versionnée et installée automatiquement quand quelqu'un installe les dépendances du projet.

Un contournement à utiliser avec parcimonie

Enfin, l'option qui permet d'ignorer un hook existe pour de bonnes raisons, une urgence réelle, un cas exceptionnel, mais elle ne doit jamais devenir une habitude : un hook contourné systématiquement n'a plus aucune valeur protectrice.

Piège fréquent

Utiliser git commit --no-verify régulièrement pour "gagner du temps" revient à désactiver silencieusement toutes les protections mises en place par l'équipe (linter, format de message). Réserve cette option aux urgences documentées, jamais à un usage systématique.

La leçon suivante prend du recul sur les outils individuels vus jusqu'ici pour aborder une question d'équipe : comment s'organiser collectivement autour de Git, avec les workflows d'équipe.

Commandes & code

Hooks Git

Les hooks exécutent un script automatiquement à certains moments du cycle de vie Git (commit, push...).

bash
ls .git/hooks/
# pre-commit.sample, pre-push.sample, commit-msg.sample... (désactivés par défaut, suffixe .sample)
bash
#!/bin/sh
# .git/hooks/pre-commit (rendre exécutable : chmod +x .git/hooks/pre-commit)
# Bloque le commit si le linter échoue

npx eslint . --max-warnings=0
if [ $? -ne 0 ]; then
  echo "Commit annulé : erreurs ESLint détectées."
  exit 1
fi
bash
#!/bin/sh
# .git/hooks/commit-msg - valide le FORMAT du message de commit
# reçoit en $1 le chemin du fichier contenant le message

commit_msg=$(cat "$1")
pattern="^(feat|fix|chore|docs|refactor|test)(\(.+\))?: .{1,72}$"

if ! echo "$commit_msg" | grep -qE "$pattern"; then
  echo "Message de commit invalide."
  echo "Format attendu : type(scope): description  (ex: feat(auth): add password reset)"
  exit 1
fi
bash
# Problème : .git/hooks/ n'est PAS versionné, donc PAS partagé avec l'équipe par défaut
# Solution moderne : Husky (Node.js) installe des hooks versionnés dans le dépôt
npm install --save-dev husky
npx husky init
echo "npx lint-staged" > .husky/pre-commit
git add .husky/pre-commit
git commit -m "chore: add pre-commit hook via husky"
json
// package.json - lint-staged limite le linting aux fichiers STAGÉS, pour rester rapide
{
  "lint-staged": {
    "*.js": ["eslint --fix", "prettier --write"],
    "*.md": ["prettier --write"]
  }
}
bash
# Contourner un hook ponctuellement (à utiliser avec parcimonie, jamais en réflexe)
git commit --no-verify -m "hotfix: bypass lint for emergency deploy"

Résumé

  • Les hooks locaux (.git/hooks/) ne sont pas versionnés : Husky les rend partageables via npm.
  • pre-commit valide le code avant de committer, commit-msg valide le FORMAT du message.
  • lint-staged limite les vérifications aux fichiers réellement modifiés, pour rester rapide.
  • --no-verify contourne les hooks : à réserver aux cas exceptionnels documentés.

Exercices pratiques

1 disponible
1

Mission : rendre un garde-fou vraiment partagé par l'équipe

Objectif : Diagnostiquer pourquoi un hook local ne protège pas toute l'équipe, et le rendre versionné avec Husky.

Contexte

Un développeur configure un hook pre-commit manuellement dans .git/hooks/ sur sa machine pour bloquer les commits contenant des erreurs ESLint. Deux semaines plus tard, un collègue qui a cloné le même dépôt commite du code plein d'erreurs de lint sans jamais être bloqué.

Résoudre l’exercice →