Volver a un commit anterior en Git sin perder trabajo
Cómo volver a un commit anterior en Git según tu intención: inspeccionar con checkout, mover tu rama con reset o deshacer con revert, sin perder trabajo.
Acabas de hacer un commit que no querías. O tres. O simplemente necesitas ver cómo estaba el proyecto hace una semana, antes de aquel refactor que lo tocó todo. La pregunta que teclearás en el buscador es “cómo volver a un commit anterior git”, pero la pregunta real no es qué comando escribes, es qué quieres hacer con el pasado: mirarlo, mover tu rama hasta él, o deshacer un cambio concreto. Elegir mal esa intención es lo que hace que la gente pierda un día de trabajo con un reset --hard a destiempo.
¿Qué significa realmente “volver” a un commit?
“Volver” abarca tres operaciones muy distintas que la gente mete en el mismo saco. Para elegir bien conviene tener claro cómo se sostiene la historia por dentro, aunque sea a grandes rasgos.
Tu HEAD normalmente apunta a una rama (main, pongamos). Esa rama apunta a un commit, y cada commit apunta a su padre, formando una cadena hacia atrás. Eso es el famoso DAG: un grafo de commits encadenados por sus padres. Cuando dices que quieres “volver”, puedes estar pidiendo cosas incompatibles entre sí.
| Quiero… | Herramienta | Qué le pasa a tu trabajo |
|---|---|---|
| Ver cómo estaba el proyecto en un commit | git checkout <commit> / git switch --detach | Nada. Es solo mirar. |
| Mover mi rama local a un commit anterior | git reset (eliges el modo) | Depende del modo que uses |
| Deshacer un commit que ya está compartido con el equipo | git revert | Se crea un commit nuevo; la historia anterior se conserva intacta |
| Recuperar algo después de un susto | git reflog | Vuelves al punto justo antes del error |
El resto del post es cada una de esas filas en detalle, con el comando exacto y con cómo proteger tu trabajo en cada caso. Este artículo es una pieza de la guía para deshacer cambios en Git, donde tienes el mapa completo de todas las formas de retroceder.
Mirar el pasado sin tocar nada: git checkout y git switch --detach
Si solo quieres inspeccionar cómo estaba el código en un commit, git switch --detach <commit> (o el clásico git checkout <commit>) te lleva ahí sin mover ninguna rama. Entras en un estado que se llama detached HEAD: tu HEAD apunta directamente a un commit concreto en vez de apuntar a una rama. Si quieres el detalle completo de este estado y por qué desconcierta tanto, tienes la explicación aparte de qué es el detached HEAD en Git.
En detached HEAD puedes leer archivos, compilar, ejecutar los tests, hacer git log para entender qué pasó. Es una visita al museo. No estás sobre ninguna rama, así que nada de lo que hagas afecta a main ni a tu feature.
# Inspeccionar cómo estaba el proyecto en un commit concreto
git switch --detach a1b2c3d # equivalente: git checkout a1b2c3d
# ...miras, compilas, ejecutas los tests. Nada de esto toca tus ramas.
# Volver a donde estabas
git switch - # vuelve a la rama anterior
# o directamente por nombre
git switch main
¿Y si mirando el pasado decides que ese punto merece continuar como una línea de trabajo propia? Lo conviertes en rama antes de irte:
# Convertir la visita en una rama de verdad
git switch -c experimento-viejo
Aquí está la trampa que pilla a mucha gente: si haces commits en detached HEAD y luego cambias de rama sin haber creado una, esos commits quedan huérfanos. No están en ninguna rama, así que dejan de aparecer en git log y da la sensación de que se han evaporado. No están perdidos del todo (más adelante verás que reflog los rescata), pero es un susto innecesario. La regla es simple: si vas a trabajar en serio, sal del museo con git switch -c antes de teclear nada.
Antes de seguir, pruébalo tú. Este simulador te deja moverte por el grafo de commits y ver a dónde apunta HEAD en cada paso, sin riesgo de romper nada real:
Cargando ejercicio...
Mover tu rama al pasado: git reset y sus tres modos
git reset mueve el puntero de tu rama actual (y con él HEAD) a otro commit. Esa es la parte que todos los modos comparten. La diferencia entre --soft, --mixed y --hard está en qué hacen con las otras dos zonas de Git: el índice (lo que tienes preparado para el próximo commit, el staging area) y el árbol de trabajo (tus archivos tal y como están en disco).
--softmueve el puntero y no toca nada más. El índice y el árbol de trabajo quedan intactos, así que todos los cambios de los commits que has “deshecho” siguen ahí, preparados [1]. Es lo que usas para rehacer los últimos commits en uno solo.--mixedes el modo por defecto. Mueve el puntero, actualiza el índice para que coincida con el nuevoHEAD(nada queda preparado) y deja el árbol de trabajo como estaba [1]. Tus cambios siguen en disco, pero sin preparar.--hardmueve el puntero y sobrescribe el índice y el árbol de trabajo para que coincidan con el commit destino [1]. Es el único modo destructivo: los cambios que no tenías commiteados se pierden.
# reset mueve el puntero de tu rama (y HEAD)
git reset --soft HEAD~1 # deshace el último commit, deja los cambios preparados
git reset HEAD~1 # --mixed (por defecto): los deja sin preparar
git reset --hard HEAD~1 # descarta también lo que tenías en el árbol de trabajo
Esta tabla es la que conviene tener grabada antes de escribir un reset:
| Modo | Puntero (rama + HEAD) | Índice | Árbol de trabajo |
|---|---|---|---|
--soft | Se mueve | Intacto | Intacto |
--mixed (por defecto) | Se mueve | Se resetea | Intacto |
--hard | Se mueve | Se resetea | Se resetea (aquí pierdes trabajo) |
Fíjate en que solo la última fila borra algo tuyo. --soft y --mixed son reversibles casi sin esfuerzo porque tus cambios siguen en el árbol de trabajo. --hard es el que da miedo, y con razón.
Cómo no perder trabajo antes de un reset --hard
La forma de sobrevivir a un reset --hard es asegurar el trabajo antes de ejecutarlo, no rezar después. Tienes dos maneras sencillas, y elegir una cuesta cinco segundos frente a las horas que cuesta recuperar lo que no guardaste.
La primera es git stash, que guarda tus cambios sin commitear en una pila aparte y te deja el árbol de trabajo limpio. Reseteas con tranquilidad y luego recuperas lo guardado cuando quieras.
# Opción A: guardar los cambios sin commitear
git stash push -m "wip antes de resetear"
git reset --hard HEAD~3
git stash pop # recupera tus cambios encima del nuevo estado
La segunda, y la que uso cuando el estado actual me importa de verdad, es dejar una rama plantada en el punto donde estás antes de mover nada. Una rama es solo un puntero a un commit, así que crearla no cuesta nada y te deja una vía de vuelta explícita.
# Opción B: dejar una rama de respaldo en el estado actual
git branch respaldo # 'respaldo' queda apuntando a donde estás ahora
git reset --hard HEAD~3 # tu rama retrocede; 'respaldo' sigue en el punto de antes
Si el reset sale mal, git switch respaldo te devuelve exactamente a donde estabas. La regla que no me salto: nunca un reset --hard con trabajo sin guardar que me importe.
Deshacer un commit en historia compartida: git revert
git revert <commit> crea un commit nuevo que aplica los cambios inversos de otro, sin borrar nada de la historia [2]. Si un commit añadió tres líneas, el revert añade un commit que las quita. La historia crece hacia delante en vez de reescribirse hacia atrás.
Esa diferencia es la razón por la que en ramas compartidas (main, o cualquier rama que ya hayas empujado y que otros usan) se usa revert y no reset. Un reset reescribe la historia de la rama: los commits que había después del punto destino desaparecen de ella. En tu máquina no pasa nada, pero para volver a subir esa rama al remoto tendrías que hacer un push --force, y eso pisa el historial que tus compañeros ya tienen. La próxima vez que hagan pull, se les rompe. Con revert nadie tiene que forzar nada porque solo estás sumando un commit más.
# Crea un commit nuevo que invierte los cambios de otro
git revert a1b2c3d
El caso especial son los merge commits. Un merge tiene dos padres, así que Git no sabe cuál de las dos ramas es la “línea principal” que quieres conservar. Se lo dices con -m y el número del padre (empezando por 1) [2]:
# Revertir un merge: -m 1 indica que el padre 1 es la línea principal
git revert -m 1 <merge-commit>
Ten en cuenta un matiz: revertir un merge le dice a Git que no quieres esos cambios nunca más, y eso complica volver a fusionar esa rama después. Pero para el caso común, deshacer un commit ya publicado, revert es la respuesta correcta.
La red de seguridad: git reflog
git reflog registra cada movimiento de HEAD en tu repositorio local: cada commit, cada cambio de rama, cada reset. Es la razón por la que casi nada se pierde de verdad en Git, ni siquiera después de un reset --hard que creías catastrófico. El commit al que apuntabas sigue existiendo un tiempo, y el reflog guarda su referencia.
# Cada movimiento de HEAD queda registrado
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# 9f8e7d6 HEAD@{1}: commit: la feature que creías perdida
# Volver al estado justo antes del reset
git reset --hard HEAD@{1}
# O rescatarlo a una rama nueva sin tocar la rama actual
git switch -c rescate HEAD@{1}
Cada línea es un punto al que puedes regresar con HEAD@{n}. Si te equivocaste hace dos movimientos, HEAD@{2} es tu ancla.
Dos avisos importantes para no confiarte de más. El reflog es local: vive en tu .git, no se empuja ni se comparte, así que no te salva de un problema en otra máquina. Y tiene caducidad: el recolector de basura de Git (gc) va limpiando entradas y objetos inalcanzables pasado un tiempo (por defecto, semanas). Rescatar algo del reflog es cuestión de horas o días, no de meses.
Errores comunes
reset --hard con trabajo sin guardar. Es el clásico “perdí un día de trabajo”. El reset destructivo no distingue entre lo que querías tirar y lo que estabas a medio escribir. Si tienes cambios en el árbol de trabajo que importan, stash o rama de respaldo antes, siempre.
reset en una rama ya empujada seguido de push --force. Aquí no te haces daño a ti, se lo haces al equipo. Reescribes la historia de una rama compartida, fuerzas el push, y el siguiente pull de tus compañeros entra en conflicto con un historial que ya no existe. En ramas compartidas: revert.
Commits en detached HEAD que desaparecen. Trabajas un rato inspeccionando un commit antiguo, haces un par de commits sin darte cuenta de que no estás sobre ninguna rama, cambias a main y tu trabajo ya no aparece en git log. No entres en pánico: están en el reflog. Pero evítalo de raíz saliendo con git switch -c en cuanto vayas a escribir.
Confundir revert con reset. Suenan parecido y hacen cosas opuestas. reset mueve tu puntero hacia atrás y reescribe la historia local; revert añade un commit hacia delante y la conserva. Si dudas de cuál usar, la pregunta que lo resuelve: ¿esta rama la tiene alguien más? Si sí, revert. Y si quieres afinar cuándo entra en juego un tercer comando, git restore, tienes el desglose de las diferencias entre reset, revert y restore.
Cómo decidir en cinco segundos
Cuando estés delante del terminal con prisa, esta es la regla mental que lo resuelve:
- Quiero mirar el pasado →
git checkout <commit>ogit switch --detach. Solo lectura, vuelves congit switch -. - Quiero mover mi rama local hacia atrás →
git reset. Elige--soft(mantiene preparado),--mixed(mantiene sin preparar) o--hard(destructivo, asegura antes). - Quiero deshacer un commit que ya comparto →
git revert. Commit nuevo, sin forzar nada. - La he liado y necesito recuperar →
git reflog, localizaHEAD@{n}y vuelve.
Decide primero qué quieres hacer con el pasado y solo entonces elige el comando: ese orden separa una vuelta atrás limpia de una tarde reconstruyendo lo que no debiste borrar.
Si quieres dejar de improvisar con Git y practicar la navegación por el DAG, los tres modos de reset y el revert sobre un repo de verdad, guiado paso a paso, eso es justo lo que trabajas en el curso Git de cero a profesional. Ejercicios interactivos, sin miedo a romper nada.
Un concepto nuevo cada semana
Fuentes
- git reset (documentación oficial de Git): comportamiento exacto de los modos
--soft,--mixed(por defecto) y--hardsobre HEAD, el índice y el árbol de trabajo. - git revert (documentación oficial de Git): revert crea commits nuevos que invierten cambios sin reescribir la historia; opción
-mpara elegir la línea principal al revertir un merge.
Preguntas Frecuentes
¿Cómo vuelvo a un commit anterior en Git sin perder mis cambios actuales?
Depende de qué quieras hacer. Si solo quieres ver ese commit, usa git switch --detach <commit>, que no toca ninguna rama ni tus archivos. Si quieres mover tu rama hacia atrás pero conservar el trabajo actual, usa git reset --soft o git reset --mixed (el árbol de trabajo se mantiene en ambos). Evita git reset --hard mientras tengas cambios sin guardar que te importen.
¿Cuál es la diferencia entre git reset y git revert?
git reset mueve el puntero de tu rama a un commit anterior y reescribe la historia local. git revert deja la historia intacta y añade un commit nuevo que invierte los cambios de otro.
git reset --hard HEAD~1 # mueve la rama atrás, reescribe historia (ramas locales)
git revert HEAD # añade un commit que invierte el último (ramas compartidas)
La regla práctica: reset para ramas locales que solo tienes tú, revert para ramas ya empujadas que comparte el equipo, porque revert no obliga a nadie a hacer push --force.
Hice git reset --hard y perdí commits. ¿Puedo recuperarlos?
Casi siempre, sí. Ejecuta git reflog, que lista cada movimiento reciente de HEAD con su referencia HEAD@{n}. Localiza la línea anterior al reset y vuelve con git reset --hard HEAD@{n}, o lleva ese estado a una rama nueva con git switch -c rescate HEAD@{n}. El reflog es local y sus entradas caducan con el tiempo por el recolector de basura, así que hazlo pronto.
¿Qué es el estado detached HEAD y es peligroso?
Es cuando tu HEAD apunta a un commit directamente en vez de a una rama, algo que ocurre con git checkout <commit> o git switch --detach. Inspeccionar así no tiene riesgo; solo evita hacer commits sin crear antes una rama con git switch -c <rama>, o quedarán huérfanos.