Cómo deshacer cambios en Git: la guía para no perder trabajo

Guía de decisión para deshacer cambios en Git: qué comando usar según la situación (restore, reset, revert, stash) y por qué casi nada se pierde de verdad.

Cómo deshacer cambios en Git: la guía para no perder trabajo

Acabas de ejecutar un comando. La terminal no protesta, no sale ningún error rojo, todo parece de lo más normal. Y entonces lo ves: el archivo en el que llevabas dos horas ha vuelto a estar vacío, o el commit que acabas de hacer ha desaparecido, o la carpeta entera está como hace tres días. Se te cae el estómago al suelo.

Respira. En Git, casi nada se pierde de verdad.

Esa frase es el hilo que atraviesa todo lo que viene ahora. Git guarda copias de seguridad de tu trabajo en sitios que no ves, y “deshacer cambios en Git” casi nunca significa destruir algo: significa mover tu trabajo de un sitio a otro. El problema no es que Git borre cosas. El problema es que cuando algo se rompe, entras en pánico y ejecutas comandos a ciegas sin saber qué mueve cada uno. Esta guía te da lo contrario: un mapa mental para que, cuando metas la pata, sepas exactamente qué herramienta coger según la situación.

Para seguir el post necesitas saber tres cosas básicas. Un commit es una foto guardada de tu proyecto en un momento concreto. El staging (o index) es una caja donde metes los cambios que quieres incluir en la próxima foto, antes de sacarla. Y HEAD es un puntero que señala en qué commit estás ahora mismo. Si esos tres términos te suenan aunque sea de lejos, puedes seguir.

¿Dónde vive tu trabajo en Git? Las cuatro áreas

Antes de tocar un solo comando, tienes que saber dónde está tu trabajo en cada momento. Git no guarda tus cambios en un único sitio: los reparte entre cuatro áreas, y cada comando de “deshacer” mueve trabajo entre ellas. Si tienes claro el mapa, los comandos dejan de dar miedo.

La primera área es el working directory, tu carpeta de trabajo. Es el escritorio donde editas archivos con tu editor. Cuando cambias una línea y guardas el archivo, ese cambio vive aquí y solo aquí. Todavía no está en Git de ninguna forma segura.

La segunda es el staging (el index). Piensa en una caja de cartón donde vas metiendo lo que quieres que salga en la próxima foto. Cuando haces git add archivo.js, no guardas nada permanente: solo metes ese archivo en la caja. Es el paso intermedio entre “lo he editado” y “lo he guardado para siempre”.

La tercera es el repositorio local, donde viven los commits. Aquí está tu historia: cada commit es una foto permanente que Git no borra a la ligera. Cuando haces git commit, sacas la foto de lo que había en la caja y la pegas en el álbum. Esa foto se queda ahí con su propio identificador (un hash), y recuperarla más tarde es casi siempre posible.

Y hay una cuarta área de la que nadie te habla al principio: el reflog. Es un diario privado que Git lleva en tu máquina y que anota cada vez que HEAD se mueve. Cada commit, cada cambio de rama, cada reset. Es la red de seguridad que hace que la frase “casi nada se pierde” sea literalmente cierta. Le dedico una sección entera al final porque es el héroe de esta historia.

La regla que te va a salvar: deshacer casi siempre es mover trabajo entre estas áreas, no destruirlo. La gran excepción son los cambios que están solo en el working directory y nunca han pasado por un commit. Esos sí se pueden perder de verdad. Guárdate esa idea, porque vuelve una y otra vez.

Diagrama de las cuatro áreas de Git (working directory, staging, repositorio local y reflog) con los comandos que mueven el trabajo entre ellas: git add, git commit, git restore, git restore --staged, git reset --soft/--mixed/--hard y git revert
Las cuatro áreas de Git y los comandos que mueven tu trabajo entre ellas.

¿Cómo descarto los cambios de un archivo que aún no he guardado?

Para tirar a la basura los cambios de un archivo que has editado pero todavía no has añadido al staging, usa git restore <archivo>. Ese comando coge el archivo de tu working directory y lo devuelve a la última versión guardada.

