Git - pre-commit-hooks
Vous détestez les pipelines CI/CD qui échouent ? Découvrez comment utiliser les hooks Git pre-commit, en particulier le framework pre-commit, pour appliquer vos standards de qualité de code en local avant de pusher.
Traduction automatique
Cet article fait partie de la série Qualité du code :
- Markdown linter - corriger les problèmes de formatage dans les fichiers md
- Rector 1.0.0 🎉🎉🎉, mon ami, mon coach
- Une image Docker qui fournit les outils d'analyse statique pour PHP
- Clean code - Linux Bash - Réduire au maximum le nombre de paramètres d'une fonction
- SQL - Outil de formatage
- JSON - Linter en ligne
- Formater du code PHP mal formaté
- Python - Les outils de qualité de code
- Git - pre-commit-hooks
- Validez votre schéma OpenAPI face aux standards REST Belgif
Journal des modifications

Évitez les pipelines CI/CD en échec en attrapant les erreurs avant de committer. Cet article explique comment utiliser les hooks Git pre-commit pour appliquer vos standards de qualité de code en local. Il propose un guide pratique pour installer et utiliser le framework pre-commit, un outil polyvalent capable de gérer des hooks multi-langages. Vous allez voir comment configurer des hooks pour un projet Python afin de détecter automatiquement des problèmes comme un formatage incohérent, et comment les installer et les lancer. L'article explique aussi où trouver d'autres hooks et quand il est légitime de les contourner.
Vous aimez le code propre, non ? Et vous détestez recevoir un email de votre outil de versioning (par exemple Github ou GitLab) qui vous annonce que votre dernier commit n'est pas passé, que le formatage de votre code est mauvais ; et tout ça parce que vous avez laissé un espace de trop en fin de ligne ou utilisé des simples quotes au lieu de doubles (ou l'inverse), par exemple.
Vous avez pushé vos modifications, vous êtes déjà passé à autre chose, peut-être un autre projet, et boum, deux heures après votre dernier commit (parce que le serveur CI traitait plein de pipelines avant le vôtre), boum, vous recevez un Your last commit has failed, #gnagnagna. Je hais ça autant que j'aime le code propre.
Alors, que faire pour éviter ça ?
La réponse est simple ! Avant chaque push, on devrait lancer les mêmes outils d'analyse de code que ceux exécutés dans la CI, c'est-à-dire phplint, php-cs-fixer, phpcbf, phan, phpstan, ... (pour PHP, tous regroupés dans l'jakzal/phpqa Docker image) ou pylint, mypy, prospector, black, ruff, ... (pour Python, voir Python - Les outils de qualité de code) ou shellcheck et shellformat (pour Bash sous Linux) ou ...
On devrait, mais y pense-t-on chaque fois ? Malheureusement non.
Voyons comment corriger ça.
Le résultat : un commit reformaté avant même d'exister
Voici pre-commit en train d'attraper (et de corriger) une violation de formatage, sur un projet de démo que vous allez construire plus bas :
$ pre-commit run --all-files
Vous avez vu la ligne reformatted main.py ? Une incohérence de quotes ("double" vs 'single') a été corrigée silencieusement par black, directement dans le stage local :

Pas de run CI, pas d'email deux heures plus tard — la violation ne quitte jamais votre machine.
Pourquoi ça fonctionne
Git propose deux étapes de hooks, pre- et post- ; un hook pre-commit s'exécute avant la création du commit et peut l'annuler complètement si quelque chose échoue.
Parmi les outils bâtis sur ce principe (husky, pre-commit, grumphp, CaptainHook), cet article utilise pre-commit :
- Il est multi-langages — PHP, Python, Bash, tout ce dont le projet a besoin — un seul fichier de configuration.
- Il lance les mêmes outils que votre CI (
black,pylint,phpstan, ...), juste plus tôt et gratuitement, sans runner à attendre. - Une fois installé, il revérifie automatiquement à chaque
git commit— rien à retenir, c'est imposé.
Installation
On va créer un nouveau dossier temporaire, lancer git init pour initialiser un projet, puis créer une image Docker et lancer un container pour notre démo.
D'abord, créez un dossier temporaire et placez-vous dedans, puis initialisez-le comme repository git (on va travailler hors ligne mais, oui, pour utiliser les hooks git pre-commit, il faut un projet git).
$ mkdir /tmp/hooks && cd $_ $ git init
Il nous faut trois fichiers : un Dockerfile pour créer notre image Docker Python, un compose.yaml pour définir quelques paramètres et main.py comme exemple de script Python.
# syntax=docker/dockerfile:1
FROM python:3.13-slim AS base
RUN apt-get -y update \ && apt-get install --yes --no-install-recommends git \ && rm -rf /var/lib/apt/lists/*
WORKDIR "/app/src"
# Keep the container runningENTRYPOINT ["tail", "-f", "/dev/null"]Ce Dockerfile crée une image Docker basée sur Python 3.13 allégée, installe git et définit le répertoire de travail à /app/src. Le conteneur reste actif indéfiniment en exécutant une commande tail qui ne fait rien, ce qui permet de l'utiliser comme environnement de développement interactif.
services: app_python: build: . volumes: - .:/app/srcCe fichier Docker Compose définit un service nommé app_python qui construit une image à partir du Dockerfile du répertoire courant, puis monte le code source local dans le conteneur pour permettre le développement en direct sans avoir à rebâtir l'image à chaque changement.
print("I'm your Python code")
print('Who you, who are you?')Ce script affiche deux messages textes simples à l'écran. Il démontre l'utilisation basique de la fonction print() avec deux styles de guillemets différents.
On va créer notre image Docker et un container avec cette seule commande : docker compose up --detach --build.
Et, maintenant, on entre dans le container en lançant : docker compose exec app_python /bin/sh.
Et on peut lancer notre script :
Installer pre-commit
Pour un projet Python, c'est vraiment facile, il suffit de lancer pip install pre-commit.
Ajouter un fichier de configuration
Simple aussi, créez un fichier appelé .pre-commit-config.yaml avec ce contenu :
repos:- repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.3.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace- repo: https://github.com/psf/black rev: 24.10.0 hooks: - id: blackCe fichier configure pre-commit, un outil qui exécute automatiquement des vérifications et des corrections sur le code avant chaque commit. Il définit trois dépôts de hooks : le premier fournit des vérifications basiques (YAML valide, fin de fichier, espaces inutiles), et le second applique le formateur de code Black pour assurer une cohérence de style Python.
Déclencher le hook manuellement
pre-commit peut être déclenché manuellement mais il faut avoir des fichiers dans le stage local git. Dans cet article, on a créé quelques fichiers, lancez git add . simplement pour les mettre dans le stage local git.
Maintenant, pour démarrer manuellement tous les contrôles définis dans le fichier yaml, lancez simplement pre-commit run --all-files — c'est exactement la commande et la sortie montrées en haut de cet article.
Vous avez remarqué ma coquille ?
print("I'm your Python code")
print('Who you, who are you?')
La première fois, j'ai utilisé des doubles quotes (dans le premier print) alors que j'ai utilisé des simples dans le second. J'ai donc (volontairement) créé une violation de code et l'outil black l'a vue et a reformaté main.py, exactement comme montré plus haut.
Autres démos
Installer les hooks
Ok, l'idée n'était pas de déclencher les hooks pre-commit à la main, non ? Lancez simplement pre-commit install et, à partir de maintenant, chaque fois que vous lancerez git commit, les contrôles pre-commit seront d'abord effectués et c'est seulement quand tous les contrôles réussissent (c'est-à-dire qu'ils renvoient tous un code de sortie 0) que votre commit sera autorisé.
Quelques hooks en plus
Il existe énormément de hooks et vous pouvez même créer les vôtres :
- Code spell, Check code for common misspellings
- Git leaks, Find secrets with Gitleaks
- Git lint, Linting for your git commit messages
- Markdown format, CommonMark compliant Markdown formatter
- Markdownlint, Markdown lint tool
- Ruff, A pre-commit hook for Ruff.
- Shell check, python3/pip3 wrapper for installing shellcheck
- Trufflehog, Find, verify, and analyze leaked credentials
Une configuration Python 3.13 prête à l'emploi
Pour un projet Python 3.13, voici mon fichier .pre-commit-config.yaml :
default_language_version: python: python3.13
repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: check-ast # Python linter - Simply check whether files parse as valid python. - id: check-docstring-first # No code before the initial docstring - id: check-json # JSON linter - Attempts to load all json files to verify syntax. - id: check-toml # TOML linter - Attempts to load all TOML files to verify syntax. - id: check-xml # XML linter - Attempts to load all XML files to verify syntax. - id: check-yaml # YAML linter - Attempts to load all yaml files to verify syntax. - id: check-merge-conflict # Check for files that contain merge conflict strings. - id: end-of-file-fixer # Makes sure files end in a newline and only a newline. - id: file-contents-sorter files: \.config/*.txt$ # Sort all lines in any .config/*.txt file (like cspell.txt f.i.) args: [--ignore-case] - id: no-commit-to-branch args: [--branch, main] # Don't allow to push directly on the main branch - id: requirements-txt-fixer # Sorts entries in requirements.txt - id: trailing-whitespace # Trims trailing whitespace. - repo: https://github.com/psf/black rev: 24.10.0 hooks: - id: black # Python black formatter - repo: local hooks: - id: isort # isort (should be installed using `pip install isort` or mentioned in the `requirements.txt` file) name: isort entry: isort language: system types: [python] require_serial: true args: ["--line-length 120"] - id: mypy # Mypy (should be installed using `pip install mypy` or mentioned in the `requirements.txt` file) name: mypy entry: mypy language: system types: [python] require_serial: true args: ["--config-file=.config/mypy.ini"] - id: pylint # Pylint (should be installed using `pip install pylint` or mentioned in the `requirements.txt` file) name: pylint entry: pylint language: system types: [python] require_serial: true args: ["--rcfile=.config/.pylintrc"]Ce fichier configure pre-commit, un outil qui exécute automatiquement des vérifications avant chaque commit. Il définit la version Python à utiliser et liste plusieurs vérificateurs : des outils standards pour valider JSON, YAML, XML et syntaxe Python, ainsi que des outils personnalisés comme black pour formater le code, isort pour organiser les imports, mypy pour la vérification de types, et pylint pour l'analyse de qualité du code.
Sous le capot (passez ce point si vous voulez juste l'utiliser)
Remarque
Si la mécanique vous intéresse, affichez simplement le fichier .git/hooks/pre-commit. L'instruction pre-commit install a configuré git pour exécuter un petit script Bash appelé .git/hooks/pre-commit.
Chercher des hooks et écrire les vôtres
Prenez le temps de parcourir https://github.com/pre-commit/pre-commit-hooks pour en découvrir quelques-uns, ou cherchez sur https://sourcegraph.com/search avec des queries comme context:global file:^\.pre-commit-hooks\.yaml$ "types: [python]" par exemple (lien direct).
Comme illustré sur https://pre-commit.com/#repository-local-hooks, vous pouvez ajouter des hooks locaux.
Imaginez : vous avez déjà installé un outil comme prospector (pour Python) ou phpstan (pour PHP). Ces outils sont installés sur votre machine (vous pouvez donc les appeler en ligne de commande). Il suffit alors d'ajouter un nouveau hook comme ceci :
- repo: local hooks: - id: prospector name: prospector entry: prospector language: systemCe fichier configure un hook pre-commit local qui exécute prospector, un outil d'analyse statique Python. Le hook est défini pour s'exécuter en tant que commande système lors des commits.
Vous pouvez aussi passer des arguments :
- repo: local hooks: - id: mypy # Mypy (should be installed using `pip install mypy`) name: mypy entry: mypy language: system types: [python] require_serial: true args: ["--config-file=.config/mypy.ini"]Ce fichier configure un hook pre-commit local qui exécute mypy, un vérificateur de types Python, sur les fichiers Python avant chaque commit. Le hook est configuré pour utiliser un fichier de configuration personnalisé situé à .config/mypy.ini et s'exécute de manière sérielle.
Astuce : --no-verify
Dans certaines situations, vous devez pusher vos modifications même s'il reste des violations de code. Disons que c'est votre dernière heure avant trois semaines de vacances et que vous travaillez seul sur une branch comme feat-user-profile. Vous voulez pusher vos modifications et profiter de la pause.
Dans ce cas, vous pouvez ajouter le flag --no-verify, par exemple git commit -m "wip: not yet finished" --no-verify. Et là, les hooks pre-commit ne seront pas exécutés, donc vos modifications seront committées.
Le flag --no-verify
N'utilisez ce flag que si vous savez exactement ce que vous faites. Ce serait une très mauvaise idée de faire ça, par exemple, sur la branch dev si vous travaillez en équipe.
Conclusion
Les emails de CI en échec de l'introduction disparaissent dès que les mêmes contrôles tournent en local, avant que le commit n'existe — pre-commit transforme « attendre que le serveur vous le dise » en « le savoir immédiatement ». Installez-le une fois (pre-commit install) et tous vos futurs commits sont couverts, dans tous les langages que votre projet mélange.
Gardez --no-verify sous le coude pour la rare exception assumée, et consultez Python - Les outils de qualité de code pour les outils sur lesquels reposent les hooks de la démo de cet article.
Cet article fait partie de la série Qualité du code :
- Markdown linter - corriger les problèmes de formatage dans les fichiers md
- Rector 1.0.0 🎉🎉🎉, mon ami, mon coach
- Une image Docker qui fournit les outils d'analyse statique pour PHP
- Clean code - Linux Bash - Réduire au maximum le nombre de paramètres d'une fonction
- SQL - Outil de formatage
- JSON - Linter en ligne
- Formater du code PHP mal formaté
- Python - Les outils de qualité de code
- Git - pre-commit-hooks
- Validez votre schéma OpenAPI face aux standards REST Belgif




