tawasuyu

sergio/takana

> 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…

TOML 1430Shell 145Markdown 87Rust 852973 Sutichaykuna

⎇ 7c794568a963b18b68d747959e6bbd07cf2ebff7 scripts/git-sincronizar.sh Shell · 90 siq'ikuna · 5.7 KB Ñawpa willay ch'uya

git-sincronizar: gritaba «el commit desapareció» SIEMPRE — era `pipefail` + SIGPIPE, no el rebase 597406ad7 · Sergio ·

1#!/usr/bin/env bash
2# git-sincronizar.sh — publicar un commit propio en un árbol COMPARTIDO sin perderlo.
3#
4# ── POR QUÉ EXISTE ──────────────────────────────────────────────────────────────────────────────
5# Este repo lo trabajan varios agentes a la vez sobre el MISMO árbol, y ahí `git pull --rebase` se
6# lleva commits por delante: el 2026-09-16/17 pasó CINCO veces, siempre con el mismo reflog —
7# `commit:` propio, `(start): checkout <otro>`, `(finish): returning to main`, y **ni un `pick` en el
8# medio**. El comando no es el culpable (probado en un repo de juguete: ahí reaplica bien); lo que
9# falla es que alguien mueve `refs/heads/main` mientras el rebase está en vuelo.
10#
11# La receta manual está en CLAUDE.md regla 2 ter y funciona: mirar el log después del pull y, si el
12# commit no está, `git reflog` + `git cherry-pick`. Esto es esa receta, automatizada, porque una
13# regla que hay que acordarse de aplicar en cada push es una regla que un día no se aplica.
14#
15# ── QUÉ HACE, EN ORDEN ──────────────────────────────────────────────────────────────────────────
16#   1. anota el sha del HEAD propio ANTES de tocar nada;
17#   2. `pull --rebase`;
18#   3. **comprueba que ese commit sigue en la rama** (por `git cherry`, que compara por parche, no
19#      por sha: el rebase lo reescribe);
20#   4. si no está, lo recupera con `cherry-pick` desde el reflog y lo vuelve a comprobar;
21#   5. empuja, y si el ESPEJO de GitHub quedó divergente lo realinea con `--force-with-lease`
22#      —nunca `--force` a secas— después de verificar que no se pierde contenido.
23#
24# Uso:  scripts/git-sincronizar.sh          # sincroniza y empuja el HEAD actual
25set -euo pipefail
26cd "$(cd "$(dirname "$0")/.." && pwd)"
27
28TMPERR=$(mktemp); trap 'rm -f "$TMPERR"' EXIT
29MIO=$(git rev-parse HEAD)
30MSG=$(git log -1 --format=%s "$MIO")
31echo "==> mi HEAD: ${MIO:0:9} · $MSG"
32
33git fetch -q origin main
34if git merge-base --is-ancestor "$MIO" FETCH_HEAD 2>/dev/null; then
35  echo "    ya está publicado; nada que rebasar"
36else
37  git pull --rebase -q origin main || { echo "!!  el rebase falló — resolvé a mano"; exit 1; }
38  # `git cherry` compara por PARCHE: el rebase le cambia el sha al commit, así que buscarlo por sha
39  # daría un falso negativo y esto entraría en un bucle de cherry-picks duplicados.
40  # ⚠ NADA DE `git log … | grep -q` ACÁ, y la causa es sutil: este script corre con `set -o
41  # pipefail`, y `grep -q` SALE EN CUANTO ENCUENTRA — lo que le manda SIGPIPE a `git log`, que
42  # termina ≠0, y con `pipefail` la tubería entera se lee como FALLO **aunque el grep haya
43  # encontrado**. Resultado: el guión anunciaba «EL COMMIT DESAPARECIÓ» en cada corrida, se iba a
44  # recuperarlo, y el `cherry-pick` le decía que ya estaba. Un guardián que grita siempre se ignora.
45  # La salida se captura primero y se compara después: sin tubería, sin SIGPIPE, sin falso positivo.
46  RECIENTES=$(git log --format=%s -20)
47  if printf '%s\n' "$RECIENTES" | grep -qF "$MSG"; then
48    echo "    ✓ el commit sobrevivió al rebase"
49  else
50    echo "!!  el commit no aparece en los últimos 20 tras el rebase — intento recuperarlo"
51    # ⚠ `cherry-pick` puede salir con «the previous cherry-pick is now empty»: eso NO es un fallo,
52    # es que el CONTENIDO ya estaba en el árbol (la comprobación por mensaje puede errar cuando el
53    # rebase lo dejó más atrás de 20 commits, o cuando otro agente lo trajo por su lado). Se aborta
54    # el cherry-pick y se sigue: lo que importa es que el contenido esté, no que el sha coincida.
55    if git cherry-pick "$MIO" 2>"$TMPERR"; then
56      echo "    ✓ recuperado"
57    elif grep -q "now empty\|nothing to commit" "$TMPERR"; then
58      git cherry-pick --abort 2>/dev/null || true
59      echo "    ✓ el contenido ya estaba (cherry-pick vacío): falso positivo de la comprobación"
60    else
61      git cherry-pick --abort 2>/dev/null || true
62      echo "!!  no pude recuperarlo. Está en el reflog:"; git reflog -8 | sed "s/^/      /"; exit 1
63    fi
64  fi
65fi
66
67git push -q origin main 2>/dev/null || true
68GITEA=$(git ls-remote --heads origin 2>/dev/null | cut -f1)
69LOCAL=$(git rev-parse HEAD)
70[ "$GITEA" = "$LOCAL" ] || { echo "!!  gitea quedó en ${GITEA:0:9} y yo en ${LOCAL:0:9} — revisalo"; exit 1; }
71echo "    gitea ✓ ${GITEA:0:9}"
72
73# ── el espejo ───────────────────────────────────────────────────────────────────────────────────
74# El `push` con doble `pushurl` puede triunfar en un remoto y fallar en el otro; cuando pasa, el
75# espejo se queda con un sha huérfano. Se realinea con lease (verifica que el remoto sigue donde
76# creemos) y sólo después de comprobar que su contenido ya está en nuestra historia.
77ESPEJO_URL=$(git remote get-url --push --all origin | grep -i github || true)
78if [ -n "$ESPEJO_URL" ]; then
79  GH=$(git ls-remote --heads "$ESPEJO_URL" 2>/dev/null | cut -f1 || true)
80  if [ -n "$GH" ] && [ "$GH" != "$LOCAL" ]; then
81    if git merge-base --is-ancestor "$GH" "$LOCAL" 2>/dev/null; then
82      git push -q "$ESPEJO_URL" main && echo "    espejo ✓ (avance normal)"
83    else
84      echo "    espejo divergente (${GH:0:9}); realineando con lease"
85      git push -q --force-with-lease=main:"$GH" "$ESPEJO_URL" main && echo "    espejo ✓ realineado"
86    fi
87  else
88    echo "    espejo ✓ ${GH:0:9}"
89  fi
90fi