Cómo Deshacer Commits Locales y Remotos en Git de Forma Segura

Deshaz commits de Git de forma segura con reset, revert, reflog y force-with-lease sin perder trabajo ni romper ramas compartidas.

Cómo Deshacer Commits Locales y Remotos en Git de Forma Segura

Deshacer un commit en Git es fácil. Deshacer lo correcto, sin perder trabajo o sorprender a todos los demás en la rama, es la parte que requiere criterio.

La primera pregunta no es "¿qué comando ejecuto?" Es "¿quién ha visto este commit?" Si el commit existe solo en tu laptop, normalmente puedes reescribirlo con git reset o git commit --amend. Si el commit ya se ha enviado a una rama que otras personas usan, prefiere git revert. Esto mantiene el historial intacto y crea un nuevo commit que revierte el cambio malo.

Antes de tocar el historial, toma una instantánea rápida de dónde estás:

git status
git branch backup-before-undo
git log --oneline --decorate -5

Esa rama temporal es un seguro barato. Si reseteas demasiado lejos o cambias de opinión, el commit antiguo aún tiene un nombre.

Si el commit no se ha enviado

Para un commit local que no has enviado, git reset suele ser la herramienta más limpia. Mueve el puntero de tu rama hacia atrás. El modo que elijas decide qué sucede con los archivos.

Usa --soft cuando el mensaje del commit fue incorrecto u olvidaste un archivo pequeño:

git reset --soft HEAD~1

Tu último commit desaparece, pero los cambios permanecen en el área de preparación. Puedes agregar el archivo faltante y hacer commit de nuevo:

git add missing-file.yml
git commit -m "Actualizar configuración de despliegue"

Usa el reset mixto por defecto cuando quieras que los cambios vuelvan a tu árbol de trabajo, sin preparar:

git reset HEAD~1

Ese es el comando cotidiano de "deshacer este commit, pero mantener mis ediciones". Es útil cuando un commit debería convertirse en dos commits más pequeños, o cuando hiciste commit de una declaración de depuración junto con código real.

Usa --hard solo cuando realmente quieras descartar los cambios locales:

git reset --hard HEAD~1

Esto restablece los archivos rastreados al commit anterior. No pregunta cortésmente si lo decías en serio. Si tienes trabajo no comprometido en archivos rastreados, puede desaparecer del árbol de trabajo. Verifica git status primero, y guarda en stash o crea una rama con cualquier cosa que puedas querer recuperar.

Para una pequeña corrección en el commit local más reciente, git commit --amend suele ser mejor que reset:

git add corrected-file.js
git commit --amend

Eso reemplaza el último commit con uno nuevo. La misma regla aplica: modifica libremente antes de enviar; ten cuidado después de enviar.

Si el commit ya se ha enviado

En una rama compartida, usa git revert a menos que tengas una razón sólida para reescribir el historial. Revert crea un nuevo commit que aplica el parche opuesto.

git revert a1b2c3d

Ese comando abre tu editor con un mensaje generado. Guárdalo, y Git crea un nuevo commit. El commit original permanece en el historial, que es exactamente lo que quieres en main, master, develop, ramas de lanzamiento, y cualquier rama que los compañeros de equipo puedan haber descargado.

Si necesitas revertir varios commits consecutivos, puedes hacerlo en un lote preparado:

git revert --no-commit HEAD~3..HEAD
git status
git commit -m "Revertir cambios recientes de despliegue"

Lee el rango cuidadosamente. HEAD~3..HEAD significa los últimos tres commits, sin incluir HEAD~3 en sí. En caso de duda, lista los commits primero:

git log --oneline HEAD~3..HEAD

Los commits de fusión necesitan una decisión extra. Una fusión tiene más de un padre, por lo que Git necesita saber qué lado debe tratarse como la línea principal:

git revert -m 1 <sha-del-commit-de-fusión>

La mayoría de los equipos usan -m 1 al revertir una fusión de rama de característica desde la rama objetivo, pero no ejecutes esto a ciegas. Mira el commit de fusión con git show --summary <sha> y confirma el orden de los padres.

Si enviaste algo incorrecto y debes eliminarlo

A veces revert no es suficiente. Si enviaste un secreto, un binario enorme o datos privados de clientes, un revert solo lo elimina del árbol más reciente. El contenido sensible aún existe en el historial. Eso se convierte en un problema de respuesta a incidentes, no solo de limpieza de Git.

Para secretos, rota la credencial primero. Luego elimina los datos del historial con una herramienta adecuada de reescritura de historial como git filter-repo, BFG Repo-Cleaner, o un proceso específico de eliminación de secretos del host. Coordina con el propietario del repositorio y asume que cualquier persona con acceso podría haber descargado el commit malo antes de que lo eliminaras.

Para un commit erróneo normal en tu propia rama de característica, reescribir la rama remota puede ser aceptable. Resetea localmente, luego haz push forzado con un lease:

git reset --hard HEAD~1
git push --force-with-lease origin my-feature-branch

--force-with-lease es la forma más segura porque se niega a actualizar el remoto si alguien más envió trabajo nuevo desde tu última descarga. Sigue siendo un push forzado. Sigue reescribiendo la rama. Úsalo en ramas de característica personales o coordinadas, no casualmente en ramas compartidas.

Un buen hábito antes de hacer push forzado es:

git fetch origin
git log --oneline --left-right --graph origin/my-feature-branch...my-feature-branch

Eso muestra lo que existe solo en el remoto y lo que existe solo localmente. Si el lado izquierdo contiene commits de otra persona, detente y habla con ellos.

Recuperación con reflog

git reflog es el comando que salva muchas tardes malas. Registra dónde han apuntado recientemente tu HEAD local y las referencias de rama. Si reseteaste al lugar equivocado, eliminaste una rama o modificaste el commit incorrecto, reflog a menudo conoce el SHA del commit antiguo.

git reflog

Podrías ver algo como:

7cc8a91 HEAD@{0}: reset: moving to HEAD~1
2b41f0d HEAD@{1}: commit: add retry around deploy step

Para recuperar el commit antiguo, crea una rama en él:

git branch recovered-deploy-work 2b41f0d

Prefiero crear una rama en lugar de ejecutar inmediatamente otro hard reset. Te da un identificador estable, y puedes inspeccionar el trabajo recuperado con calma:

git show recovered-deploy-work
git switch recovered-deploy-work

Reflog es local. El reflog de tu compañero de equipo no contendrá tus commits locales perdidos, y los hosts remotos pueden no exponer el mismo camino de recuperación. También se poda con el tiempo según la configuración de recolección de basura de Git, así que trátalo como una red de seguridad reciente, no como almacenamiento de archivo.

Una guía práctica de decisión

Si hiciste commit demasiado pronto y no has enviado, usa git reset --soft HEAD~1 o git reset HEAD~1.

Si hiciste commit del archivo incorrecto y quieres que las ediciones desaparezcan localmente, usa git reset --hard HEAD~1 solo después de verificar git status.

Si el commit malo ya está en una rama compartida, usa git revert <sha>.

Si tu rama de característica es remota pero solo tú la usas, git reset más git push --force-with-lease suele ser aceptable.

Si el commit contiene un secreto o datos sensibles, no confíes en revert. Rota el secreto, reescribe el historial con la herramienta adecuada y coordina la limpieza.

El flujo de trabajo más seguro para deshacer en Git es aburrido: inspecciona primero, haz una rama de respaldo, elige el comando basado en si el commit está compartido, y usa reflog cuando necesites recuperarte de tu propio intento de recuperación.