CommunityProgramación y desarrollogithub.com

roselletabuena/git-safe-history-rewrite

Reescribe el historial de Git sin perder trabajo ni afectar a otros: modifica, resetea, fusiona ('squash'), reordena, elimina o edita confirmaciones ('commits'), o fuerza un empuje ('force-push'). Úsalo cada vez que el usuario pida 'amend', 'squash', 'fixup', 'deshacer el último commit', 'eliminar ese commit', 'limpiar mis commits antes del PR', o forzar un push; y siempre que una tarea termine con `git push --force`. Decide si es apropiado reescribir (un commit que otros ya han 'pulled' debe revertirse, no borrarse), toma primero un punto de recuperación, evita los editores interactivos que se bloquean en una terminal no interactiva, y empuja con un 'lease' en lugar de un 'push --force' simple.

¿Qué es git-safe-history-rewrite?

Esta habilidad permite reescribir el historial de Git de forma segura, sin arriesgar trabajo ajeno ni el propio. Evalúa si la reescritura es apropiada: los commits locales o en ramas personales pueden reescribirse, pero los compartidos o publicados deben revertirse con `git revert`. Antes de tocar nada, crea una rama de respaldo (`backup/<rama>-pre-rewrite`) para poder recuperarse si algo sale mal. Proporciona comandos específicos para cada operación (amend, reset, squash, rebase no interactivo) y una verificación posterior con `git range-diff`. Finalmente, publica los cambios con `git push --force-with-lease` con la SHA esperada y evita a toda costa el `--force` desnudo. Es compatible con cualquier flujo de Git y está diseñada para asistentes como Claude Code, Cursor o Codex.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/roselletabuena/skills/tree/HEAD/plugins/git-workflows/skills/git-safe-history-rewrite

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Reescribir el historial de forma segura

Cada reescritura —commit --amend, reset, rebase, filter-repo— produce confirmaciones nuevas y mueve el puntero de la rama fuera de las antiguas. Esto es inofensivo mientras nadie más tenga las confirmaciones antiguas, y destructivo en cuanto alguien las tenga: su clon aún conserva el historial antiguo, y la próxima vez que empujen (push) o traigan (pull), reaparece como duplicados o conflictos. Por lo tanto, la primera pregunta nunca es "cómo", es "¿quién más tiene estas confirmaciones?"

1. Decidir: reescribir o revertir

