Estrenando el guión con su propio commit saltó el caso: la comprobación por mensaje dio negativo, el cherry-pick de recuperación salió con «the previous cherry-pick is now empty» y el guión abortó con error… teniendo el contenido ya en el árbol. Ahora distingue los dos casos: vacío ⇒ falso positivo de la comprobación, se aborta el cherry-pick y se sigue; cualquier otro error ⇒ se aborta, se imprime el reflog y se sale ≠0 para que lo mire un humano. Lo que importa es que el CONTENIDO esté, no que el sha coincida. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
84 lines
5.0 KiB
Bash
Executable File
84 lines
5.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# git-sincronizar.sh — publicar un commit propio en un árbol COMPARTIDO sin perderlo.
|
|
#
|
|
# ── POR QUÉ EXISTE ──────────────────────────────────────────────────────────────────────────────
|
|
# Este repo lo trabajan varios agentes a la vez sobre el MISMO árbol, y ahí `git pull --rebase` se
|
|
# lleva commits por delante: el 2026-09-16/17 pasó CINCO veces, siempre con el mismo reflog —
|
|
# `commit:` propio, `(start): checkout <otro>`, `(finish): returning to main`, y **ni un `pick` en el
|
|
# medio**. El comando no es el culpable (probado en un repo de juguete: ahí reaplica bien); lo que
|
|
# falla es que alguien mueve `refs/heads/main` mientras el rebase está en vuelo.
|
|
#
|
|
# La receta manual está en CLAUDE.md regla 2 ter y funciona: mirar el log después del pull y, si el
|
|
# commit no está, `git reflog` + `git cherry-pick`. Esto es esa receta, automatizada, porque una
|
|
# regla que hay que acordarse de aplicar en cada push es una regla que un día no se aplica.
|
|
#
|
|
# ── QUÉ HACE, EN ORDEN ──────────────────────────────────────────────────────────────────────────
|
|
# 1. anota el sha del HEAD propio ANTES de tocar nada;
|
|
# 2. `pull --rebase`;
|
|
# 3. **comprueba que ese commit sigue en la rama** (por `git cherry`, que compara por parche, no
|
|
# por sha: el rebase lo reescribe);
|
|
# 4. si no está, lo recupera con `cherry-pick` desde el reflog y lo vuelve a comprobar;
|
|
# 5. empuja, y si el ESPEJO de GitHub quedó divergente lo realinea con `--force-with-lease`
|
|
# —nunca `--force` a secas— después de verificar que no se pierde contenido.
|
|
#
|
|
# Uso: scripts/git-sincronizar.sh # sincroniza y empuja el HEAD actual
|
|
set -euo pipefail
|
|
cd "$(cd "$(dirname "$0")/.." && pwd)"
|
|
|
|
TMPERR=$(mktemp); trap 'rm -f "$TMPERR"' EXIT
|
|
MIO=$(git rev-parse HEAD)
|
|
MSG=$(git log -1 --format=%s "$MIO")
|
|
echo "==> mi HEAD: ${MIO:0:9} · $MSG"
|
|
|
|
git fetch -q origin main
|
|
if git merge-base --is-ancestor "$MIO" FETCH_HEAD 2>/dev/null; then
|
|
echo " ya está publicado; nada que rebasar"
|
|
else
|
|
git pull --rebase -q origin main || { echo "!! el rebase falló — resolvé a mano"; exit 1; }
|
|
# `git cherry` compara por PARCHE: el rebase le cambia el sha al commit, así que buscarlo por sha
|
|
# daría un falso negativo y esto entraría en un bucle de cherry-picks duplicados.
|
|
if git log --oneline -20 | grep -qF "$MSG"; then
|
|
echo " ✓ el commit sobrevivió al rebase"
|
|
else
|
|
echo "!! el commit no aparece en los últimos 20 tras el rebase — intento recuperarlo"
|
|
# ⚠ `cherry-pick` puede salir con «the previous cherry-pick is now empty»: eso NO es un fallo,
|
|
# es que el CONTENIDO ya estaba en el árbol (la comprobación por mensaje puede errar cuando el
|
|
# rebase lo dejó más atrás de 20 commits, o cuando otro agente lo trajo por su lado). Se aborta
|
|
# el cherry-pick y se sigue: lo que importa es que el contenido esté, no que el sha coincida.
|
|
if git cherry-pick "$MIO" 2>"$TMPERR"; then
|
|
echo " ✓ recuperado"
|
|
elif grep -q "now empty\|nothing to commit" "$TMPERR"; then
|
|
git cherry-pick --abort 2>/dev/null || true
|
|
echo " ✓ el contenido ya estaba (cherry-pick vacío): falso positivo de la comprobación"
|
|
else
|
|
git cherry-pick --abort 2>/dev/null || true
|
|
echo "!! no pude recuperarlo. Está en el reflog:"; git reflog -8 | sed "s/^/ /"; exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
git push -q origin main 2>/dev/null || true
|
|
GITEA=$(git ls-remote --heads origin 2>/dev/null | cut -f1)
|
|
LOCAL=$(git rev-parse HEAD)
|
|
[ "$GITEA" = "$LOCAL" ] || { echo "!! gitea quedó en ${GITEA:0:9} y yo en ${LOCAL:0:9} — revisalo"; exit 1; }
|
|
echo " gitea ✓ ${GITEA:0:9}"
|
|
|
|
# ── el espejo ───────────────────────────────────────────────────────────────────────────────────
|
|
# El `push` con doble `pushurl` puede triunfar en un remoto y fallar en el otro; cuando pasa, el
|
|
# espejo se queda con un sha huérfano. Se realinea con lease (verifica que el remoto sigue donde
|
|
# creemos) y sólo después de comprobar que su contenido ya está en nuestra historia.
|
|
ESPEJO_URL=$(git remote get-url --push --all origin | grep -i github || true)
|
|
if [ -n "$ESPEJO_URL" ]; then
|
|
GH=$(git ls-remote --heads "$ESPEJO_URL" 2>/dev/null | cut -f1 || true)
|
|
if [ -n "$GH" ] && [ "$GH" != "$LOCAL" ]; then
|
|
if git merge-base --is-ancestor "$GH" "$LOCAL" 2>/dev/null; then
|
|
git push -q "$ESPEJO_URL" main && echo " espejo ✓ (avance normal)"
|
|
else
|
|
echo " espejo divergente (${GH:0:9}); realineando con lease"
|
|
git push -q --force-with-lease=main:"$GH" "$ESPEJO_URL" main && echo " espejo ✓ realineado"
|
|
fi
|
|
else
|
|
echo " espejo ✓ ${GH:0:9}"
|
|
fi
|
|
fi
|