mirror git: el poblador reporta el error real de git, no una conjetura

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
This commit is contained in:
Sergio
2026-08-26 21:07:10 +00:00
co-authored by Claude Opus 5
parent 75b8cdb945
commit 276efdeced
2 changed files with 35 additions and 12 deletions
+26 -11
View File
@@ -40,22 +40,37 @@ def ya_en_mirror():
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. None si upstream no lo da."""
"""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))
if git("init", "-q").returncode != 0: return None
if git("remote", "add", "origin", e["repo"]).returncode != 0: return None
# --depth 1 del SHA exacto: el servidor debe permitir fetch por sha (uploadpack.allowReachableSHA1).
if git("fetch", "-q", "--depth", "1", "origin", e["commit"], t=1800).returncode != 0:
return None
if git("update-ref", "refs/heads/hammer", e["commit"]).returncode != 0: return None
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')
if git("bundle", "create", out, "refs/heads/hammer", t=1800).returncode != 0: return None
return out if os.path.exists(out) else None
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()
@@ -76,10 +91,10 @@ def main():
tmpdir = tempfile.mkdtemp(prefix="hammer-git-mirror-")
try:
for i, e in enumerate(faltan, 1):
b = bundle(e, tmpdir)
b, motivo = bundle(e, tmpdir)
if not b:
fallo += 1
print(f" [{i}/{len(faltan)}] {e['receta']:<24}upstream no da el commit", flush=True)
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,