Cómo deshacer el último commit en Git sin perder tu código
Deshacer el último commit en Git con git reset (soft, mixed, hard) o git revert. Qué pasa con tus archivos en cada caso y cuál usar según tu situación.
Acabas de hacer commit y un segundo después te das cuenta: faltaba un archivo, el mensaje está mal, o ni siquiera querías guardar eso todavía. Se te acelera el pulso. Te lo digo por adelantado para que dejes de sudar: en Git casi nada es irreversible, y deshacer el último commit son diez segundos cuando sabes qué comando escribir. El comando es lo de menos. Lo que cuesta es saber, antes de pulsar Enter, qué le va a pasar a tus archivos.
Para seguir este post solo necesitas saber hacer git add y git commit. Nada más. Vamos por partes, y al final tendrás una tabla para decidir en frío. Esto forma parte de la guía completa para deshacer cambios en Git, pero aquí nos centramos solo en el caso más urgente: el último commit.
¿Qué significa “deshacer” un commit en Git?
Un commit es una foto de tus archivos guardada con fecha, nombre y un identificador único. Cuando haces git commit, Git congela ese estado y coloca un puntero llamado HEAD apuntando a la foto más reciente. Ese puntero es la clave de todo lo que viene.
“Deshacer” el último commit significa una de dos cosas, y no son lo mismo:
- Mover el puntero hacia atrás para que Git olvide esa foto y actúe como si nunca la hubieras sacado. Eso es lo que hace
git reset. - Sacar una foto nueva que anula la anterior, dejando las dos en el historial. Eso es
git revert.
La diferencia parece un detalle. No lo es. Mover el puntero reescribe tu historia local. Crear un commit inverso la respeta y añade uno encima. Cuál de las dos te conviene depende de una sola pregunta.
Antes de tocar nada: ¿tu commit ya está subido?
Esta es la pregunta que decide todo lo demás. Antes de escribir ningún comando, respóndela: ¿ese commit sigue solo en tu ordenador, o ya hiciste git push?
Si el commit es local, o sea, todavía no lo has subido a GitHub ni a ningún remoto, tienes vía libre para reescribir tu historia con git reset. Nadie más ha visto ese commit, así que borrarlo del historial no rompe nada a nadie.
Si el commit ya está en el remoto y otras personas trabajan sobre esa rama, la cosa cambia. Reescribir un historial que tus compañeros ya tienen descargado es la forma más rápida de convertir un martes tranquilo en una tarde de conflictos. Para ese caso está git revert, que no borra nada: añade un commit que deshace el anterior.
Guárdate esta regla y el resto del post encaja solo: local, reset; ya compartido, revert. Ahora vamos con los tres sabores de reset, que se diferencian en una sola cosa: qué hacen con tu trabajo.
Deshacer el commit y conservar los cambios: git reset --soft HEAD~1
Este es el caso más común y el que te va a quitar el susto la mayoría de las veces. Hiciste commit demasiado pronto, o con un archivo de menos, y quieres rehacerlo. No quieres perder ni una línea.
# Deshace el último commit pero deja tus cambios preparados (staged)
git reset --soft HEAD~1
# Ahora tus archivos están de vuelta en el staging, listos para:
git status # verás los cambios en verde, ya añadidos
git commit -m "Mensaje correcto esta vez"
HEAD~1 significa “un commit antes del actual”. Le estás diciendo a Git: mueve el puntero un paso atrás. La palabra clave es --soft: según la documentación oficial, este modo deja tu working tree y tu índice (lo que venimos llamando staging) intactos[1]. Traducido: el commit desaparece del historial, pero todos sus cambios se quedan en el staging, en verde, esperando a que vuelvas a commitear.
Es como si Git deshiciera el git commit pero no el git add. Perfecto para recomponer un commit mal hecho sin repetir trabajo.
Sacar los cambios del staging: git reset --mixed HEAD~1
A veces no solo quieres rehacer el commit: quieres reorganizar qué va dentro. Quizá metiste dos cosas distintas en un commit y ahora las quieres separar en dos. Para eso necesitas que los cambios vuelvan, pero sin estar preparados.
# Deshace el commit y saca los cambios del staging (working directory)
git reset --mixed HEAD~1
# Equivalente, porque --mixed es el modo por defecto:
git reset HEAD~1
git status # los cambios aparecen ahora en rojo, sin preparar
--mixed es el modo que Git usa si no le dices ninguno[1]: por eso git reset HEAD~1 a secas hace exactamente esto. Mueve el puntero atrás y actualiza el índice para que coincida con el nuevo HEAD, así que nada queda preparado. Tus cambios siguen ahí, en los archivos, pero tendrás que hacer git add otra vez para elegir qué entra en el próximo commit.
La diferencia con --soft es de un solo paso: --soft te deja los cambios listos para commitear; --mixed te obliga a volver a añadirlos. Ninguno de los dos borra nada de tu trabajo. El que sí borra es el siguiente, y hay que tratarlo con respeto.
Borrar los cambios del todo: git reset --hard HEAD~1
git reset --hard HEAD~1 deshace el commit y tira a la basura todos los cambios que contenía. No vuelven al staging ni al working directory. Desaparecen de tus archivos.
# CUIDADO: esto borra el commit Y los cambios de tus archivos
git reset --hard HEAD~1
Úsalo solo cuando estés seguro de que ese trabajo no lo quieres para nada: un experimento fallido, un commit que fue un error de principio a fin. El modo --hard sobrescribe los archivos con la versión del commit destino[1], o sea, deja tu carpeta exactamente como estaba antes del commit que borras.
Aquí es donde muchos juniors se paralizan, y con razón. Pero hay una red de seguridad que casi nadie conoce el primer año: git reflog. Git lleva un diario privado de cada movimiento de HEAD, aunque el commit ya no aparezca en git log.
# Muestra el historial de movimientos de HEAD, con los commits "perdidos"
git reflog
# Verás algo así:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# e4f5g6h HEAD@{1}: commit: el commit que creías perdido
Ese e4f5g6h es el commit que borraste. Para recuperarlo, mueve el puntero de vuelta a él:
# Recupera el commit borrado usando su posición en el reflog
git reset --hard HEAD@{1}
Por defecto, Git no borra un commit huérfano hasta pasados 30 días (gc.reflogExpireUnreachable), así que el reflog te da un margen amplio antes de que el recolector de basura limpie los commits sueltos[2]. No es infinito, pero para un “acabo de hacer un --hard por error” es más que suficiente. Respira.
Deshacer un commit ya subido: git revert HEAD
Volvamos a la pregunta del principio. Si el commit ya está en el remoto y hay más gente en la rama, reset no es tu amigo: reescribir un historial que otros ya tienen provoca conflictos feos. Aquí entra git revert.
# Crea un commit NUEVO que deshace los cambios del último commit
git revert HEAD
git revert HEAD no borra nada. Según la documentación oficial, toma el último commit, invierte sus cambios y graba un commit nuevo con esa inversión[3]. El commit original sigue en el historial; encima aparece otro que lo anula. Como no reescribes nada, puedes hacer push con tranquilidad y tus compañeros solo verán un commit más, no un historial cambiado bajo sus pies.
Git te abrirá el editor para el mensaje del commit inverso. Puedes aceptar el que propone o escribir el tuyo. Si esto te suena parecido a git restore, que toca archivos sin crear commits, tienes el mapa conceptual de los tres comandos en la comparación entre git reset, revert y restore.
¿Qué comando uso? Tabla de decisión
Cuatro comandos, una sola tabla para elegir sin pensar cuando vuelvas con prisa:
| Comando | Qué pasa con el commit | Qué pasa con tus cambios | ¿Reescribe historia? | Cuándo usarlo |
|---|---|---|---|---|
git reset --soft HEAD~1 | Se elimina | Vuelven al staging, listos para commitear | Sí (solo local) | Rehacer un commit local sin perder nada. El caso más común |
git reset --mixed HEAD~1 | Se elimina | Vuelven al working directory, sin preparar | Sí (solo local) | Reorganizar qué archivos entran en el próximo commit |
git reset --hard HEAD~1 | Se elimina | Se borran por completo | Sí (solo local) | Descartar un commit local y su trabajo. Destructivo |
git revert HEAD | Se conserva | Se añade un commit inverso | No | Deshacer un commit ya subido a una rama compartida |
Si solo te equivocaste en el texto del mensaje y el contenido del commit está bien, no deshagas nada: no necesitas reset ni revert. Es más rápido cambiar el mensaje del último commit con un solo comando.
Ya tienes los cuatro comandos. Ahora lo mejor es hacerlos una vez con las manos, en un entorno seguro donde no puedas romper nada de verdad. Prueba a deshacer un commit paso a paso aquí mismo:
Cargando ejercicio...
Errores comunes
Estos son los tropiezos que veo una y otra vez en gente que empieza. Ninguno es grave si sabes reconocerlo a tiempo.
git reset --hard a lo loco
El clásico. Alguien lee “reset deshace el commit” en un foro, copia el primer comando que ve, y resulta ser --hard. Se lleva por delante cambios que no había guardado en ningún commit. Antes de escribir --hard, para y pregúntate si de verdad quieres perder ese trabajo. Y recuerda que el reflog te cubre las espaldas casi siempre.
reset sobre un commit que ya subiste a una rama compartida
Este es el que de verdad hace daño, porque el daño no es tuyo, es del equipo. Haces reset sobre un commit que tus compañeros ya tienen, tu push normal falla porque tu historial diverge del remoto, y entonces caes en la tentación de git push --force. Ese --force pisa el trabajo de todos los que estaban en la rama. Por eso existe la regla: en cuanto un commit es compartido, se deshace con revert, nunca con reset.
Confundir revert con reset
Suenan parecido y hacen cosas opuestas. reset mueve el puntero hacia atrás y reescribe historia. revert deja el historial intacto y añade un commit que anula. Si dudas cuál es cuál, quédate con esto: revert siempre suma un commit, nunca resta.
Contar mal los HEAD~n
HEAD~1 es el último commit. HEAD~2 deshace los dos últimos. Es fácil escribir HEAD~2 cuando solo querías deshacer uno y llevarte por delante un commit de más. Cuenta despacio, y si vas a usar --hard, mira antes git log --oneline para ver exactamente cuántos commits vas a mover.
# Comprueba qué commits tienes antes de deshacer nada
git log --oneline
# a1b2c3d (HEAD) Commit que quiero deshacer
# e4f5g6h Commit que quiero conservar <-- aquí quiero acabar
Antes de pulsar Enter
Una comprobación rápida que te ahorra sustos, sobre todo con reset --hard:
- Sé si el commit es local o ya está subido (esto elige entre
resetyrevert) - He mirado
git log --onelinepara contar bien losHEAD~n - Si voy a usar
--hard, tengo claro que ese trabajo no lo quiero - Sé que
git reflogpuede recuperar un commit borrado si me equivoco - En una rama compartida, uso
reverty nopush --force
Practica Git hasta que deje de darte miedo
Deshacer commits es de esas cosas que asustan hasta que las has hecho diez veces con tus propias manos. Si quieres coger soltura con reset, revert y el resto del día a día con Git sin arriesgar tu código real, en el curso Git de cero a profesional practicas cada comando paso a paso con ejercicios interactivos como el de arriba, empezando desde cero.
¿Quieres que te avise cuando salgan guías nuevas como esta? Déjame tu correo:
Un concepto nuevo cada semana
Fuentes
- Documentación oficial de git-reset: semántica exacta de
--soft,--mixed(modo por defecto) y--hardsobre HEAD, el índice y el working tree. - Documentación oficial de git-reflog: registro de los movimientos de HEAD que permite recuperar commits antes de la recolección de basura.
- Documentación oficial de git-revert: confirma que revert graba un commit nuevo con los cambios inversos y no reescribe el historial.
Preguntas Frecuentes
¿git reset --soft HEAD~1 borra mi código?
No. Es justo lo contrario: --soft deshace el commit pero deja todos sus cambios preparados en el staging, listos para que vuelvas a hacer git commit sin perder ni una línea.
¿Puedo recuperar un commit tras git reset --hard?
Casi siempre, sí. Git guarda un registro de cada posición de HEAD en el reflog, así que ejecuta git reflog, localiza el commit que creías perdido y vuelve a él con git reset --hard HEAD@{1} (o el número que corresponda). Funciona mientras el recolector de basura no haya limpiado ese commit suelto y, por defecto, Git no lo borra hasta pasados 30 días (gc.reflogExpireUnreachable)[2], así que tienes margen de sobra para reaccionar.
¿Qué diferencia hay entre reset y revert al deshacer un commit?
reset mueve el puntero HEAD hacia atrás y reescribe tu historia local, como si el commit nunca hubiera existido. revert no borra nada: crea un commit nuevo que aplica los cambios inversos y deja el original en el historial. Usa reset cuando el commit es solo local y revert cuando ya está compartido en un remoto.
¿HEAD~1 y HEAD^ son lo mismo?
Para deshacer el último commit, sí: los dos apuntan al commit anterior a HEAD, así que estas dos líneas hacen exactamente lo mismo:
git reset --soft HEAD~1
git reset --soft HEAD^
HEAD^ a secas siempre es igual que HEAD~1: el commit anterior. La diferencia solo aparece con números: HEAD~2 va dos commits atrás por la línea principal, mientras que HEAD^2 apunta al segundo padre de un merge (y solo tiene sentido en un commit de merge). Para deshacer el último commit da igual cuál uses; escoge el que te resulte más fácil de recordar.
Ya hice push, ¿puedo deshacer el commit con reset?
Depende de si trabajas solo o en equipo. Si eres la única persona en esa rama, puedes hacer reset y luego git push --force, asumiendo que reescribes el remoto. Mejor aún, usa git push --force-with-lease: es la variante segura, porque aborta el push si el remoto tiene algo que no esperabas en vez de pisarlo a ciegas. Pero si hay más gente trabajando sobre esa rama, no lo hagas: el --force pisaría su trabajo. En equipo, deshaz el commit con git revert HEAD, que es seguro porque no reescribe la historia.