Git revert vs reset: ¿cuál salva tu historial?

Git revert vs reset explicado: qué comando deshace commits sin riesgo, cuándo reset --hard destruye trabajo y cómo cada uno reescribe el historial de GitHub.

HUMANpor @mrsaynothing41d6 min de lectura

TL;DR: git revert añade un commit nuevo que deshace uno viejo — el historial se conserva, seguro en ramas donde otras personas ya hicieron pull. git reset mueve el puntero de la rama hacia atrás — el historial se reescribe, seguro solo en commits que no has pusheado. Usa revert en ramas compartidas, reset para limpieza local. reset --hard además borra los cambios sin commit del working tree; ese es el que se come trabajo de verdad.

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

Ambos hacen que tu proyecto parezca como si un commit del pasado nunca hubiera existido. Discrepan en cómo:

  • git revert <sha> calcula el patch inverso de un commit y lo commitea. Tu historial crece un commit que dice "deshaz ese". El commit malo queda en el log, seguido de su cancelación. Los SHA de todo lo demás quedan intactos.
  • git reset <sha> mueve el puntero de la rama actual a <sha>. Los commits posteriores quedan descolgados — siguen en la base de objetos hasta el garbage collection, pero dejan de ser alcanzables desde cualquier rama o desde git log.

Uno no reescribe nada; el otro finge que nada pasó. Esa única propiedad decide cuál pide cada situación:

# Demo: un repo desechable para correr
git init /tmp/revert-vs-reset && cd /tmp/revert-vs-reset
echo one > file.txt && git add . && git commit -m "one"
echo two > file.txt && git add . && git commit -m "two"

# Revert: el historial conserva ambos commits, más un tercero que deshace "two"
git revert --no-edit HEAD
git log --oneline        # 3 commits: revert, two, one

# Reset: el puntero de la rama retrocede, "two" desaparece del log
git reset --hard HEAD~1
git log --oneline        # 1 commit: one

Corre ambos e inspecciona git log — la asimetría es toda la respuesta.

¿Qué hacen en realidad git reset --soft, --mixed y --hard?

Los tres modos controlan dónde sobreviven tus cambios después de que el puntero se mueve:

Modo Puntero de rama Staging area Working tree Tus edits
--soft retrocede quedan staged intacto se conservan por completo
--mixed (por defecto) retrocede unstaged intacto se conservan, unstaged
--hard retrocede reset reset se borran

Lecturas concretas de esa tabla:

  • git reset --soft HEAD~1 — "commiteé demasiado pronto." Todo vuelve a staged, listo para recommitir, quizá junto con más trabajo.
  • git reset HEAD~1 (mixed) — "metí al staging los archivos equivocados." Los edits sobreviven en el working tree, nada queda staged. Combínalo con cómo borrar archivos untracked con git cuando también quieras quitarte los archivos sueltos.
  • git reset --hard HEAD~1 — "este commit y mis edits sin commit son basura." Git no pregunta dos veces; no hay undo para la parte del working tree salvo que antes la hayas guardado con stash o commiteado.

revert no tiene modos porque nunca destruye nada — solo añade un commit compensatorio.

¿Cuándo usar git revert en lugar de git reset?

Hazte una pregunta: ¿alguien más ya hizo pull de este commit? Si la respuesta es sí, reset queda descartado.

Resetear una rama sobre la que otros construyeron reescribe el historial compartido. Su próximo pull se encuentra con ramas divergentes, y el "arreglo" suele ser un force-push que convierte tu error en problema de todos. Revert añade un commit normal, así que un git pull simple hace merge limpio para todos — por eso revert es la respuesta por defecto en main y en los pull requests de GitHub, donde el botón "Revert" existe precisamente porque reescribir el historial de un PR mergeado no es opción.

Reset es para la ventana anterior a compartir: el commit de hace treinta segundos, el error de staging, la rama local de experimento. Si la única copia del trabajo vive en tu máquina, eres libre de reorganizar el historial — ese es todo el punto de la ventana pre-push.

Hay un caso intermedio: pusheaste a tu propia feature branch y nadie más construye sobre ella. Un force-push tras el reset es socialmente aceptable ahí, pero revert sigue costando menos coordinación. Reserva los force-push para ramas que posees en solitario.

¿Es git revert más seguro que git reset en ramas compartidas?

Sí, estructuralmente — no solo por convención:

  • Revert produce un commit ordinario; CI corre sobre él, el diff es revisable y git log le explica a tu yo futuro por qué desapareció el cambio.
  • Reset descarta en silencio el estado intermedio. Nadie que revise después puede ver qué se deshizo ni cuándo, porque el log muestra una línea recta que nunca lo contuvo.

Dos filos prácticos:

  • Revertir un merge commit necesita git revert -m 1 <sha> (conserva la línea del primer padre). Sin -m, git se niega y te deja a ti leyendo el error.
  • Revert no es una máquina del tiempo: deshace el diff de un commit. Si commits posteriores tocaron las mismas líneas, puede que tengas que resolver conflictos — eso es git diciéndote que el undo está enredado, información útil, no un mal funcionamiento.

Para deshacer específicamente tu último commit — conservando o descartando sus cambios — las opciones están en git undo last commit: cómo conservar los cambios.

¿Qué hay de git restore — dónde encaja?

git restore (y git switch, su pareja) llegaron en git 2.23 para tomar los trabajos que reset hacía mal por sobrecarga de nombres:

Tarea Forma vieja Forma clara
Descartar edits del working tree en un archivo git checkout -- file / git reset --hard git restore file
Sacar un archivo del staging git reset HEAD file git restore --staged file
Mover la rama a otro commit git reset <sha> git reset <sha> (sin reemplazo — esto se queda)

La división moderna queda así: restore arregla archivos, reset mueve ramas, revert deshace commits publicados. El autocomplete muestra que la gente busca "git revert vs reset vs restore" junto, con razón — son un solo modelo mental repartido en tres comandos. Las formas viejas de git checkout siguen funcionando en todas partes; las nuevas solo te impiden mandar una rama entera a la trituradora cuando solo querías sacar un archivo del staging.

Si tu objetivo es copiar hacia adelante un commit bueno en vez de borrar uno malo, ese es el trabajo de cherry-pick — mira git cherry-pick: varios commits, ramas y conflictos.

Resumen rápido para decidir

  1. Commit público (pusheado, otros hicieron pull) → git revert <sha>.
  2. Commit solo local, quieres rehacerlo → git reset --soft o --mixed y commit de nuevo.
  3. Solo local, quieres que desaparezca junto con el desorden sin commit → git reset --hard, después de una mirada honesta a qué más guarda el working tree.
  4. Un archivo quedó hecho pedazos → git restore <file> y deja la rama en paz.

La única entrada genuinamente peligrosa de esa lista es --hard — todo lo demás se puede recuperar con git reflog. Las pérdidas permanentes en git son angostas y casi siempre requieren que las escribas explícitamente; el hábito que vale la pena construir es pausar medio segundo antes de --hard, no evitar las herramientas afiladas de git.