# Has editado config.js y quieres tirar esos cambios sin más
git restore config.js
# Ahora config.js vuelve a como estaba en la última versión guardada

Aquí toca un aviso serio, porque esta es una de las poquísimas cosas en Git que sí borra de verdad. Cuando ejecutas git restore config.js, Git restaura el archivo desde el index (el staging). Si no habías hecho git add de ese archivo, el index coincide con tu último commit, así que el archivo vuelve al estado del último commit y tus ediciones se van para siempre. No hay commit de por medio, así que el reflog no te va a rescatar. Lo que nunca llegó a ser una foto, Git no lo guardó en ningún sitio.

El peligro se multiplica con el punto:

# El punto significa "todos los archivos del directorio actual"
git restore .
# Descarta los cambios sin guardar de TODOS los archivos. Sin red.

git restore . descarta los cambios sin guardar de todos los archivos del directorio actual y sus subcarpetas de golpe (ejecutado desde la raíz del repositorio, afecta a todo el proyecto). Es rapidísimo y es de lo poco realmente irreversible que existe en Git. Antes de ejecutarlo, para un segundo y mira git status para ver exactamente qué vas a tirar.

Si llevas años viendo tutoriales viejos, seguro que has visto git checkout -- config.js para esto mismo. Hace lo mismo que git restore, pero el equipo de Git separó las responsabilidades: checkout estaba tan sobrecargado (cambiaba de rama, restauraba archivos, movía HEAD) que era una fuente constante de errores. Desde Git 2.23, restore se encarga de restaurar archivos y switch de cambiar de rama. Usa restore: es más difícil pegarte un tiro en el pie con él.

¿Cómo saco un archivo del área de staging?

Si has hecho git add de un archivo y te arrepientes, git restore --staged <archivo> lo saca del staging sin tocar tu trabajo. Los cambios siguen intactos en tu working directory: lo único que haces es sacar el archivo de la caja antes de la foto.

git add archivo.js          # metes archivo.js en el staging
git restore --staged archivo.js   # lo sacas del staging
# Tus cambios en archivo.js SIGUEN AHÍ, solo que sin preparar

Fíjate en la diferencia con la sección anterior, porque es la que más confunde a la gente. git restore archivo.js (sin --staged) toca tu working directory y borra tus ediciones. git restore --staged archivo.js toca el staging y no borra nada: solo desestaciona. El flag --staged cambia por completo qué área se ve afectada.

El comando clásico para esto es git reset archivo.js. Verás muchísimo código y muchísimos tutoriales que lo usan, y funciona perfectamente. La propia documentación de Git dice que git reset <archivo> es lo opuesto a git add <archivo> y equivale exactamente a git restore --staged <archivo>. Los dos sacan el archivo del staging y dejan tu trabajo en paz.

Aquí conviene desactivar un miedo muy común: cuando usas reset con el nombre de un archivo, no borra nada de tu working directory. Mucha gente asocia la palabra “reset” con “destruir”, y evita el comando por eso. Pero git reset archivo.js solo desestaciona. La versión peligrosa de reset es otra, la que actúa sobre commits enteros, y llega ahora.

¿Cómo deshago un commit que acabo de hacer?

Para deshacer el último commit que has hecho en local, usa git reset apuntando al commit anterior. La forma de escribir “el commit anterior a donde estoy” es HEAD~1: HEAD es donde estás, y ~1 significa “uno hacia atrás”.

Lo que hace único a git reset es que tiene tres modos, y cada uno decide qué pasa con tu trabajo. Aquí es donde la mayoría de la gente se equivoca, así que vamos despacio.

Con --soft, Git mueve HEAD al commit anterior y deja todo lo demás intacto. El commit desaparece, pero los cambios que contenía se quedan en el staging, listos para volver a commitear. Es el modo que usas cuando el commit estaba bien pero quieres cambiarle el mensaje o juntarlo con otro.

# Deshace el último commit, pero mantiene sus cambios en el staging
git reset --soft HEAD~1
# El commit ya no está; sus cambios están preparados para re-commitear

Con --mixed, que es el modo por defecto si no pones ningún flag, Git mueve HEAD al commit anterior y además vacía el staging. Los cambios no se pierden: bajan al working directory, sin preparar. Es como si nunca hubieras hecho ni el git add ni el git commit, pero tus modificaciones siguen ahí, en tus archivos.

