|
|
|
|
@@ -5,7 +5,7 @@ Accès lecture seule uniquement (fetch/pull/checkout, jamais de push).
|
|
|
|
|
Tous les chemins sont relatifs à l'emplacement de l'exécutable.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
VERSION = "0.5.2"
|
|
|
|
|
VERSION = "0.5.3"
|
|
|
|
|
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
@@ -77,13 +77,13 @@ def setup_logging():
|
|
|
|
|
log = setup_logging()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run_git(args, cwd=None):
|
|
|
|
|
def run_git(args, cwd=None, timeout=30):
|
|
|
|
|
# -c safe.directory=* : évite l'erreur "dubious ownership" sur clé USB
|
|
|
|
|
cmd = ["git", "-c", "safe.directory=*"] + args
|
|
|
|
|
log.debug(f"git {' '.join(args)} (cwd={cwd})")
|
|
|
|
|
try:
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
cmd, cwd=cwd, capture_output=True, text=True, timeout=30,
|
|
|
|
|
cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout,
|
|
|
|
|
creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0,
|
|
|
|
|
)
|
|
|
|
|
if result.returncode != 0 and result.stderr.strip():
|
|
|
|
|
@@ -341,21 +341,19 @@ def check_repo(repo):
|
|
|
|
|
return result
|
|
|
|
|
result["branch"] = branch
|
|
|
|
|
|
|
|
|
|
# Vérifier que le remote est accessible via l'URL du config.ini
|
|
|
|
|
code, _, err = run_git(["ls-remote", "--exit-code", url, "HEAD"])
|
|
|
|
|
if code != 0:
|
|
|
|
|
log.warning(f"[{name}] Remote inaccessible: {url}")
|
|
|
|
|
result["error"] = "Depot hors ligne (remote inaccessible)"
|
|
|
|
|
result["offline"] = True
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
# Mettre à jour l'URL origin si elle a changé dans config.ini
|
|
|
|
|
run_git(["remote", "set-url", "origin", url], cwd=local_path)
|
|
|
|
|
|
|
|
|
|
# Fetch
|
|
|
|
|
# Fetch (détecte aussi si le remote est inaccessible)
|
|
|
|
|
code, _, err = run_git(["fetch", "origin"], cwd=local_path)
|
|
|
|
|
if code != 0:
|
|
|
|
|
result["error"] = f"Erreur fetch : {err}"
|
|
|
|
|
offline_keywords = ["could not resolve", "connection refused", "unable to connect", "timed out", "the remote end hung up"]
|
|
|
|
|
if any(kw in err.lower() for kw in offline_keywords):
|
|
|
|
|
log.warning(f"[{name}] Remote inaccessible: {url}")
|
|
|
|
|
result["error"] = "Depot hors ligne (remote inaccessible)"
|
|
|
|
|
result["offline"] = True
|
|
|
|
|
else:
|
|
|
|
|
result["error"] = f"Erreur fetch : {err}"
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
# Comparer HEAD local vs origin
|
|
|
|
|
@@ -436,7 +434,7 @@ def do_clone(repo):
|
|
|
|
|
"""Clone un dépôt."""
|
|
|
|
|
local_path = str(resolve_relative(repo["path"]))
|
|
|
|
|
log.info(f"Clonage: {repo['url']} -> {local_path}")
|
|
|
|
|
code, _, err = run_git(["clone", repo["url"], local_path])
|
|
|
|
|
code, _, err = run_git(["clone", repo["url"], local_path], timeout=300)
|
|
|
|
|
if code == 0:
|
|
|
|
|
log.info(f"Clonage reussi: {local_path}")
|
|
|
|
|
else:
|
|
|
|
|
@@ -447,7 +445,7 @@ def do_clone(repo):
|
|
|
|
|
def do_pull(local_path, branch):
|
|
|
|
|
"""Pull les mises à jour (lecture seule)."""
|
|
|
|
|
log.info(f"Pull: {local_path} (branche {branch})")
|
|
|
|
|
code, out, err = run_git(["pull", "origin", branch], cwd=local_path)
|
|
|
|
|
code, out, err = run_git(["pull", "origin", branch], cwd=local_path, timeout=120)
|
|
|
|
|
if code == 0:
|
|
|
|
|
log.info(f"Pull reussi: {local_path}")
|
|
|
|
|
else:
|
|
|
|
|
@@ -800,7 +798,7 @@ class App(tk.Tk):
|
|
|
|
|
|
|
|
|
|
return card
|
|
|
|
|
|
|
|
|
|
def _do_update(self, res):
|
|
|
|
|
def _do_update(self, res, batch=False):
|
|
|
|
|
"""Met à jour un dépôt (pull + restore)."""
|
|
|
|
|
name = res["name"]
|
|
|
|
|
log.info(f"[{name}] MAJ unitaire demandee")
|
|
|
|
|
@@ -859,7 +857,7 @@ class App(tk.Tk):
|
|
|
|
|
status = "SUCCES" if success else "ECHEC"
|
|
|
|
|
log.info(f"[{name}] MAJ unitaire terminee - {status}")
|
|
|
|
|
self._log_gui(f"[{name}] Termine - {status}", "success" if success else "error")
|
|
|
|
|
self.after(0, lambda: self._show_update_result(res, messages, success))
|
|
|
|
|
self.after(0, lambda: self._show_update_result(res, messages, success, batch=batch))
|
|
|
|
|
|
|
|
|
|
threading.Thread(target=work, daemon=True).start()
|
|
|
|
|
|
|
|
|
|
@@ -888,18 +886,28 @@ class App(tk.Tk):
|
|
|
|
|
|
|
|
|
|
threading.Thread(target=work, daemon=True).start()
|
|
|
|
|
|
|
|
|
|
def _show_update_result(self, res, messages, success):
|
|
|
|
|
title = "Mise a jour" if success else "Erreur"
|
|
|
|
|
messagebox.showinfo(title, f"{res['name']}\n\n" + "\n".join(messages))
|
|
|
|
|
self._start_check()
|
|
|
|
|
def _show_update_result(self, res, messages, success, batch=False):
|
|
|
|
|
if batch:
|
|
|
|
|
# En mode batch : pas de messagebox individuelle, on ne rafraichit qu'une fois à la fin
|
|
|
|
|
self._batch_remaining -= 1
|
|
|
|
|
if self._batch_remaining == 0:
|
|
|
|
|
self._start_check()
|
|
|
|
|
else:
|
|
|
|
|
title = "Mise a jour" if success else "Erreur"
|
|
|
|
|
messagebox.showinfo(title, f"{res['name']}\n\n" + "\n".join(messages))
|
|
|
|
|
self._start_check()
|
|
|
|
|
|
|
|
|
|
def _update_all(self):
|
|
|
|
|
"""Met à jour tous les dépôts qui ont des MAJ."""
|
|
|
|
|
to_update = [r for r in self.repo_results if not r["up_to_date"] and not r.get("error") and not r.get("needs_clone")]
|
|
|
|
|
if not to_update:
|
|
|
|
|
return
|
|
|
|
|
log.info(f"MAJ globale demandee - {len(to_update)} depot(s) a mettre a jour")
|
|
|
|
|
self._log_gui(f"MAJ globale : {len(to_update)} depot(s) a mettre a jour", "warning")
|
|
|
|
|
# Compteur pour n'appeler _start_check qu'une seule fois quand tous sont termines
|
|
|
|
|
self._batch_remaining = len(to_update)
|
|
|
|
|
for res in to_update:
|
|
|
|
|
self._do_update(res)
|
|
|
|
|
self._do_update(res, batch=True)
|
|
|
|
|
|
|
|
|
|
def _show_no_repos(self):
|
|
|
|
|
bg_card = "#313244"
|
|
|
|
|
|