ADR 0013: mirror de fuentes git — bundles shallow por commit
Los 606 repos por commit son el 52% de las fuentes y el mirror de tarballs no los cubría. Se espejan
como `hammer/fuentes-git/{commit}.bundle` — 571 commits distintos, porque hay commits compartidos
entre colas y se espeja uno solo.
La identidad es el commit y la verificación la hace git: al desempaquetar comprueba cada objeto
contra su SHA, así que un bundle alterado no pasa. No hace falta índice ni sha256 aparte.
SHALLOW, NO CLONES COMPLETOS. El bundle sale de un `fetch --depth 1` del commit exacto: para `act`
son 9,3 MB en vez del repo entero, y con 571 fuentes eso decide si el mirror cabe. Es legítimo
porque hammer NUNCA usa la historia — lo único que hace con un repo es `git archive <commit> |
tar -x`, materializar un árbol.
EL DETALLE QUE COSTÓ ENCONTRAR. Un bundle hecho desde un repo shallow no lleva la frontera de
historia, y al desempaquetarlo git aborta con «Failed to traverse parents … did not send all
necessary objects». El mensaje dice que faltan objetos y es ENGAÑOSO: llegan enteros —`git archive`
ya funciona pese al error—; lo que falta es decirle a git dónde termina la historia. Se escribe el
propio commit en `<destino>/shallow` antes del fetch. No hay nada que transportar: la frontera de un
`--depth 1` es exactamente ese commit.
Y UN BUG PROPIO QUE VALE DOCUMENTAR: se pasaba al `git fetch` la ruta RELATIVA del bundle, y como
`run_git` invoca `git -C <destino>`, git la resolvía dentro de `<destino>`. Como el fallo del mirror
se traga a propósito para caer a upstream, el síntoma salía lejísimos: el build moría con «commit …
no existe en <repo> tras fetch», culpando a upstream de un error de ruta local. Ése es el precio de
que el mirror falle en silencio, y por eso el silencio se paga con comentarios explícitos.
Verificado igual que el de tarballs, con receta EFÍMERA para que no haya cache-hit: commit de `act`
ya espejado + un repo cuyo host no resuelve por DNS. Sin HAMMER_MIRROR_GIT falla en el clone; con
él, sella — y el artefacto trae el README.md real de act.
`cargo test -p hammer-build`: 5/5. Población de los 571 bundles corriendo aparte.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v9ozVm44p6DB7EMXeZK4o
This commit is contained in:
@@ -155,10 +155,54 @@ artefacto tiene el contenido real. Los bytes no pudieron venir de ningún otro s
|
||||
la verdad: la verdad son las recetas en git. Si el mirror se pierde, se repuebla desde cualquier
|
||||
máquina que tenga `work/tarballs`.
|
||||
|
||||
## Fuentes git: bundles shallow por commit
|
||||
|
||||
Los 606 repos por commit son el 52% de las fuentes y no los cubre el mirror de tarballs. Se espejan
|
||||
como **bundles**, en un espacio de nombres propio: `hammer/fuentes-git/{commit}.bundle` (571 commits
|
||||
distintos — hay commits compartidos entre colas, y se espeja uno solo).
|
||||
|
||||
**La identidad es el commit y la verificación la hace git.** Al desempaquetar, git comprueba cada
|
||||
objeto contra su SHA: un bundle alterado no pasa. No hace falta índice ni un `sha256` aparte, igual
|
||||
que en los tarballs el nombre del fichero ES su verificación.
|
||||
|
||||
### Shallow, no clones completos
|
||||
|
||||
El bundle se genera desde un `git fetch --depth 1` del commit exacto. Para `act` son **9,3 MB en vez
|
||||
del repositorio entero**, y con 571 fuentes esa diferencia decide si el mirror cabe. Es legítimo
|
||||
porque **hammer nunca usa la historia**: lo único que hace con un repo es `git archive <commit> |
|
||||
tar -x`, o sea materializar un árbol.
|
||||
|
||||
### El detalle que costó encontrar: el fichero `shallow`
|
||||
|
||||
Un bundle hecho desde un repo shallow **no lleva la frontera de historia**. Al desempaquetarlo:
|
||||
|
||||
```text
|
||||
fatal: Failed to traverse parents of commit 4f41128…
|
||||
error: … did not send all necessary objects
|
||||
```
|
||||
|
||||
El mensaje dice «no envió todos los objetos necesarios» y **es engañoso**: los objetos del commit
|
||||
llegan enteros — de hecho `git archive` ya funciona pese al error. Lo que falta no es un objeto, es
|
||||
decirle a git dónde termina la historia. Se arregla escribiendo el propio commit en
|
||||
`<destino>/shallow` **antes** del fetch, y no hay que transportar nada: la frontera de un clon
|
||||
`--depth 1` es exactamente ese commit.
|
||||
|
||||
### Y un bug propio que vale documentar
|
||||
|
||||
`hidratar_desde_bundle` pasaba al `git fetch` la ruta **relativa** del bundle. Como `run_git` invoca
|
||||
`git -C <destino> …`, git la resolvía **dentro de `<destino>`** y no encontraba nada. Como el fallo
|
||||
del mirror se traga a propósito (para caer a upstream), el síntoma salía lejísimos del origen: el
|
||||
build moría con *«commit … no existe en \<repo\> tras fetch»*, **culpando a upstream de un error de
|
||||
ruta local**. Se arregla con `canonicalize`. Es el precio de que el mirror falle en silencio, y por
|
||||
eso el silencio se paga con comentarios explícitos en el código.
|
||||
|
||||
## Lo que este ADR NO decide
|
||||
|
||||
- Qué hacer con la concentración en GitHub.
|
||||
- Si el mirror debe replicarse fuera de Hetzner (hoy el repo, el respaldo y el mirror están todos en
|
||||
la misma cuenta y el mismo proveedor).
|
||||
- El mirror de fuentes **git**: hoy sólo se espeja el tarball. Los 606 repos por commit siguen
|
||||
dependiendo del remoto. Es el siguiente eslabón, y es más grande.
|
||||
- El **coste de mantener el mirror git al día**: un commit nuevo en una receta es un bundle nuevo, y
|
||||
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`.
|
||||
|
||||
Reference in New Issue
Block a user