# --mixed es el modo por defecto: no hace falta escribirlo
git reset HEAD~1
# El commit se deshace y sus cambios vuelven al working directory sin preparar

Y con --hard, Git mueve HEAD al commit anterior y arrasa con todo lo que había por el camino: staging y working directory quedan exactamente como el commit destino. Aquí es donde la gente pierde trabajo de verdad, porque los cambios no commiteados que tuvieras en el working directory desaparecen sin pasar por ningún sitio recuperable.

# CUIDADO: descarta el commit Y los cambios sin guardar del working directory
git reset --hard HEAD~1

Esta tabla es la que deberías tener grabada. Muestra qué toca cada modo en las tres áreas que importan:

ModoHistoria (HEAD)Staging (index)Working directory
--softMueve HEAD atrásIntacto (cambios preparados)Intacto
--mixed (por defecto)Mueve HEAD atrásSe vacía (cambios sin preparar)Intacto
--hardMueve HEAD atrásSe sobrescribeSe sobrescribe (cambios perdidos)

La lectura práctica: --soft y --mixed son seguros porque tu trabajo sigue en tus archivos, solo cambia en qué área está. --hard es el único que destruye cambios no commiteados. Y aquí está el matiz que salva vidas: los commits que descartas con --hard sí se pueden recuperar por el reflog, porque fueron fotos guardadas. Lo que no se recupera son los cambios del working directory que nunca llegaron a ser un commit.

Si lo único que necesitas es deshacer el último commit y punto, tienes el caso concreto explicado paso a paso en cómo deshacer el último commit en Git.

¿Y si ya has compartido ese commit? Reset vs revert

Si el commit que quieres deshacer ya lo has subido con push a una rama que otros usan, no uses reset: usa git revert. Esta es la regla de oro del control de versiones, y romperla es de las pocas formas de crear un lío que afecta a otras personas y no solo a ti.

El motivo es este. git reset reescribe la historia: borra commits y mueve el puntero de la rama hacia atrás. Mientras eso pasa solo en tu máquina, es asunto tuyo. Pero si esos commits ya están en el remoto y un compañero se ha bajado esa rama, tu versión de la historia y la suya dejan de coincidir. La próxima vez que intentes subir tendrás que forzar el push, y cuando tu compañero haga pull, Git intentará “recuperar” los commits que tú borraste porque él todavía los tiene. Un caos de commits duplicados y conflictos que has creado tú.

git revert resuelve esto sin tocar la historia. En lugar de borrar el commit malo, crea un commit nuevo que aplica exactamente los cambios contrarios. Si el commit original añadía tres líneas, el commit de revert las quita. El commit original sigue ahí, visible en el log, y la historia crece hacia delante en vez de reescribirse hacia atrás. Por eso es seguro en ramas compartidas: nadie tiene que “recuperar” nada, todos simplemente reciben un commit más.

# Crea un commit nuevo que deshace los cambios de HEAD (el último commit)
git revert HEAD
# La historia queda intacta; se añade un commit que revierte el anterior
git resetgit revert
Qué haceMueve HEAD atrás y borra commitsCrea un commit nuevo que invierte los cambios
¿Reescribe la historia?No
¿Seguro en una rama compartida?No, rompe a tus compañeros
Cuándo usarloCommits que solo están en tu máquinaCommits que ya has subido con push

Una forma sencilla de recordarlo: reset es para tu trabajo privado, revert es para el trabajo que ya has compartido. Si tienes dudas sobre si un commit ya está fuera de tu máquina, revert es la opción que nunca hace daño. La comparación completa de los tres verbos, con más ejemplos, está en las diferencias entre reset, revert y restore.

Estos matices (qué modo de reset toca cada área, cuándo un commit ya es “público”, por qué revert crea historia en vez de borrarla) son justo lo que separa a quien teclea comandos con miedo de quien sabe lo que hace. Si quieres interiorizarlos practicando en vez de a base de sustos, el curso Git de cero a profesional te lleva por cada uno de estos escenarios de forma visual e interactiva: rompes cosas a propósito y aprendes a recuperarlas.

