Comparación

git reset y git revert: cómo deshacer un commit sin romper el historial del equipo

Actualizado: · Skillaria.Top

git reset mueve el puntero de la rama a otro commit y reescribe el historial, así que solo sirve para commits locales que aún no se han subido. git revert crea un commit nuevo con los cambios inversos y no toca el historial, por eso es la forma segura de deshacer un commit que ya está en el servidor.

En resumen: git reset hace retroceder la rama y finge que los commits sobrantes nunca existieron. git revert deja el historial tal cual y añade un commit nuevo que anula los cambios del antiguo. Lo primero sirve para el trabajo local; lo segundo, para todo lo que ya está en el repositorio compartido. Si solo recuerdas esta regla, evitarás la mayoría de los desastres con Git.

A continuación veremos cómo funcionan ambos comandos, en qué se diferencian los modos de reset, cómo deshacer un commit después de un push, cómo recuperar commits «perdidos» y qué pinta aquí git clean.

Las tres áreas que modifican los comandos para deshacer

Para entender la diferencia, recuerda las tres áreas de Git: el directorio de trabajo (los archivos en disco), el índice (staging area, lo que entrará en el próximo commit) y el historial (los commits a los que apuntan la rama y HEAD). Cada comando para deshacer actúa sobre su propio conjunto de áreas.

ComandoHistorialÍndiceDirectorio de trabajoSeguro en una rama compartida
git restore archivononosísí
git restore --staged archivonosínosí
git reset --softsínonono
git reset --mixed (por defecto)sísínono
git reset --hardsísísíno
git revertañade un commit——sí
git cleannonoborra archivos sin seguimientosí, pero es irreversible

Cómo funciona git reset

El comando git reset <commit> mueve el puntero de la rama actual (y HEAD con él) al commit indicado. Los commits «posteriores» no se borran físicamente, pero dejan de verse en la rama. El modo determina qué pasa con el índice y los archivos.

git reset --soft: deshacer el commit y dejar los cambios en el índice

La rama se mueve, pero el índice y los archivos quedan como estaban. Todos los cambios de los commits deshechos quedan preparados para el commit. Es la forma ideal de reescribir un mensaje o juntar los últimos commits en uno solo.

git reset --soft HEAD~3
git commit -m "feat: formulario de registro"

Los tres últimos commits se han convertido en uno y el contenido de los archivos no ha cambiado.

git reset --mixed: deshacer el commit y el índice

Es el modo por defecto: git reset HEAD~1 equivale a git reset --mixed HEAD~1. La rama se mueve, el índice se vacía y los cambios se quedan en el directorio de trabajo sin preparar. Útil cuando has incluido de más en un commit y quieres volver a decidir qué añadir y en qué partes.

git reset HEAD~1
git status
# Changes not staged for commit:
#   modified:   app.py
#   modified:   config.yml

git reset --hard: volver atrás por completo

La rama, el índice y el directorio de trabajo quedan en el estado del commit indicado. Todos los cambios sin commit en archivos con seguimiento se pierden sin aviso. Es el único modo de reset que realmente puede destruir tu trabajo.

git reset --hard HEAD~1          # descartar el último commit y todos los cambios
git reset --hard origin/main     # dejar la rama igual que en el servidor

Antes de --hard conviene ejecutar git status y git stash si hay algo valioso. Lo que está en un commit se puede recuperar (lo vemos más abajo); lo que no, no.

git reset sobre un solo archivo

Con una ruta, reset no mueve la rama, solo modifica el índice: git reset app.py saca el archivo de los preparados. El equivalente moderno es git restore --staged app.py, que es más claro. Las opciones --soft y --hard no funcionan con rutas.

Cómo funciona git revert

git revert <commit> calcula los cambios del commit indicado, los aplica a la inversa y crea un commit nuevo. Las líneas añadidas se eliminan y las eliminadas vuelven. El historial avanza, no se reescribe nada, así que tus compañeros no tienen que arreglar nada en sus copias.

git log --oneline
# a3f9c21 (HEAD -> main) docs: README
# 7be41d0 feat: caché de respuestas
# 1c02e8f fix: timeout de las peticiones

git revert 7be41d0
# [main 5d8e2aa] Revert "feat: caché de respuestas"
#  2 files changed, 3 insertions(+), 41 deletions(-)

Git abrirá el editor con un mensaje del tipo Revert "..." y la línea This reverts commit 7be41d0.... Es buena práctica añadir por qué se ha deshecho el cambio.

Opciones útiles de revert

  • --no-edit: aceptar el mensaje estándar sin abrir el editor.
  • -n (--no-commit): aplicar los cambios inversos al índice sin crear el commit. Así puedes deshacer varios commits con uno solo.
  • git revert A..B: deshacer un rango de commits (cada uno con su propio commit de revert, de los más nuevos a los más antiguos).
  • -m 1: obligatorio para un commit de merge; indica qué padre se considera la línea principal.
git revert --no-commit HEAD~3..HEAD
git commit -m "Revert: deshacer tres commits del experimento"

Revert de un commit de merge

Una fusión tiene dos padres y Git no sabe respecto a cuál calcular los cambios «inversos», así que sin -m da el error is a merge but no -m option was given. Normalmente la rama principal es el primer padre:

git revert -m 1 9f1e2ab

La trampa: si más adelante quieres volver a fusionar esa misma rama, Git considerará que sus commits ya están fusionados y no traerá los cambios. La solución es hacer primero un revert del propio commit de revert y después añadir los cambios nuevos.

git reset vs git revert: cuál elegir

