fix(granja): el publicador avisaba 48 veces al día de algo que no pasaba
Tras publicar, el intento de adelantar la rama local **falla en el caso normal** y el aviso lo trataba como anomalía: «⚠ la rama local no pudo adelantar (árbol con cambios)», cada media hora, para siempre. Es la misma trampa que `espejo-setup.sh` acababa de dejar de pisar — un guardián que grita en falso se deja de leer, y el día que grite de verdad tampoco se lee. Y no pasaba nada: el generador acaba de reescribir `docs/state/` en el árbol y eso es justo lo que se publicó, así que **el árbol ya tiene el mismo contenido que el commit nuevo**. git igual se niega («local changes would be overwritten») porque su comprobación mira ÍNDICE contra HEAD, no árbol contra destino. Medido en `/opt/takana`: los 10 ficheros que bloqueaban el ff eran byte a byte idénticos a lo publicado. Entonces el adelanto se hace, con `reset --mixed`, que deja el árbol intacto. Va GUARDADO por dos condiciones, y la primera importa más que el arreglo: **el índice no tiene nada stageado**. El índice es estado COMPARTIDO (regla 2) y pisar lo que otro agente dejó en `git add` es el daño que este repo ya documentó con un commit que se llevó un rename ajeno por delante. Si hay algo stageado, no se toca nada y se dice en voz baja. De paso, el ref de SEGUIMIENTO: se trae con refspec explícito y se actualiza DESPUÉS del push. Sin eso quedaba un ciclo atrás por construcción —se trae antes de empujar— y el `git log origin/main..HEAD` que este guion le sugiere a un humano mentía justo cuando se iba a usar para diagnosticar. Controles 9-12 en repo de juguete: árbol con el contenido publicado ⇒ adelanta y queda limpio; tres ciclos seguidos ⇒ `HEAD == origin/main`, 0 sucios; con algo stageado por otro ⇒ NO toca el índice y lo ajeno sigue stageado. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
1 archivos · +36 −9
diff --git a/scripts/farm/publicar-estado.sh b/scripts/farm/publicar-estado.sh
index 9a390be6..30b9fe70 100755
--- a/scripts/farm/publicar-estado.sh
+++ b/scripts/farm/publicar-estado.sh
@@ -81,8 +81,13 @@ intento=0
while :; do
intento=$((intento + 1))
- git fetch -q "$REMOTE" main || { echo "!! no pude traer $REMOTE/main"; exit 1; }
- base="$(git rev-parse FETCH_HEAD)"
+ # El refspec explícito `+main:refs/remotes/<remoto>/main` no es adorno: con `git fetch <remoto>
+ # main` a secas el ref de SEGUIMIENTO puede quedar rancio, y entonces el `git log origin/main..HEAD`
+ # que este guion le sugiere a un humano miente justo cuando se va a usar para diagnosticar. El `+`
+ # fuerza el ref de seguimiento, que no es trabajo local: se puede pisar sin perder nada.
+ git fetch -q "$REMOTE" "+main:refs/remotes/$REMOTE/main" \
+ || { echo "!! no pude traer $REMOTE/main"; exit 1; }
+ base="$(git rev-parse "refs/remotes/$REMOTE/main")"
# Índice temporal sembrado con el árbol de origin/main. `GIT_INDEX_FILE` lo aísla del índice
# compartido del repo, que es el que arrastra el trabajo a medias de otro agente.
@@ -134,6 +139,10 @@ while :; do
CANON="$(git config --get remote.$REMOTE.url)"
if git push -q "$CANON" "$commit:refs/heads/main" 2>/dev/null; then
echo "==> estado publicado: ${commit:0:9} sobre ${base:0:9} ($puestos fichero(s))"
+ # El ref de seguimiento al día CON LO QUE ACABAMOS DE EMPUJAR. Sin esto queda un ciclo atrás por
+ # construcción —se trae antes de empujar— y el `git log origin/main..HEAD` del final mentiría
+ # siempre. Es un ref de seguimiento, no trabajo local: se escribe sin riesgo.
+ git update-ref "refs/remotes/$REMOTE/main" "$commit" 2>/dev/null || true
# ── ESPEJOS ───────────────────────────────────────────────────────────────────────────────
# Un espejo que falla NO invalida lo publicado, así que no cambia el código de salida; pero se
# grita, porque el modo de falla conocido es que el espejo DERIVE EN SILENCIO durante días
@@ -148,14 +157,32 @@ while :; do
|| echo " ⚠ EL ESPEJO QUEDÓ ATRÁS: $url — lo publicado está en el canónico, el espejo no"
done
[ "$espejos" -gt 0 ] || echo " (este clon no tiene espejo configurado — ver scripts/espejo-setup.sh)"
- # Adelantar la rama local sólo si es un fast-forward limpio y el árbol no estorba. Si no se
- # puede, NO es un fallo: lo publicado ya está, que es lo que importa. Se dice y se sigue.
- if git merge-base --is-ancestor HEAD "$commit" 2>/dev/null; then
- git merge --ff-only -q "$commit" 2>/dev/null \
- && echo " rama local al día" \
- || echo " ⚠ la rama local no pudo adelantar (árbol con cambios) — lo publicado NO depende de eso"
- else
+ # ── ADELANTAR LA RAMA LOCAL ───────────────────────────────────────────────────────────────
+ # Lo publicado NO depende de esto: si no se puede, no es un fallo. Pero tampoco se avisa a lo
+ # bruto, porque este guion corre 48 veces al día y un ⚠ en cada ciclo deja de leerse — la misma
+ # lección que `espejo-setup.sh` acababa de aprender gritando «gh NO autenticado» con el push
+ # funcionando.
+ #
+ # ⚠ EL CASO NORMAL ES QUE EL `merge --ff-only` FALLE, Y NO INDICA NADA MALO. El generador acaba
+ # de reescribir `docs/state/` en el árbol y eso es justo lo que se publicó, así que el árbol ya
+ # tiene EL MISMO CONTENIDO que el commit nuevo. git igual se niega («local changes would be
+ # overwritten») porque su comprobación mira ÍNDICE contra HEAD, no árbol contra destino.
+ if ! git merge-base --is-ancestor HEAD "$commit" 2>/dev/null; then
echo " ⚠ la rama local está DIVERGIDA de $REMOTE/main — publicado igual; mirá: git log $REMOTE/main..HEAD"
+ elif git merge --ff-only -q "$commit" 2>/dev/null; then
+ echo " rama local al día"
+ # Si el árbol YA coincide con el destino en todo lo que el ff tocaría, el adelanto es un no-op
+ # en disco y se puede hacer moviendo la rama. Se hace con `reset --mixed`, que deja el árbol
+ # intacto y reescribe el índice —y por eso va GUARDADO por «el índice no tiene nada»: el índice
+ # es estado COMPARTIDO (regla 2) y pisar lo que otro agente dejó en `git add` es exactamente el
+ # daño que este repo ya documentó. Si hay algo stageado, no se toca y se dice.
+ elif git diff --cached --quiet HEAD 2>/dev/null \
+ && git diff --quiet "$commit" -- $(git diff --name-only HEAD "$commit") 2>/dev/null; then
+ git reset --mixed -q "$commit" \
+ && echo " rama local al día (el árbol ya tenía el contenido publicado)" \
+ || echo " la rama local queda detrás — lo publicado NO depende de eso"
+ else
+ echo " la rama local queda detrás (hay cambios propios en el árbol) — lo publicado NO depende de eso"
fi
exit 0
fi