”Necesito cambiar de tarea ya”: aparcar trabajo con git stash

git stash guarda tus cambios a medias en un cajón aparte y te deja el working directory limpio, como si acabaras de hacer commit. No es deshacer: es aparcar. El caso típico es que estás a mitad de una función, todo desordenado, y te piden arreglar un bug urgente en otra rama. No quieres hacer un commit de código a medias, pero tampoco quieres perderlo.

git stash          # guarda tus cambios y limpia el working directory
# ...arreglas el bug urgente, haces commit...
git stash pop      # recupera tus cambios y los vuelve a poner

Cuando ejecutas git stash, Git coge lo que tienes en el working directory y en el staging, lo guarda en una pila aparte, y deja tus archivos como estaban en el último commit. Tu trabajo no se ha ido: está aparcado. Puedes cambiar de rama, hacer lo que necesites, y volver.

git stash pop hace lo contrario: coge lo último que guardaste, lo vuelve a aplicar sobre tu working directory y lo saca de la pila. Si prefieres aplicarlo sin sacarlo de la pila (por ejemplo, para llevar los mismos cambios a dos ramas), usa git stash apply en su lugar.

Puedes aparcar varias cosas y verlas todas con git stash list:

git stash list
# stash@{0}: WIP on main: 3a4f5b2 Añadir validación de formulario
# stash@{1}: WIP on main: 9cc0589 Empezar refactor del login

Un detalle importante para que no te pille por sorpresa: si al hacer git stash pop hay un conflicto (porque el código ha cambiado desde que aparcaste), Git aplica lo que puede pero no borra el stash de la pila. Te toca resolver el conflicto a mano y luego eliminarlo tú con git stash drop. Es una protección: Git no tira tu trabajo aparcado hasta estar seguro de que se ha aplicado bien.

Un concepto nuevo cada semana

La red de seguridad que lo recupera casi todo: git reflog

git reflog es un registro que Git lleva de cada movimiento de HEAD en tu repositorio, y es lo que convierte errores aparentemente catastróficos en un susto de dos minutos. Cada vez que haces un commit, cambias de rama, o ejecutas un reset, Git anota en el reflog dónde estaba HEAD antes. Es un diario privado, local a tu máquina, que casi nadie mira hasta que lo necesita de verdad.

Aquí está el escenario que da pánico. Has hecho git reset --hard HEAD~3 pensando que querías volver tres commits atrás, y de repente te das cuenta de que esos tres commits tenían trabajo bueno que ya no está por ningún lado. git log no los muestra. Parecen borrados.

No lo están. Mira el reflog:

git reflog
# 7d3c9a1 HEAD@{0}: reset: moving to HEAD~3
# a1b2c3d HEAD@{1}: commit: Terminar el endpoint de pagos
# 4e5f6a7 HEAD@{2}: commit: Añadir tests del carrito
# 8b9c0d1 HEAD@{3}: commit: Validación de stock

Cada línea es un sitio donde HEAD ha estado. El commit que creías perdido (a1b2c3d, con el endpoint de pagos) sigue ahí, con su hash intacto. Para recuperarlo, apunta tu rama de vuelta a ese commit:

# Devuelve tu rama al estado justo antes del reset
git reset --hard a1b2c3d
# Tus commits "perdidos" vuelven a estar en la historia

Y ya está. El trabajo que dabas por muerto vuelve como si nada. Si prefieres inspeccionarlo antes de comprometerte, git checkout a1b2c3d te lleva a ese commit sin mover tu rama (quedas en un estado “detached HEAD”, solo mirando), y desde ahí puedes crear una rama nueva con git branch recuperado a1b2c3d.

Este es el rescate que más tranquilidad da de todo Git, y se entiende mucho mejor haciéndolo que leyéndolo. Prueba tú mismo a recuperar trabajo tras un git reset --hard en esta simulación: provoca el “desastre” y sácalo del reflog con tus propias manos.

Cargando ejercicio...

