Deshacer cambios y commits en Git


Siempre hay algo que deshacer. Escribiste una edición que no querías, intentaste una corrección que empeorló las cosas, o hiciste commit de algo que preferirías que nadie viera. Git mantiene un registro de casi todas las versiones de tu proyecto que ha visto, así que la mayoría de los errores son más reversibles de lo que parecen en el momento. Qué herramienta usar depende de cuán lejos ha llegado el error: todavía en tu editor, ya preparado, ya confirmado, o ya enviado a algún lugar donde un compañero de equipo puede verlo. Este capítulo recorre cada etapa en ese orden.
¿Qué son push y pull?
Push envía tus commits a una copia compartida del repositorio desde la que tus compañeros también trabajan, y pull trae sus commits hacia ti. Remotes and GitHub cubre ambos en detalle; para este capítulo, "enviado" significa que el commit ha salido de tu máquina.
Descartar un cambio antes de confirmarlo
Digamos que todavía estás rediseñando styles.css en weather-app desde the commit loop, y el nuevo diseño resulta ser una mala idea. No lo has preparado, no lo has confirmado. Quieres que el archivo vuelva al estado anterior, nada más. El comando exacto para ese trabajo es git restore:
$ git status
On branch main
Changes not staged for commit:
modified: styles.css
$ git restore styles.css
$ git status
On branch main
nothing to commit, working tree cleangit restore <file> descarta una edición no confirmada y devuelve el archivo a su último estado guardado. Si ya habías preparado el cambio con git add antes de cambiar de opinión, git restore simple no lo tocará, ya que el archivo ahora está en el área de preparación en lugar de solo en tu directorio de trabajo. Primero sácalo del área de preparación, luego restáuralo:
$ git add styles.css
$ git restore --staged styles.css
$ git restore styles.cssDos movimientos separados para dos áreas separadas: git restore --staged saca un archivo del área de preparación, y git restore solo descarta la edición en tu directorio de trabajo. git restore solo alcanza el trabajo no confirmado, una vez que un cambio está confirmado, un comando diferente toma el relevo.
git restore <file> descarta una edición que no has confirmado y devuelve el archivo al estado anterior. Si ya preparaste el cambio con git add, primero sácalo del área de preparación con git restore --staged <file>, luego restaura el archivo si quieres que la edición desaparezca también. Esto solo funciona en cambios que no has confirmado aún, así que no puede rescatar un error que ya guardaste. Guardar trabajo con git stash
A veces no hay un error para arreglar, solo mala sincronización. Estás a mitad de editar styles.css y un compañero de equipo te necesita en otra rama ahora mismo, pero no estás listo para hacer commit del estilo a medio terminar. git stash guarda tus cambios y te da un directorio de trabajo limpio para que puedas alejarte y volver más tarde.
$ git status
On branch main
Changes not staged for commit:
modified: styles.css
$ git stash
Saved working directory and index state WIP on main: 8f3c7d1 Cache forecast responses in localStorage
$ git status
On branch main
nothing to commit, working tree cleanLa edición a styles.css no se ha ido, está guardada. Tráela de vuelta con git stash pop cuando estés listo:
$ git stash pop
On branch main
Changes not staged for commit:
modified: styles.cssgit stash es para cambios que no estás listo para confirmar aún, nunca crea un commit en tu rama.
git stash guarda cambios que no estás listo para confirmar, así que tu directorio de trabajo queda limpio, y git stash pop los trae de vuelta después. Nada se confirma y nada se pierde, la edición se queda guardada por un tiempo. Recurro a esto constantemente siempre que un compañero me necesita en otra rama ahora mismo. Deshacer un commit con git revert
Digamos que el commit 8f3c7d1, "Cache forecast responses in localStorage", resulta ser incorrecto, y ya está enviado, así que un compañero también lo ha traído. Reescribir ese commit ahora cambiaría algo que alguien más ya tiene, y su copia y la tuya no estarían de acuerdo la próxima vez que hagan pull. git revert resuelve esto sin cambiar nada que ya exista: crea un commits completamente nuevo que aplica lo opuesto del cambio.
$ git revert 8f3c7d1
[main 2b6f0d1] Revert "Cache forecast responses in localStorage"El historial sigue mostrando ambos commits, el original y el que lo deshace, en orden. La copia de nadie necesita ser reformada, un compañero trae el nuevo commit de reversión como cualquier otro.
git revert siempre es seguro en un commit que otros ya tienen, porque añade un nuevo commit en lugar de cambiar uno antiguo.
git revert <commit> deshace un commit añadiendo un commit completamente nuevo que lo invierte, en lugar de borrar el original. Eso lo hace la opción segura una vez que un commit ya se comparte con alguien más, ya que nada guardado previamente se cambia. El historial mantiene un registro tanto del error como de la corrección, que me gusta más cuanto más uso Git. Retroceder con git reset: soft, mixed, y hard
Hay una tercera forma de deshacer un commit, git reset, y funciona diferente de revert: en lugar de añadir un nuevo commit que deshace el antiguo, reset mueve tu rama actual para que apunte a un commit diferente por completo. Eso lo hace rápido y limpio para commits que nadie más ha visto, y arriesgado para commits que han visto.
Reset es el primo más amplio y más fácil de usar mal que restore y revert arriba. No lo necesitarás para la mayoría de arreglos diarios, restore, revert, y stash cubren la gran mayoría de momentos "ayuda, deshaz esto". Lo verás en las instrucciones de otros aunque, así que aquí está el titular: reset mueve tu rama hacia atrás en el historial, y uno de sus modos, --hard, descarta el trabajo no confirmado sin confirmación. Si un comando que estás copiando de algún lado incluye git reset --hard, lee qué hace antes de ejecutarlo.
git reset en la mayoría de arreglos diarios, restore, revert, y stash ya cubren casi todo. Aún así, conoce su forma: reset mueve tu rama hacia atrás a un commit anterior, y su modo --hard limpia tu directorio de trabajo para que coincida sin confirmación. Trata cualquier comando que copies que incluya reset --hard con verdadera cautela. Elegir entre revert y reset
La regla que decide qué herramienta usar: ¿alguien más ya ha traído este commit? Si sí, revert. Si el commit solo existe en tu máquina, en una rama que no has enviado, reset está bien y a menudo es la corrección más limpia. Cuando no estés seguro, revert es siempre la opción segura, ya que funciona en ambos casos.
Recuperar un commit con git reflog
Un reset --hard, un rebase desordenado, o eliminar una rama por error puede parecer que un commit ha desaparecido completamente. Generalmente no ha sido así. Git mantiene un registro local de dónde han apuntado tu HEAD y ramas, llamado el reflog, y ese registro es casi siempre tu forma de volver a un commit que parece perdido.
Es poco probable que necesites esto a menudo, pero aquí está el titular: Git raramente descarta algo en el momento en que parece haberlo hecho. Si alguna vez ejecutas reset --hard y te das cuenta después de que necesitabas ese commit, muy probablemente hay una forma de recuperarlo, cubierto aquí para siempre que estés listo para profundizar.
reset --hard y te das cuenta demasiado tarde de que necesitabas ese commit, probablemente sea todavía recuperable. Git mantiene un registro local llamado el reflog de dónde tu trabajo ha apuntado, y eso es generalmente suficiente para recuperar un commit que pareció desaparecido. Esto es territorio de inmersión profunda, pero es reconfortante saber que está ahí. 
