> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
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
diff --git a/docs/adr/0013-mirror-de-fuentes.md b/docs/adr/0013-mirror-de-fuentes.md
index f66e0b65..1a421956 100644
--- a/docs/adr/0013-mirror-de-fuentes.md
+++ b/docs/adr/0013-mirror-de-fuentes.md
@@ -205,4 +205,12 @@ eso el silencio se paga con comentarios explícitos en el código.
hoy eso lo dispara una persona corriendo `mirror-git-poblar.sh`, no el latido.
- Qué hacer con los repos cuyo servidor **no permite fetch por SHA suelto**
(`uploadpack.allowReachableSHA1InWant` desactivado): ahí el `--depth 1` del commit exacto falla y
- hay que caer a un clon completo. El poblador los reporta como `✗ upstream no da el commit`.
+ hay que caer a un clon completo.
+
+ El poblador reporta el **stderr real de git**, no una conjetura. Lo escribí al revés la primera
+ vez — imprimía siempre `✗ upstream no da el commit` — y en la primera tanda marcó así a tres
+ recetas (`diffutils`, `findutils-xargs`, `kustomize`) cuyos commits **sí se traen a mano**. Un
+ fallo en ese punto puede ser el servidor negando el sha, pero también un timeout, DNS o un
+ rate-limit de GitHub, y cada uno se arregla distinto. **Un diagnóstico hardcodeado convierte un
+ fallo transitorio en una conclusión falsa sobre upstream**, que es exactamente el error que ya
+ costó caro en la ruta relativa del bundle.
diff --git a/scripts/fuentes/mirror-git-poblar.py b/scripts/fuentes/mirror-git-poblar.py
index 861cab04..43dc7931 100755
--- a/scripts/fuentes/mirror-git-poblar.py
+++ b/scripts/fuentes/mirror-git-poblar.py
@@ -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,