Esto es lo que hace que la frase del principio sea literal: mientras tu trabajo haya sido un commit alguna vez, el reflog lo recuerda. Hay dos límites que conviene tener claros. El primero: el reflog es local, vive solo en tu máquina, no se comparte con push ni se clona. El segundo: las entradas no son eternas, Git las va limpiando pasado un tiempo (por defecto, del orden de meses), pero eso te deja margen de sobra para recuperar lo de hoy o lo de esta semana. La explicación completa de cómo funciona por dentro está en el reflog como red de seguridad de Git.

La única situación en la que el reflog no puede ayudarte es la que ya vimos: cambios que estaban solo en el working directory y nunca fueron un commit. Un git reset --hard que borra ediciones sin commitear, o un git restore . sobre trabajo sin guardar. Por eso el mejor hábito para no depender del rescate es hacer commits pequeños y frecuentes: cada commit es una foto que el reflog puede devolverte.

Chuleta: la situación y el comando

Esta es la tabla que puedes guardar y volver a mirar cuando algo se rompa. La idea es la misma de todo el post: identifica en qué situación estás y coge la herramienta correcta.

Tu situaciónEl comando¿Qué le pasa a tu trabajo?
Descartar cambios sin guardar de un archivogit restore <archivo>Se borran (no hay red, ten cuidado)
Sacar un archivo del staging sin perder cambiosgit restore --staged <archivo>Se conserva en el working directory
Deshacer el último commit y conservar los cambios preparadosgit reset --soft HEAD~1En el staging, listo para re-commitear
Deshacer el último commit y editar los cambiosgit reset HEAD~1En el working directory, sin preparar
Deshacer un commit y tirar todo lo asociadogit reset --hard HEAD~1Se borra (recuperable por reflog si era commit)
Deshacer un commit que ya has subido con pushgit revert <commit>Intacto; se añade un commit que lo invierte
Aparcar trabajo a medias para otra tareagit stash y luego git stash popGuardado en la pila, se recupera después
Recuperar trabajo tras un reset que creías fatalgit reflog y git reset --hard <hash>Vuelve, si alguna vez fue un commit

Fíjate en la última columna. En casi todas las filas, tu trabajo se conserva o es recuperable. Las dos únicas filas rojas son las que tocan el working directory con cosas sin commitear. Ese es el mapa entero resumido en una tabla.

Árbol de decisión para elegir el comando correcto de Git según la situación: restore para archivos, reset para commits locales, revert para commits ya compartidos con push, stash para aparcar trabajo, y reflog como red de seguridad
De la situación al comando: el árbol de decisión para deshacer en Git.

Errores comunes al deshacer (y cómo evitarlos)

Los comandos de arriba son seguros si entiendes qué mueve cada uno. Los problemas casi siempre vienen de cinco confusiones que se repiten una y otra vez.

Usar git reset --hard sin saber qué hay sin guardar

Este es el error que más trabajo hace perder de verdad. git reset --hard no distingue entre lo que quieres borrar y lo que no: arrasa con todo lo que haya en el working directory hasta el commit destino. Si tenías cambios sin commitear que no querías perder, no hay reflog que los rescate, porque nunca fueron una foto. El hábito que lo evita es de cinco segundos: mira siempre git status antes de un --hard, para ver qué tienes sin guardar. Si hay algo que quieres conservar, haz un commit o un git stash primero.

Hacer reset sobre commits que ya has compartido

Reescribir con reset una historia que ya está en el remoto rompe a todos los que tienen esa rama. Sus repositorios conservan los commits que tú borraste, y el siguiente pull los reintroduce como duplicados, con conflictos que no entienden porque no los han creado ellos. La regla es sencilla: si ya has hecho push, se deshace con revert, no con reset.

Creer que git restore . se puede deshacer

Descartar cambios del working directory que nunca guardaste es de lo poquísimo que Git no puede recuperar. git restore . es tentador porque limpia el proyecto de un golpe, pero cada archivo que tenía cambios sin commitear se pierde. Antes de ejecutarlo, git status te dice exactamente qué estás a punto de tirar. Un segundo de mirar la lista te ahorra el drama.

Ejecutar comandos a ciegas después de un susto

