Installer pre-commit sur un repo non-Python
Comment j'ai branché pre-commit sur ce site (HTML/CSS/JS pur), pourquoi j'ai laissé tomber les hooks Python « standards », et comment l'intégrer à une CI GitHub Actions existante sans casser le pattern fail-loud/fail-soft.
À quoi ça sert
La fiche théorique pre-commit décrit l'outil avec sa config Python type (Ruff, Black, isort). Mais ce repo n'a aucune ligne de Python — c'est du HTML, du CSS et du JavaScript vanilla. Du coup, plusieurs questions concrètes se posent au moment de l'install :
- Quels hooks garder, lesquels enlever ?
- Faut-il rajouter un hook lychee local alors que la CI en a déjà deux ?
- Comment intégrer pre-commit au workflow GitHub Actions existant sans casser le pattern fail-loud/fail-soft qu'on a mis en place ?
- Que faire si le tout premier run de pre-commit casse plein de fichiers d'un coup ?
Cette fiche raconte les décisions prises à chaque étape, plutôt que de redonner la doc générique.
La fiche pre-commit explique l'outil dans l'absolu : la notion de hook Git, la config YAML, les hooks courants. Cette fiche-ci raconte un cas concret : comment l'installer sur un repo qui n'a pas le profil « Python type » attendu par la plupart des tutos.
Un exemple d'usage
Au tout premier pre-commit run --all-files, sur un repo qui
avait déjà du contenu et une CI verte depuis des semaines :
trim trailing whitespace.................................................Passed
fix end of files.........................................................Failed
- hook id: end-of-file-fixer
- exit code: 1
- files were modified by this hook
Fixing .lycheeignore
check yaml...............................................................Passed
check for merge conflicts................................................Passed
check for added large files..............................................Passed
detect private key.......................................................Passed
Un fichier corrigé tout seul (.lycheeignore n'avait pas de
newline finale). Aucune autre erreur. La CI était verte, mais pas le
repo : pre-commit attrape ce que les yeux humains laissent
passer. C'est exactement à ça qu'il sert.
How-to : ce que j'ai fait, dans l'ordre
-
Choisir les hooks pour ce repo
La plupart des tutos pre-commit commencent par
ruffoublack. Inutile ici : pas de Python. La règle que j'ai suivie : choisir les hooks selon le langage du repo, pas selon la liste standard.Au final, six hooks « hygiène générale » suffisent :
trailing-whitespace+end-of-file-fixer— propreté des fichiers texte.check-yaml— valide les workflows GitHub Actions et le YAML de pre-commit lui-même.check-merge-conflict— détecte les marqueurs oubliés.check-added-large-files(max 500 ko) — bloque les gros binaires committés par erreur (capture d'écran lourde, node_modules…).detect-private-key— par hygiène, même si peu probable sur ce repo.
-
Décider : pas de hook lychee local
Tentation : ajouter un hook qui lance lychee avant chaque commit. Refusé. Raisons :
- La CI a déjà deux jobs lychee (interne strict + externe informatif). Doublonner localement ralentit chaque commit pour zéro gain.
- Le check externe a besoin du réseau, ce qui casse le principe « pre-commit doit être rapide ». 2-3 secondes max.
- Le check interne offline serait redondant avec ce que GitHub Actions fait en ~2 s, sans valeur ajoutée locale.
Règle dégagée : ne pas dupliquer en pre-commit ce que la CI couvre déjà bien. pre-commit = filet rapide, CI = vérité définitive.
-
Installer l'outil avec uv tool
Pre-commit étant un outil global (comme Ruff ou UV lui-même), je l'ai installé avec
uv toolplutôt que comme dépendance du repo :bashuv tool install pre-commitAvantage : disponible dans tous les repos, sans polluer le
pyproject.tomldu projet (qui n'existe d'ailleurs pas ici). -
Créer le YAML à la racine
Piège : pre-commit cherche son fichier de config exactement à
.pre-commit-config.yamlà la racine du repo. On ne peut pas le mettre dans.github/ou ailleurs. À ne pas confondre avec le workflow CI lui-même, qui lui vit dans.github/workflows/ci.yml.bashdevianotes/ ├── .pre-commit-config.yaml ← config pre-commit (racine) ├── .github/workflows/ │ └── ci.yml ← workflow GitHub Actions └── ... -
Activer le hook Git local
bashpre-commit installÇa crée
.git/hooks/pre-commit. À refaire à chaque clone du repo (le dossier.git/n'est jamais versionné par Git lui-même). À mentionner dans le README pour les futurs collaborateurs. -
Premier run sur tout l'existant
bashpre-commit run --all-filesSur ce repo, un seul fichier corrigé (
.lycheeignoresans newline finale). Sur un repo plus ancien ou plus gros, attends-toi à un diff plus large — ce n'est pas grave : tu re-stages et tu commits d'un coup le « grand nettoyage ».Le piège : si trop de fichiers changentSur un gros repo qui n'a jamais eu pre-commit, le premier run peut modifier des centaines de fichiers (whitespace, newlines). Mieux vaut faire un commit dédié « chore: pre-commit run --all-files » après avoir installé pre-commit mais avant de mélanger avec d'autres changements. Le diff reste lisible.
-
Ajouter le job pre-commit en CI
pre-commit local n'est pas suffisant : il faut un filet de sécurité côté CI, sinon un
git commit --no-verifyou un contributeur qui n'a paspre-commit installé casse l'historique. J'ai ajouté un nouveau job dansci.yml:yamlpre-commit: name: pre-commit (strict) runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v5 with: python-version: "3.12" - uses: pre-commit/action@v3.0.1L'action officielle
pre-commit/actioninstalle pre-commit, met en cache les environnements des hooks, et lancepre-commit run --all-files. Trois lignes, c'est tout. -
Brancher pre-commit dans
deploy.needsLe point crucial. Un job CI qui ne bloque pas le déploiement n'est qu'une décoration. Donc :
yaml (avant)deploy: needs: link-check-internalyaml (après)deploy: needs: [pre-commit, link-check-internal]Côté pattern, ça respecte le fail-loud/fail-soft du projet : pre-commit rejoint le check de liens internes (strict) comme bloquant, pendant que le check de liens externes reste informatif. Cohérent : c'est de l'hygiène qu'on contrôle, donc bloquant.
-
Vérifier le tout
bash# Re-run en local pour confirmer que rien ne reste à fixer pre-commit run --all-files # Tenter un commit normal : les hooks doivent passer silencieusement git add . git commit -m "chore: setup pre-commit"Si le commit passe sans rejet, le setup est complet. Au prochain push, le job pre-commit s'exécutera en CI en parallèle des deux checks lychee, et bloquera le déploiement en cas de problème.
Ce que j'ai appris
- Les hooks pre-commit se choisissent selon le langage du repo, pas selon la « liste standard » qu'on voit partout. Pour un repo non-Python, six hooks d'hygiène générale suffisent largement.
- Pre-commit et CI sont complémentaires, pas concurrents. Pre-commit = rapide et local, pour éviter les allers-retours. CI = définitive et bloquante, pour ce qui sort du repo. On ne duplique pas ce que la CI fait déjà bien (ex : lychee chez moi).
-
Un job pre-commit en CI qui ne bloque pas le deploy ne sert
à rien. Le mettre dans
needs:dedeployest ce qui transforme l'outil en vrai filet de sécurité. -
end-of-file-fixera attrapé un truc dès le premier run. Preuve immédiate que ça apporte de la valeur même sur un repo « propre » à l'œil. -
pre-commit installest par-machine, pas versionné. Ça doit aller dans le README pour les futurs cloneurs du repo, sinon ils contournent le filet local sans le savoir.
Pour aller plus loin
- Fiche théorique : pre-commit (théorie)
- Site officiel : pre-commit.com
- Liste des hooks « génériques » : github.com/pre-commit/pre-commit-hooks
- Action GitHub officielle : github.com/pre-commit/action
- Retex voisin : CI/CD du site (pattern fail-loud / fail-soft)