#!/usr/bin/env python3 """Publier le genome : eregion ET la forge du site, en un seul geste. POURQUOI (2026-09-28). La forge du SITE fait autorite (D-81) : un ecosysteme s'y reproduit. Mais les commits naissent sur le poste, et n'y arrivent que par `make genome-pousser` — un geste A PART, que rien ne force ni ne signale. Un `git push` vers eregion ne la met pas a jour. Le 2026-08-26, elle avait quatre commits de retard, dont le correctif du pare-feu Proxmox : un ecosysteme reconstruit ce jour-la aurait reproduit le defaut qu'on croyait repare. Le 2026-09-28 encore, `make genome-etat` la trouvait en retard d'un commit. Le retard vient d'un geste qu'on oublie : on fait du geste un seul geste. CE QUE CA FAIT, pour chaque depot que le runner du site porte (`serveur_ops_depots`, la MEME liste que `genome-pousser` — lue, pas recopiee) : 1. recuperer eregion, et REFUSER si eregion porte des commits que le poste n'a pas (une demande de fusion acceptee, par exemple) : publier sans les integrer les ecraserait sur la forge du site ; 2. signaler ce qui n'est pas committe — cela ne part pas ; 3. pousser sur eregion ; puis `make genome-pousser`, puis `make genome-etat` : la forge du site doit porter la meme tete que le poste, sinon on le dit. make publier tous les depots du genome make publier DEPOT=ops-chezlepro """ from __future__ import annotations import argparse import json import os import subprocess import sys from pathlib import Path RACINE = Path(__file__).resolve().parent.parent DEPOTS = RACINE.parent def git(depot: Path, *args: str) -> subprocess.CompletedProcess: return subprocess.run(["git", "-C", str(depot), *args], capture_output=True, text=True) def genome() -> list[dict]: r = subprocess.run(["ansible-inventory", "-i", "scripts/site_inventaire.py", "--host", "site-ops-01"], capture_output=True, text=True, cwd=RACINE, env=os.environ) try: return json.loads(r.stdout).get("serveur_ops_depots") or [] except ValueError: raise SystemExit("Inventaire du site illisible : la liste du genome est inconnue.\n" + r.stderr[:300]) def main() -> int: ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) ap.add_argument("--depot", default="", help="un seul depot (nom sur la forge)") args = ap.parse_args() liste = [d for d in genome() if not args.depot or d["depot"] == args.depot] if not liste: raise SystemExit(f"Aucun depot du genome ne s'appelle « {args.depot} ».") refus = [] for d in liste: depot = DEPOTS / d["dest"] if not (depot / ".git").is_dir(): print(f" - {d['depot']:<17} pas de copie sur ce poste — rien a publier") continue branche = git(depot, "branch", "--show-current").stdout.strip() if git(depot, "remote", "get-url", "eregion").returncode != 0: print(f" - {d['depot']:<17} aucun remote `eregion`") continue git(depot, "fetch", "-q", "eregion") distant = f"eregion/{branche}" if git(depot, "rev-parse", "--verify", "-q", distant).returncode == 0: manquants = git(depot, "rev-list", "--count", f"HEAD..{distant}").stdout.strip() if manquants not in ("", "0"): refus.append(d["depot"]) print(f" !! {d['depot']:<17} eregion porte {manquants} commit(s) absent(s) du poste " f"— les integrer d'abord (`git pull --rebase eregion {branche}`)") continue sale = [l for l in git(depot, "status", "--porcelain").stdout.splitlines() if l.strip()] if sale: print(f" ~ {d['depot']:<17} {len(sale)} changement(s) NON committe(s) — ils ne partent pas") avance = git(depot, "rev-list", "--count", f"{distant}..HEAD").stdout.strip() or "?" r = git(depot, "push", "-q", "eregion", branche) if r.returncode != 0: refus.append(d["depot"]) print(f" !! {d['depot']:<17} push refuse : {r.stderr.strip()[:120]}") continue print(f" ok {d['depot']:<17} eregion a jour ({avance} commit(s) pousse(s))") if refus: print(f"\nREFUS : {', '.join(refus)}. Rien n'a ete porte a la forge du site — la corriger " f"en retard vaut mieux que la corriger avec un depot incomplet.") return 2 print("\nPorter a la forge du site (runner) :") env = dict(os.environ, **({"DEPOT": args.depot} if args.depot else {})) if subprocess.run(["make", "--no-print-directory", "genome-pousser"] + ([f"DEPOT={args.depot}"] if args.depot else []), cwd=RACINE, env=env, capture_output=True).returncode != 0: print(" ECHEC de `make genome-pousser` : eregion est a jour, la forge du site NON.") return 3 r = subprocess.run(["make", "--no-print-directory", "genome-etat"], cwd=RACINE, capture_output=True, text=True) for ligne in r.stdout.splitlines(): if '"msg": "' in ligne and " : poste " in ligne: print(" " + ligne.split('"msg": "', 1)[1].replace("\\n", " ").rstrip('"')) if r.returncode != 0: print(" La forge du site ne porte PAS la tete du poste — voir `make genome-etat`.") return 4 print("Publie : eregion et la forge du site portent la tete du poste.") return 0 if __name__ == "__main__": sys.exit(main())