Criteriogit resetgit revert
Qué hace con el historiallo reescribe (los commits desaparecen de la rama)añade un commit nuevo
El commit ya está subidorequiere push --force y rompe el trabajo de los compañerosun git push normal
Deshacer un commit en mitad del historialsolo junto con todos los posterioresse puede de forma puntual
Rastro en el historialno quedaqueda: se ve quién lo deshizo y por qué
Escenario típicolimpiar commits locales antes del pushdeshacer en main un commit que rompió producción

Regla sencilla: si alguien aparte de ti ha visto el commit, revert; si no, reset. La excepción es tu rama de feature personal que nadie ha descargado: ahí reescribir el historial y hacer git push --force-with-lease es aceptable.

Por qué reset --hard en una rama compartida es mala idea

Imagina que haces git reset --hard HEAD~2 en main y git push --force. Tus compañeros siguen teniendo esos dos commits. En el siguiente git pull, Git intentará fusionar su rama con la tuya y los commits «borrados» volverán, ahora con un commit de merge sobrante. Peor aún si alguien ya había subido sus commits encima: el force push simplemente los sobrescribe.

Por eso en las ramas protegidas (main, release) el force push suele estar prohibido en la configuración del hosting. Si no hay más remedio que reescribir, usa git push --force-with-lease: se negará si en el servidor hay commits que tú no has visto.

Escenarios típicos para deshacer cambios

Cómo deshacer el último commit que aún no se ha subido

git reset --soft HEAD~1   # cambios en el índice, se puede volver a hacer commit
git reset HEAD~1          # cambios en los archivos, índice vacío
git reset --hard HEAD~1   # cambios descartados

Si solo quieres corregir el mensaje o añadir un archivo olvidado, no hace falta reset: git add olvidado.txt y git commit --amend.

Cómo deshacer un commit después del push

git revert <hash>
git push

Esta es la respuesta a la búsqueda frecuente «git reverse commit»: en Git no existe un comando git reverse; el commit «inverso» lo crea precisamente git revert.

Cómo dejar la rama igual que en el servidor

git fetch origin
git reset --hard origin/main

Útil cuando la rama local se ha liado tras un rebase fallido o experimentos. Todos los commits locales que no estén en el servidor desaparecerán de la rama.

Cómo recuperar commits tras un reset fallido

Git registra cada movimiento de HEAD en el reflog. Tras un reset, los commits siguen en la base de objetos al menos varias semanas (por defecto, las entradas inalcanzables del reflog se guardan 30 días).

git reflog
# 1c02e8f HEAD@{0}: reset: moving to HEAD~2
# a3f9c21 HEAD@{1}: commit: docs: README
# 7be41d0 HEAD@{2}: commit: feat: caché de respuestas

git reset --hard a3f9c21   # o HEAD@{1}

El reflog es local: solo existe en tu clon y no se envía al servidor.

git clean: borrar archivos sin seguimiento

git reset --hard no toca los archivos que Git no sigue: archivos nuevos que nunca pasaron por el índice, artefactos de compilación, logs. Para ellos existe git clean. El comando es irreversible: los archivos que borra no van ni al reflog ni a la papelera.

git clean -n      # dry run: mostrar qué se va a borrar
git clean -f      # borrar archivos sin seguimiento
git clean -fd     # lo mismo más las carpetas sin seguimiento
git clean -fdx    # más los archivos ignorados (.gitignore): node_modules, dist
git clean -i      # selección interactiva

Sin -f, Git se niega a ejecutarlo por defecto (opción clean.requireForce). Empieza siempre con -n, sobre todo con -x: también borrará tu .env local si está ignorado.

Combinación para volver por completo al último commit: git reset --hard && git clean -fd.

Errores habituales

  • reset --hard en lugar de --soft. Lo que estaba en commits lo salva el reflog; lo que no, se ha perdido. La costumbre de hacer git stash antes de experimentar resuelve el problema.
  • revert sin entender las dependencias. Si commits posteriores modificaron las mismas líneas, revert producirá un conflicto. Resuélvelo como cualquier otro: edita los archivos, git add, git revert --continue; si cambias de idea, git revert --abort.
  • Confundir restore y reset. Para deshacer los cambios de un archivo no hace falta un reset de toda la rama: git restore archivo.
  • force push a main. Mejor revert y un push normal, aunque el historial quede algo más largo.

Conclusión

git reset es la herramienta para tu propio historial aún no publicado: --soft conserva los cambios en el índice, --mixed en los archivos y --hard lo descarta todo. git revert es la forma segura de deshacer en ramas compartidas y deja un rastro honesto en el historial. git clean termina de limpiar los archivos sin seguimiento, pero no admite errores. Los escenarios para deshacer cambios con restore, reset y revert se practican en el curso de Git.

Preguntas frecuentes

¿git reset borra los commits para siempre?

No, los commits siguen en la base de objetos y se pueden encontrar con git reflog hasta que el recolector de basura los elimine; por defecto, a los 30 días para las entradas inalcanzables.

¿Se puede deshacer un git revert?

Sí, el commit de revert es un commit normal: puedes deshacerlo con otro git revert o, si no lo has subido, quitarlo con git reset.

¿En qué se diferencia git reset de git checkout?

git reset mueve el puntero de la rama actual, mientras que git checkout (o git switch) cambia HEAD a otra rama o commit sin modificar la rama en sí.

¿Qué significan HEAD~1 y HEAD^?

Ambos indican el padre del commit actual; HEAD~3 es el tercer antecesor siguiendo los primeros padres y HEAD^2 es el segundo padre de un commit de merge.

¿Cómo deshacer git clean?

Con Git no se puede: los archivos sin seguimiento borrados nunca estuvieron en el repositorio, así que ejecuta siempre antes git clean -n.

Aprende este tema en vídeo

Este tema se explica a fondo y con práctica en el curso «Git desde cero hasta desarrollador seguro».

Ir al curso