Imprimía siempre «upstream no da el commit» pasara lo que pasara. En la primera tanda marcó así a diffutils, findutils-xargs y kustomize, cuyos commits se traen a mano sin problema: era transitorio. Ahora bundle() devuelve (ruta, motivo) con el stderr del paso que falló. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016v9ozVm44p6DB7EMXeZK4o
115 lines
5.2 KiB
Python
Executable File
115 lines
5.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Genera y sube bundles shallow de las fuentes git del corpus. Ver ADR 0013 y el .sh hermano.
|
|
|
|
Es INCREMENTAL: consulta primero qué commits ya están en el mirror y sólo trabaja los que faltan.
|
|
Interrumpible sin penalización — cada bundle es independiente.
|
|
"""
|
|
import os, re, subprocess, sys, glob, tempfile, shutil
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
os.chdir(ROOT)
|
|
SB_USER = os.environ.get("SB_USER", "u647150")
|
|
SB_HOST = os.environ.get("SB_HOST", "u647150.your-storagebox.de")
|
|
SB_PORT = os.environ.get("SB_PORT", "23")
|
|
KEY = os.environ.get("KEY", os.path.expanduser("~/.ssh/github5"))
|
|
LIMITE = int(os.environ.get("LIMITE", "0"))
|
|
SOLO = os.environ.get("SOLO", "")
|
|
DEST = "hammer/fuentes-git"
|
|
SSH = ["ssh", "-4", "-p", SB_PORT, "-i", KEY, "-o", "StrictHostKeyChecking=accept-new",
|
|
"-o", "ConnectTimeout=30"]
|
|
|
|
def fuentes_git():
|
|
vistos, out = set(), []
|
|
for f in sorted(glob.glob("recipes/**/*.toml", recursive=True)):
|
|
s = open(f, errors="ignore").read()
|
|
repo = re.search(r'^\s*repo\s*=\s*"([^"]+)"', s, re.M)
|
|
com = re.search(r'^\s*commit\s*=\s*"([0-9a-fA-F]{7,40})"', s, re.M)
|
|
if not (repo and com):
|
|
continue
|
|
nombre = (re.search(r'^\s*name\s*=\s*"([^"]+)"', s, re.M) or [None, os.path.basename(f)[:-5]])[1]
|
|
c = com.group(1).lower()
|
|
if c in vistos: # el mismo commit desde dos colas = un solo bundle
|
|
continue
|
|
vistos.add(c)
|
|
out.append({"receta": nombre, "repo": repo.group(1), "commit": c})
|
|
return out
|
|
|
|
def ya_en_mirror():
|
|
r = subprocess.run(SSH + [f"{SB_USER}@{SB_HOST}", f"ls {DEST}"],
|
|
capture_output=True, text=True, timeout=180)
|
|
return {l.strip()[:-7] for l in r.stdout.splitlines() if l.strip().endswith(".bundle")}
|
|
|
|
def bundle(e, tmpdir):
|
|
"""Clona --depth 1 el commit exacto y emite un bundle.
|
|
|
|
Devuelve (ruta, None) si sale bien y (None, motivo) si no. El motivo es el stderr REAL de git,
|
|
no una conjetura: un fallo acá puede ser el servidor negando el fetch por sha suelto, pero
|
|
tambien un timeout, DNS, o un rate-limit, y cada uno se arregla distinto. Ver ADR 0013.
|
|
"""
|
|
d = os.path.join(tmpdir, "r")
|
|
shutil.rmtree(d, ignore_errors=True)
|
|
os.makedirs(d)
|
|
def git(*a, **kw):
|
|
return subprocess.run(["git", "-C", d] + list(a), capture_output=True, text=True,
|
|
timeout=kw.get("t", 900))
|
|
def paso(etiqueta, *a, **kw):
|
|
try:
|
|
r = git(*a, **kw)
|
|
except subprocess.TimeoutExpired:
|
|
return f"{etiqueta}: timeout"
|
|
return None if r.returncode == 0 else f"{etiqueta}: {(r.stderr or r.stdout).strip().splitlines()[-1][:160] if (r.stderr or r.stdout).strip() else 'rc=' + str(r.returncode)}"
|
|
|
|
out = os.path.join(tmpdir, f'{e["commit"]}.bundle')
|
|
for etiqueta, args, kw in (
|
|
("init", ("init", "-q"), {}),
|
|
("remote", ("remote", "add", "origin", e["repo"]), {}),
|
|
# --depth 1 del SHA exacto: el servidor debe permitir fetch por sha (uploadpack.allowReachableSHA1).
|
|
("fetch", ("fetch", "-q", "--depth", "1", "origin", e["commit"]), {"t": 1800}),
|
|
("update-ref",("update-ref", "refs/heads/hammer", e["commit"]), {}),
|
|
("bundle", ("bundle", "create", out, "refs/heads/hammer"), {"t": 1800}),
|
|
):
|
|
motivo = paso(etiqueta, *args, **kw)
|
|
if motivo: return None, motivo
|
|
return (out, None) if os.path.exists(out) else (None, "bundle: git dijo OK pero no hay fichero")
|
|
|
|
def main():
|
|
fs = fuentes_git()
|
|
if SOLO: fs = [e for e in fs if e["receta"] == SOLO]
|
|
print(f"== fuentes git en el corpus: {len(fs)} commits distintos", flush=True)
|
|
try:
|
|
hay = ya_en_mirror()
|
|
except Exception as ex:
|
|
print(f"!! no pude listar el mirror ({ex}); asumo vacío", flush=True); hay = set()
|
|
print(f"== ya en el mirror: {len(hay)}", flush=True)
|
|
faltan = [e for e in fs if e["commit"] not in hay]
|
|
if LIMITE: faltan = faltan[:LIMITE]
|
|
print(f"== a espejar ahora: {len(faltan)}", flush=True)
|
|
|
|
subprocess.run(SSH + [f"{SB_USER}@{SB_HOST}", f"mkdir hammer {DEST}"],
|
|
capture_output=True, text=True)
|
|
ok = fallo = 0
|
|
tmpdir = tempfile.mkdtemp(prefix="hammer-git-mirror-")
|
|
try:
|
|
for i, e in enumerate(faltan, 1):
|
|
b, motivo = bundle(e, tmpdir)
|
|
if not b:
|
|
fallo += 1
|
|
print(f" [{i}/{len(faltan)}] {e['receta']:<24} ✗ {motivo}", flush=True)
|
|
continue
|
|
mb = os.path.getsize(b) / 1e6
|
|
r = subprocess.run(["rsync", "-a", "-e", " ".join(SSH), b,
|
|
f"{SB_USER}@{SB_HOST}:{DEST}/"], capture_output=True, text=True, timeout=3600)
|
|
if r.returncode == 0:
|
|
ok += 1
|
|
print(f" [{i}/{len(faltan)}] {e['receta']:<24} ✓ {mb:6.1f} MB", flush=True)
|
|
else:
|
|
fallo += 1
|
|
print(f" [{i}/{len(faltan)}] {e['receta']:<24} ✗ subida: {r.stderr.strip()[:60]}", flush=True)
|
|
os.remove(b)
|
|
finally:
|
|
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
print(f"== espejados {ok} fallidos {fallo}")
|
|
return 0
|
|
|
|
sys.exit(main())
|