erplibre/script/git/hooks/pre-commit
Mathieu Benoit 7cdcd8a07a [ADD] garde-fou : signaler au commit un source trop récent pour le dépôt
Une syntaxe plus récente que le Python du dépôt ne casse qu'au chargement, et
aucun outil en place ne la voyait : la cible de black borne ce qu'il ÉCRIT,
jamais ce qu'il accepte, et flake8 analyse avec l'interpréteur courant. Le
contrôle compile les fichiers indexés avec un vrai interpréteur de la version
déclarée, trouvé sans réseau par mise, pyenv ou le PATH, et se prend lui-même
quand il y tourne déjà. Sans interpréteur, il DIT qu'il n'a pas vérifié plutôt
que de laisser croire le contraire. Le hook relaie, sans bloquer le commit, et
écarte les dépôts rapatriés dont ce dépôt ne répond pas.
Vérifié : les f-strings PEP 701 de la révision précédente sont signalées, rien
ne l'est sur l'arbre courant, et l'absence du fichier de version se dit.

--- EN ---

A syntax newer than the repository's Python only breaks at load, and no tool in
place saw it: black's target bounds what it WRITES, never what it accepts, and
flake8 parses with the running interpreter. The check compiles staged files
with a real interpreter of the declared version, found without network through
mise, pyenv or the PATH, and takes itself when it already runs under it.
Without one, it SAYS it did not check rather than implying it did. The hook
relays, without blocking the commit, and skips the checked-out repositories
this one does not answer for.
Checked: the PEP 701 f-strings of the previous revision are reported, nothing
is on the current tree, and a missing version file is announced.

Assisted-by: Claude Opus 5
2026-09-24 13:56:57 -04:00

117 lines
3.6 KiB
Python
Executable file

#!/usr/bin/env python3
# © 2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
"""Hook pre-commit : montre les commentaires hors convention des fichiers indexés.
Il INFORME et ne refuse jamais — le nettoyage se fait au fur et à mesure, sur
les fichiers qu'on touche déjà, et non en une passe qui réécrirait le dépôt.
Un hook qui bloquerait sur du style se ferait désinstaller la même semaine.
Il relaie aussi check_python_version.py, sans bloquer davantage : le source
indexé qui ne parse pas sous le Python de conf/python-erplibre-version.
Installation :
git config core.hooksPath script/git/hooks
Il sort toujours en 0. Pour l'interroger à la main, sur un fichier ou un
répertoire :
python3 script/analyse/check_comment_hygiene.py script/todo/todo.py
python3 script/analyse/check_python_version.py script/todo/todo.py
"""
import subprocess
import sys
from pathlib import Path
RACINE = Path(__file__).resolve().parents[3]
OUTIL = RACINE / "script" / "analyse" / "check_comment_hygiene.py"
VERSION = RACINE / "script" / "analyse" / "check_python_version.py"
# Au-delà, le rapport cesse d'être une invitation et devient un mur.
PLAFOND = 12
def commentaires() -> int:
if not OUTIL.is_file():
return 0
try:
sortie = subprocess.run(
[sys.executable, str(OUTIL), "--staged", "--no-color"],
capture_output=True,
text=True,
cwd=str(RACINE),
timeout=30,
)
except (OSError, subprocess.SubprocessError):
return 0
if sortie.returncode not in (0, 1):
# Jamais bloquant, mais jamais muet non plus : un outil cassé qui se
# tait se confond avec un dépôt propre.
print(
f"\n ⓘ check_comment_hygiene a échoué ({sortie.returncode}) ;"
" les commentaires n'ont pas été relus\n",
file=sys.stderr,
)
return 0
if sortie.returncode != 1 or not sortie.stdout.strip():
return 0
lignes = sortie.stdout.rstrip("\n").split("\n")
trop = len(lignes) - PLAFOND
if trop > 0:
lignes = lignes[:PLAFOND] + [f" … et {trop} lignes de plus"]
print(
"\n ⓘ commentaires à relire dans ce que vous committez\n",
file=sys.stderr,
)
for ligne in lignes:
print(f" {ligne}", file=sys.stderr)
print(
"\n 🔴 identifiant : à retirer. 🟡 récit : à relire — un fait durable"
"\n reste, l'incident où on l'a vu part."
"\n La règle : .claude/rules/04-code-conventions.md"
"\n Le commit n'est PAS bloqué. Corrigez ce que vous touchez.\n",
file=sys.stderr,
)
return 0
def syntaxe() -> None:
"""Relaie les refus de syntaxe, ou l'avis d'un contrôle absent."""
if not VERSION.is_file():
return
try:
sortie = subprocess.run(
[sys.executable, str(VERSION), "--staged"],
capture_output=True,
text=True,
cwd=str(RACINE),
timeout=30,
)
except (OSError, subprocess.SubprocessError):
return
lignes = (sortie.stdout + sortie.stderr).strip().split("\n")
if not lignes[0]:
return
trop = len(lignes) - PLAFOND
if trop > 0:
lignes = lignes[:PLAFOND] + [f" … et {trop} lignes de plus"]
print("\n ⓘ syntaxe Python du dépôt\n", file=sys.stderr)
for ligne in lignes:
print(f" {ligne}", file=sys.stderr)
print("\n Le commit n'est PAS bloqué.\n", file=sys.stderr)
def main() -> int:
commentaires()
syntaxe()
return 0
if __name__ == "__main__":
sys.exit(main())