git fetch --prune
git status --short --branch                  # adelantado/retrasado, y estado sucio
git branch -r --contains <commit>            # qué ramas remotas ya lo contienen
git log --oneline @{u}..HEAD                 # commits que existen solo localmente
Dónde vive el commitHaz esto
Solo local, nunca empujadoReescribe libremente.
Empujado a una rama en la que solo trabaja este usuario (su rama de PR)Reescribe, luego empuja con 'lease' (paso 5).
En una rama compartida o protegida: main, master, develop, release/*, o cualquier otra con commits de otras personas encimaNo reescribas. Usa git revert.
Ya liberado o desplegadogit revert. Una reescritura no puede desplegarlo.

Cuando el usuario dice "elimina ese commit de main", normalmente quiere que su efecto desaparezca, no su SHA. git revert <sha> hace eso, mantiene el historial honesto y solo necesita un push normal. Para un commit de fusión (merge), usa git revert -m 1 <merge-sha>. Explica por qué elegiste revertir, y si el usuario insiste en borrarlo de una rama compartida, nombra a quién afectará y obtén un sí explícito primero.

Una reversión (revert) genera conflictos cuando commits posteriores editaron las mismas líneas, que es exactamente el caso donde los commits se construyeron sobre el malo. Resuélvelo como cualquier conflicto (ver git-conflict-resolution): elimina el cambio malo, conserva el trabajo posterior, luego git revert --continue.

Una trampa de la reversión: después de revertir una fusión, fusionar la misma rama de nuevo no trae nada de vuelta, porque git piensa que esos commits ya llegaron. Para volver a traer el trabajo, revierte primero la reversión.

2. Tomar un punto de recuperación antes de tocar nada

git rev-parse HEAD                           # anótalo
git branch backup/<rama>-pre-rewrite         # una referencia real, no solo el reflog

El reflog también registra dónde estaba la rama, pero es local a este clon, expira (90 días para entradas alcanzables, 30 para las no alcanzables por defecto), y git gc después de reflog expire lo termina. Una rama de respaldo con nombre no cuesta nada. Déjala en su lugar hasta que el usuario haya confirmado el resultado.

3. Elegir el comando menos destructivo

ObjetivoComandoNotas
Corregir el mensaje del último commit o añadir un archivo olvidadogit commit --amend-m "..." o --no-edit evita abrir un editor.
Deshacer el commit pero mantener los cambios en el área de preparacióngit reset --soft HEAD~1
Deshacer el commit y sacar del área de preparación, mantener los archivosgit reset --mixed HEAD~1 (el predeterminado)
Mover una rama hacia atrás, negándose a perder cambios localesgit reset --keep <commit>Se aborta si sobrescribiría cambios sin confirmar. Prefiere esto a --hard.
Descartar también el trabajo sin confirmargit reset --hardEl trabajo sin confirmar no está en el reflog y no se puede recuperar. Solo después de confirmar que no hay nada deseado; git stash -u primero si no estás seguro.
Descartar cambios en un archivogit restore <archivo>
Fusionar ('squash') o plegar arreglos (fix-ups) en commits anterioresgit commit --fixup=<sha> luego autosquashVer abajo.
Eliminar o reordenar commitsrebase -i con un 'todo' proporcionadoVer abajo.

Un 'pre-commit hook' fallido significa que no se hizo ninguna confirmación. Ejecutar git commit --amend para "reintentar" modifica entonces el commit anterior, mezclando los nuevos cambios en el equivocado. Después de un fallo del hook, corrige el problema, vuelve a preparar los archivos (stage) y haz un nuevo commit normal.

Rebase interactivo sin editor interactivo

git rebase -i abre un editor, que se bloquea en una terminal no interactiva. No confíes en él. Dos formas que funcionan:

# Plegar commits de arreglo (fix-up) en sus objetivos - acepta el 'todo' que git genera:
git commit --fixup=<sha-objetivo>
GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash <base>

# Eliminar o reordenar: escribe el 'todo' tú mismo.
GIT_SEQUENCE_EDITOR="sed -i '/^pick <sha-corto> /d'" git rebase -i <base>

<base> es el commit anterior al primero que quieres cambiar. Si un rebase se detiene en un conflicto, resuélvelo, haz git add, luego GIT_EDITOR=true git rebase --continue. Si va mal, git rebase --abort regresa a donde empezó.

4. Verificar que la reescritura cambió lo que pretendías

Una edición de mensaje, reordenación o fusión (squash) debe dejar el árbol final idéntico:

git diff backup/<rama>-pre-rewrite HEAD --stat      # vacío = mismo contenido
git range-diff backup/<rama>-pre-rewrite...HEAD     # comparación commit por commit

Si pretendías eliminar contenido, la diferencia debe mostrar exactamente ese contenido y nada más. Cualquier cosa inesperada significa restaurar desde la copia de seguridad y rehacerlo.

5. Publicar - solo con la aprobación del usuario

Empujar una reescritura es el paso visible hacia el exterior y difícil de revertir. Confirma primero a menos que el usuario ya haya pedido el empuje.

git push --force-with-lease=<rama>:<sha-remota-esperada> --force-if-includes origin <rama>
  • Nunca --force desnudo. Sobrescribe lo que sea que esté en el remoto, incluido el empuje de un compañero que no hayas visto.
  • Nombra la SHA esperada. Un --force-with-lease simple compara contra tu referencia de seguimiento remoto, y un git fetch de fondo del IDE actualiza silenciosamente esa referencia, por lo que el lease entonces no protege nada. Usa la SHA que viste en el paso 1 (git rev-parse origin/<rama>).
  • --force-if-includes (git 2.30+) además rechaza si el remoto se movió a algo que tu rama local nunca integró.
  • Nunca fuerces el empuje en main/master/develop/release/*. Si una regla del alojamiento lo bloquea, esa regla es correcta; no la evites.

Después de empujar, informa al usuario que cualquier persona que tenga la rama antigua en su checkout debe restablecerse a la nueva en lugar de hacer pull, y que los comentarios de revisión de un PR abierto pueden perder sus anclas.

Barreras de seguridad

  • Las confirmaciones firmadas pierden su firma al ser reescritas; vuelve a firmar con -S o informa al usuario.
  • No reescribas confirmaciones de otro autor sin preguntar, incluso en una rama de características.
  • Las ramas apiladas por encima de la reescrita ahora están obsoletas. Usa git-stacked-pr-rebase para reordenarlas en lugar de hacer rebase de cada una manualmente.
  • Un secreto confirmado no se soluciona con una reescritura. Rota la credencial primero; asume que está comprometida.
  • Informa claramente: cuál era el antiguo tope, cuál es el nuevo, dónde está la referencia de respaldo, y si hay algo sin empujar. Si la reversión fue la decisión correcta, di que eso es lo que hiciste.

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Skills relacionados