Cuando algo se rompe, el instinto es teclear comandos rápido a ver si alguno lo arregla. Es justo lo contrario de lo que funciona. Cada comando a ciegas puede enterrar más el problema o mover HEAD a un sitio que complica el rescate. Para. Ejecuta git status para ver dónde estás, y git reflog para ver de dónde vienes. Con esas dos fotos, casi cualquier lío tiene solución. El pánico es el que borra trabajo, no Git.

Confundir “deshacer” con “borrar”

Muchos comandos que suenan agresivos no destruyen nada. git reset --soft mantiene tus cambios preparados. git restore --staged solo desestaciona. git stash aparca sin perder. La palabra “reset” da miedo, pero solo --hard toca de verdad tu working directory. Entender que la mayoría de estos comandos mueven trabajo en vez de destruirlo es lo que te quita el miedo a usarlos.

Qué hacer cuando entras en pánico

  • Para de teclear comandos. El primer comando a ciegas suele empeorar las cosas.
  • Ejecuta git status para ver en qué área está tu trabajo ahora mismo.
  • Pregúntate: ¿lo que quiero recuperar llegó a ser un commit alguna vez?
  • Si fue un commit, ejecuta git reflog y busca el hash del estado bueno.
  • Vuelve a ese estado con git reset --hard <hash> o inspecciónalo con git checkout <hash>.
  • Si eran cambios sin commitear y sin stash, asume que se han ido y adopta commits más frecuentes.
  • Antes de cualquier --hard futuro, comprueba git status para no pisar trabajo sin guardar.

Deshacer sin miedo se aprende haciéndolo

Ya tienes el mapa: cuatro áreas, un comando por situación, y un reflog que recupera casi todo lo que alguna vez fue un commit. Con eso, la próxima vez que se te caiga el estómago al suelo vas a saber exactamente dónde mirar en lugar de teclear a ciegas.

La diferencia entre saberlo de memoria y saberlo de verdad es haberlo hecho unas cuantas veces con las manos. En el curso Git de cero a profesional trabajas cada uno de estos escenarios hasta que recuperar tu trabajo deja de ser un momento de pánico y pasa a ser un reflejo. Sales sabiendo leer el estado de tu repositorio y elegir el comando correcto sin dudar. Deja de tenerle miedo a Git y empieza a mandar tú.

Preguntas Frecuentes

¿Cuál es la diferencia entre git reset y git restore?

git restore trabaja a nivel de archivos: restaura el contenido de un archivo en tu working directory o lo saca del staging, sin tocar la historia de commits. git reset (sin nombre de archivo) trabaja a nivel de commits: mueve el puntero HEAD hacia atrás y, según el modo, ajusta el staging y el working directory. Para deshacer los cambios de un archivo concreto, restore. Para deshacer un commit entero, reset.

¿Puedo recuperar mi trabajo después de un git reset —hard?

Depende de si tu trabajo llegó a ser un commit. Si --hard descartó commits, sí:

git reflog                       # busca el hash del commit que creías perdido
git reset --hard a1b2c3d         # vuelve a ese commit

Pero si --hard borró cambios que estaban solo en el working directory y nunca commiteaste, esos no se pueden recuperar, porque el reflog solo guarda movimientos de commits, no ediciones sin guardar.

¿Cuándo uso reset y cuándo revert para deshacer un commit?

Si el commit solo existe en tu máquina y no lo has subido, usa reset. Si ya lo has compartido con push en una rama que otros usan, usa revert. La razón es que reset reescribe la historia, y reescribir una historia que otros ya tienen les rompe el repositorio; revert en cambio crea un commit nuevo que invierte los cambios sin borrar nada, así que es seguro en cualquier rama compartida.

¿git stash es lo mismo que deshacer cambios?

No. git stash aparca tu trabajo a medias y te deja el working directory limpio; git stash pop te lo devuelve tal cual. Es pausar, no descartar.

¿Por qué recomiendas git restore en lugar del viejo git checkout?

Porque git checkout hacía demasiadas cosas distintas (cambiar de rama, restaurar archivos, mover HEAD) y era fácil confundir una con otra y provocar un desastre. Desde Git 2.23 el equipo separó esas responsabilidades: git restore restaura archivos y git switch cambia de rama. git checkout -- archivo sigue funcionando, pero git restore archivo deja más claro qué estás haciendo y es más difícil equivocarse.