From 93b6256dbad0082ce7ddd6f53909aba29b4ecca4 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Tue, 25 Aug 2026 06:19:21 -0400 Subject: [PATCH] =?UTF-8?q?[ADD]=20d=C3=A9ploiement=20:=20la=20VM=20clone?= =?UTF-8?q?=20le=20d=C3=A9p=C3=B4t=20distant,=20pas=20ce=20checkout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit « Le problème est revenu » — alors qu'il était corrigé la veille. La VM ne reçoit pas le checkout d'ici : elle CLONE la branche depuis le dépôt distant. Tout ce qui tourne dedans — install_proxmox.sh, les scripts d'installation, le Makefile — vient donc de là. Vécu deux fois de suite. Le correctif de /etc/hosts était commité ici, absent du distant : chaque VM déployée ensuite recevait l'ancien script, et le même défaut revenait à l'identique. Rien ne le disait, et il a fallu comparer les deux versions du fichier à la main pour comprendre. Soixante-et-onze commits séparaient les deux. L'écart est donc dit AVANT de déployer, là où l'on peut encore renoncer : le nombre, les trois premiers sujets, et « git push ». Sur les deux voies, car les deux clonent. Une branche que le distant ne connaît pas n'est pas un écart — c'est une question qui ne se pose pas. La dire quand même vaudrait un avertissement à chaque déploiement d'une branche neuve. --- EN --- "The problem came back" — though it had been fixed the day before. The VM does not receive this checkout: it CLONES the branch from the remote. Everything that runs inside it — install_proxmox.sh, the install scripts, the Makefile — comes from there. Twice in a row. The /etc/hosts fix was committed here and absent from the remote: every VM deployed afterwards got the old script, and the same defect returned unchanged. Nothing said so, and it took comparing both versions of the file by hand to understand. Seventy-one commits separated them. The gap is therefore stated BEFORE deploying, where you can still back out: the count, the first three subjects, and "git push". On both paths, since both clone. A branch the remote does not know is not a gap — it is a question that does not arise. Saying it anyway would mean a warning on every deployment of a new branch. Assisted-by: Claude Opus 5 (cherry picked from commit de27be5e736eb6e9bd01efd292e01c3b2231f91a) --- script/todo/proxmox_menu.py | 7 ++++ script/todo/qemu_deploy.py | 6 +++ script/todo/qemu_manage.py | 61 +++++++++++++++++++++++++++++ script/todo/todo_i18n.py | 16 ++++++++ test/test_proxmox_form.py | 76 +++++++++++++++++++++++++++++++++++++ 5 files changed, 166 insertions(+) diff --git a/script/todo/proxmox_menu.py b/script/todo/proxmox_menu.py index 65ea910..2c01844 100644 --- a/script/todo/proxmox_menu.py +++ b/script/todo/proxmox_menu.py @@ -1483,6 +1483,13 @@ class ProxmoxMenuMixin: f" ERPLibre : {spec['install'].get('label') or ''}" f" ({spec['install'].get('branch')})" ) + # La VM CLONE la branche depuis le dépôt distant : tout ce qui + # tourne dedans — install_proxmox.sh compris — vient de là, pas + # d'ici. Un correctif non poussé est invisible pour elle. + for ligne in self._qemu_branch_gap_lines( + spec["install"].get("branch") or "" + ): + print(f" {ligne}") return self._is_yes_default_yes( input(f"\n{t('Deploy this VM now? (Y/n): ')}") ) diff --git a/script/todo/qemu_deploy.py b/script/todo/qemu_deploy.py index 4d8c95a..9ea06f1 100644 --- a/script/todo/qemu_deploy.py +++ b/script/todo/qemu_deploy.py @@ -885,6 +885,12 @@ class QemuDeployMixin: f" {t('Install:')} {t('branch')} {br_txt}, " f"{t('profile')} {lb_txt}, {env}" ) + # La VM ne reçoit pas CE checkout : elle CLONE la branche depuis + # le dépôt distant. Un correctif commité ici et non poussé n'y est + # donc pas, et le défaut « revient » alors qu'il est corrigé — + # vécu deux fois de suite sur install_proxmox.sh. + for ligne in self._qemu_branch_gap_lines(br_txt): + print(f" {ligne}") else: print(f" {t('Install:')} {t('no')}") flavour = spec.get("desktop") diff --git a/script/todo/qemu_manage.py b/script/todo/qemu_manage.py index 50f32b0..04f4694 100644 --- a/script/todo/qemu_manage.py +++ b/script/todo/qemu_manage.py @@ -2537,6 +2537,67 @@ class QemuManageMixin: nom = (res.stdout or "").strip() return "" if res.returncode or nom == "HEAD" else nom + @staticmethod + def _qemu_branch_gap(branche): + """Combien de commits LOCAUX manquent à origin/, et lesquels. + + Rend (nombre, [sujets]) — (0, []) quand il n'y a rien à dire, ou quand + la question ne se pose pas (pas de dépôt, pas de distant). + + Pourquoi le déploiement s'en soucie : la VM ne reçoit PAS le checkout + d'ici, elle CLONE la branche depuis le dépôt distant. Tout ce qui + tourne dans la VM — install_proxmox.sh, les scripts d'installation, le + Makefile — vient donc de là. + + Vécu deux fois de suite. Un correctif de install_proxmox.sh, commité + ici, absent du distant : chaque VM déployée ensuite recevait l'ancien + script, et le défaut « revenait » alors qu'il était corrigé. Rien ne + le disait ; il a fallu comparer les deux versions à la main. + """ + if not branche: + return 0, [] + try: + res = subprocess.run( + [ + "git", + "log", + "--oneline", + "--no-decorate", + f"origin/{branche}..HEAD", + ], + capture_output=True, + text=True, + timeout=15, + ) + except (OSError, subprocess.SubprocessError): + return 0, [] + # Une branche inconnue du distant, ou aucun distant : ce n'est pas un + # écart à signaler, c'est une question qui ne se pose pas. + if res.returncode: + return 0, [] + sujets = [ + ligne.strip() + for ligne in (res.stdout or "").splitlines() + if ligne.strip() + ] + return len(sujets), sujets + + def _qemu_branch_gap_lines(self, branche, limite=3): + """Les lignes à dire avant de déployer, ou [].""" + nombre, sujets = self._qemu_branch_gap(branche) + if not nombre: + return [] + lignes = [ + f"⚠ {t('The VM clones')} origin/{branche}, " + f"{t('not this checkout.')}", + f" {nombre} {t('local commit(s) are missing there:')}", + ] + lignes += [f" {s}" for s in sujets[:limite]] + if nombre > limite: + lignes.append(f" … {nombre - limite} {t('more')}") + lignes.append(f" → git push {t('to deploy your own work.')}") + return lignes + def _qemu_branch_list(self): """Branches distantes d'ERPLibre, triées. Vide si le réseau manque. diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index 20f5471..73c72b5 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -3382,6 +3382,22 @@ TRANSLATIONS = { "fr": "rejouer install_proxmox.sh sur l'hôte : il corrige /etc/hosts et empêche cloud-init de le défaire.", "en": "replay install_proxmox.sh on the host: it fixes /etc/hosts and stops cloud-init undoing it.", }, + "The VM clones": { + "fr": "La VM clone", + "en": "The VM clones", + }, + "not this checkout.": { + "fr": "et non ce checkout.", + "en": "not this checkout.", + }, + "local commit(s) are missing there:": { + "fr": "commit(s) local(aux) y manquent :", + "en": "local commit(s) are missing there:", + }, + "to deploy your own work.": { + "fr": "pour déployer votre propre travail.", + "en": "to deploy your own work.", + }, "No storage able to hold a VM disk.": { "fr": "Aucun stockage capable d'héberger un disque de VM.", "en": "No storage able to hold a VM disk.", diff --git a/test/test_proxmox_form.py b/test/test_proxmox_form.py index bccd158..adbc7fc 100644 --- a/test/test_proxmox_form.py +++ b/test/test_proxmox_form.py @@ -686,6 +686,82 @@ class TestUnParcMixte(unittest.TestCase): self.assertEqual(vu["kw"]["desktop"], "") +class TestLaVmCloneLeDepotDistant(unittest.TestCase): + """« Le problème est revenu » — alors qu'il était corrigé. + + La VM ne reçoit pas le checkout d'ici : elle CLONE la branche depuis le + dépôt DISTANT. Tout ce qui tourne dedans — install_proxmox.sh, les + scripts d'installation, le Makefile — vient donc de là. Un correctif + commité ici et non poussé lui est invisible. + + Vécu deux fois de suite : la correction de /etc/hosts était dans le + checkout depuis la veille, absente du distant, et chaque VM déployée + ensuite recevait l'ancien script. Il a fallu comparer les deux versions à + la main pour le voir. Rien ne le disait.""" + + def _todo(self, sortie, code=0): + import sys + + sys.argv = ["todo.py"] + from script.todo.todo import TODO + + todo = TODO.__new__(TODO) + faux = mock.Mock(returncode=code, stdout=sortie) + return todo, faux + + def test_the_gap_is_counted_and_named(self): + todo, faux = self._todo( + "abc1234 [FIX] un correctif\ndef5678 [ADD] autre chose\n" + ) + with mock.patch("subprocess.run", return_value=faux): + nombre, sujets = todo._qemu_branch_gap("develop") + self.assertEqual(nombre, 2) + self.assertIn("[FIX] un correctif", sujets[0]) + + def test_nothing_to_say_when_the_remote_is_up_to_date(self): + todo, faux = self._todo("") + with mock.patch("subprocess.run", return_value=faux): + self.assertEqual(todo._qemu_branch_gap("develop"), (0, [])) + self.assertEqual(todo._qemu_branch_gap_lines("develop"), []) + + def test_an_unknown_remote_branch_is_not_a_gap(self): + # « origin/xyz » inconnu fait échouer git : ce n'est pas un écart à + # signaler, c'est une question qui ne se pose pas. Le dire quand même + # serait un avertissement à chaque déploiement d'une branche neuve. + todo, faux = self._todo("", code=128) + with mock.patch("subprocess.run", return_value=faux): + self.assertEqual(todo._qemu_branch_gap("nouvelle"), (0, [])) + + def test_no_branch_asks_nothing(self): + todo, _faux = self._todo("") + self.assertEqual(todo._qemu_branch_gap(""), (0, [])) + + def test_the_long_list_is_trimmed_but_counted(self): + todo, faux = self._todo( + "\n".join(f"c{i} sujet {i}" for i in range(10)) + ) + with mock.patch("subprocess.run", return_value=faux): + lignes = todo._qemu_branch_gap_lines("develop", limite=2) + texte = " ".join(lignes) + self.assertIn("10", texte, "le nombre TOTAL doit rester lisible") + self.assertIn("8", texte, "et ce qui n'est pas montré, dit") + self.assertIn("git push", texte) + + def test_both_screens_say_it_before_deploying(self): + # L'avertissement ne vaut que là où on peut encore renoncer. + import inspect + + from script.todo.proxmox_menu import ProxmoxMenuMixin + from script.todo.qemu_deploy import QemuDeployMixin + + for fn in ( + ProxmoxMenuMixin._pve_confirm_spec, + QemuDeployMixin._qemu_print_recap, + ): + with self.subTest(fonction=fn.__name__): + self.assertIn("_qemu_branch_gap_lines", inspect.getsource(fn)) + + class TestLePontQuiNeMeneraitNullePart(unittest.TestCase): """Le pont NAT était écrit AVANT qu'on sache si le NAT existe.