Aller au contenu

Hooks pre-commit

Automatiser la validation du code avant chaque commit — linting, formatage et vérification.


Pourquoi des hooks

Un hook pre-commit est un script qui s'exécuté automatiquement avant chaque git commit. Si le script échoué (exit code != 0), le commit est bloque.

L'objectif : empêcher le code non conforme d'entrer dans l'historique Git, sans dépendre de la discipline individuelle.

graph LR
    A["git commit"] -->|"declenche"| B["pre-commit hook"]
    B -->|"succes"| C["commit cree"]
    B -->|"echec"| D["commit bloque"]
    D -->|"corriger"| A

Le problème sans hooks

  • Un développeur oublie de lancer le formatter → le diff de la MR est pollue de changements cosmétiques
  • Un développeur commit un console.log ou un import pdb → détecté en review ou pire, en production
  • Le linter passe en CI mais le développeur ne le voit que 10 minutes plus tard → boucle de feedback lente

Le principe : fail fast, local, reproductible

Principe Explication
Fail fast Détecter le problème au commit, pas au push ni en CI
Local Le hook tourne sur la machine du dev, pas sur un serveur
Reproductible Même version d'outil pour toute l'équipe, définie dans le repo

Le mécanisme Git natif

Git fournit des hooks dans .git/hooks/. Par défaut, ce sont des scripts shell avec le suffixe .sample.

# Voir les hooks disponibles
ls .git/hooks/

# Creer un hook pre-commit basique
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
echo "Running pre-commit checks..."
# Verifier qu'il n'y a pas de console.log
if git diff --cached --name-only | xargs grep -l 'console.log' 2>/dev/null; then
    echo "ERROR: console.log detecte dans les fichiers stages"
    exit 1
fi
EOF
chmod +x .git/hooks/pre-commit

Limite du mécanisme natif

Les hooks .git/hooks/ ne sont pas versionnes — ils vivent dans .git/ qui est local. Chaque développeur doit les installer manuellement. C'est la que les frameworks de hooks interviennent.


Framework pre-commit (Python)

Le framework pre-commit est le plus utilisé. Il se configuré via un fichier .pre-commit-config.yaml à la racine du projet.

Installation

pip install pre-commit
pre-commit install

Configuration type

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
        args: ['--maxkb=500']

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.5.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v4.0.0-alpha.8
    hooks:
      - id: prettier
        types_or: [javascript, typescript, css, json, yaml, markdown]

Fonctionnement

graph TD
    A["git commit"] --> B["pre-commit intercept"]
    B --> C["Telecharge les hooks<br/>(premiere fois)"]
    C --> D["Execute chaque hook<br/>sur les fichiers stages"]
    D -->|"tous OK"| E["commit cree"]
    D -->|"echec ou modif"| F["commit bloque"]
    F --> G["Formatter a modifie des fichiers ?"]
    G -->|"oui"| H["git add + git commit a nouveau"]
    G -->|"non"| I["corriger manuellement"]

Bypass en urgence

En cas d'urgence absolue, vous pouvez contourner les hooks avec git commit --no-verify. A n'utiliser que dans des situations exceptionnelles — la CI rattrapera les problèmes au push.


Husky (Node.js)

Husky est l'équivalent pour l'écosystème Node.js. Il utilise le mécanisme prepare de npm pour installer les hooks automatiquement.

Installation

npm install --save-dev husky
npx husky init

Configuration

# .husky/pre-commit
npx lint-staged
// package.json
{
  "lint-staged": {
    "*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{css,json,md}": ["prettier --write"]
  }
}

lint-staged

lint-staged ne lance les outils que sur les fichiers stages (git add), pas sur tout le projet. C'est essentiel pour la performance — un pre-commit qui prend 30 secondes ne sera pas respecte.


Lefthook (Go)

Lefthook est une alternative multi-langage, rapide et sans dépendance runtime.

Installation

# Via npm
npm install --save-dev lefthook

# Via brew
brew install lefthook

# Init
lefthook install

Configuration

# lefthook.yml
pre-commit:
  parallel: true
  commands:
    lint:
      glob: "*.{js,ts}"
      run: npx eslint --fix {staged_files}
    format:
      glob: "*.py"
      run: ruff format {staged_files}
    check-yaml:
      glob: "*.{yml,yaml}"
      run: yamllint {staged_files}

Avantage : parallélisme natif

Lefthook exécuté les hooks en parallèle par défaut, là où pre-commit et husky sont séquentiels. Sur un projet avec formatter + linter + validation YAML, le gain est significatif.


Comparatif des frameworks

Critère pre-commit (Python) Husky (Node.js) Lefthook (Go)
Langage Python Node.js Go (binaire)
Config YAML Shell + JSON YAML
Parallélisme Non Non Oui
Écosystème hooks 800+ hooks publics Via lint-staged Manuel
Poids Léger Léger Très léger
Multi-langage Oui Principalement JS Oui

Quel framework choisir

  • Projet Python → pre-commit (écosystème riche, standard de facto)
  • Projet Node.js → Husky + lint-staged (intégration npm native)
  • Projet multi-langage ou polyglotte → Lefthook (rapide, pas de runtime)

Quoi vérifier dans un pre-commit

Vérification Temps Impact
Formatage (Prettier, Black) < 1s Élimine les diffs cosmétiques
Linting (ESLint, Ruff) 1-3s Détecté bugs et mauvaises pratiques
Trailing whitespace < 1s Proprete du code
Fichiers volumineux < 1s Evite de committer des binaires
Secrets (detect-secrets) 1-2s Empêche la fuite de credentials
YAML/JSON valide < 1s Evite les erreurs de syntaxe config

Ne pas en faire trop

Un pre-commit qui prend plus de 10 secondes sera contourne par les développeurs. Gardez les verifications lourdes (tests, SAST, build) pour le pipeline CI.


Outils

Outil Description Lien
pre-commit Framework de hooks Python avec 800+ hooks publics pre-commit.com
Husky Hooks Git pour l'écosystème Node.js typicode.github.io/husky
Lefthook Framework de hooks rapide et multi-langage github.com/evilmartians/lefthook
lint-staged Exécuté les linters uniquement sur les fichiers stages github.com/lint-staged/lint-staged
detect-secrets Détection de secrets dans le code avant commit github.com/Yelp/detect-secrets