9 Deshacer cambios

Git ofrece varias formas de deshacer cambios, y la diferencia entre ellas —cuál es "segura" y cuál reescribe el historial— es una de las fuentes más comunes de confusión. Este capítulo las recorre de menor a mayor alcance.

9.1 Modificar el último commit: commit --amend

git commit --amend

En vez de crear un commit nuevo, reemplaza el commit actual (HEAD) por uno nuevo que combina lo que había en el commit anterior con lo que esté preparado en el índice en este momento. Es decir: --amend crea un objeto commit distinto —con un hash distinto— y mueve la rama actual para que apunte a ese nuevo commit en vez de al anterior. El commit original no se destruye inmediatamente: queda huérfano en la base de datos de objetos hasta que el recolector de basura lo elimine (see Packfiles), pero ninguna rama lo señala ya.

Usos típicos:

Advertencia: como --amend sustituye el commit por uno nuevo, si ese commit ya fue compartido con otras personas (push a un repositorio remoto), modificarlo reescribe historia que otros ya pueden tener descargada. Esto se retoma en Reescribir la historia.

9.2 git reset: mover HEAD y decidir hasta dónde

git reset mueve la rama actual (y, por lo tanto, HEAD) a otro commit, y opcionalmente ajusta el índice y el directorio de trabajo para que coincidan. Tiene tres modos, que se diferencian por hasta qué área llega el retroceso:

git reset --soft <commit>

Mueve HEAD (y la rama) al commit indicado. No toca el índice ni el directorio de trabajo. El resultado es que todos los cambios de los commits "deshechos" quedan preparados en el índice, como si se hubiera hecho git add de todo pero sin confirmar el commit. Es útil para combinar varios commits en uno: retroceder con --soft y volver a hacer commit crea un único commit con todos esos cambios juntos.

git reset --mixed <commit>

El modo por defecto (git reset sin opciones equivale a --mixed). Mueve HEAD y además vacía el índice hasta igualarlo con el commit indicado, pero deja el directorio de trabajo intacto. El resultado: los cambios quedan en el directorio de trabajo como modificaciones sin preparar, listas para volver a revisarse y prepararse selectivamente.

git reset --hard <commit>

Mueve HEAD, vacía el índice, y además sobrescribe el directorio de trabajo para que coincida exactamente con el commit indicado. Este modo descarta cambios de forma permanente: cualquier modificación sin confirmar en el directorio de trabajo se pierde. Es el más usado y también el más peligroso.

9.2.1 Cómo elegir entre los tres modos

                índice        directorio de trabajo
--soft          conservado    conservado
--mixed         reiniciado    conservado
--hard          reiniciado    reiniciado

Una forma práctica de decidirlo: preguntarse qué se quiere hacer con los cambios de los commits que se están "deshaciendo". Si se quieren conservar preparados para un nuevo commit, --soft. Si se quieren conservar pero revisarlos de nuevo antes de decidir qué preparar, --mixed. Si directamente no importan y deben desaparecer, --hard.

9.2.2 Reset de archivos individuales

git reset también puede aplicarse a un archivo específico en vez de mover HEAD:

git reset HEAD archivo.txt

Esto quita el archivo del índice (lo "despreparara"), sin mover HEAD ni tocar el directorio de trabajo. Es equivalente al más moderno git restore --staged archivo.txt.

9.3 git revert: deshacer sin reescribir historia

A diferencia de reset, que mueve la rama hacia atrás y hace desaparecer commits del historial visible, git revert crea un nuevo commit cuyo contenido es el opuesto exacto de un commit anterior, dejando intacto todo lo que había en medio.

git revert a1b2c3d

Esto es seguro para commits ya compartidos: no reescribe nada, solo añade un commit nuevo que anula los efectos de otro. Es la forma recomendada de deshacer un cambio que ya fue publicado a un repositorio remoto y que otras personas pueden tener descargado.

9.4 git restore: descartar cambios locales

git restore (comando más reciente, pensado para separar estas tareas de las de checkout) trabaja sobre el directorio de trabajo o el índice, sin mover HEAD.

git restore archivo.txt

Descarta los cambios sin preparar de un archivo, devolviéndolo a como estaba en el índice (o en el último commit, si no había nada preparado).

git restore --staged archivo.txt

Quita el archivo del índice, sin tocar el directorio de trabajo.

git restore --source=HEAD~2 archivo.txt

Restaura el contenido del archivo tal como estaba dos commits atrás, tanto en el directorio de trabajo como opcionalmente en el índice.

9.5 Resumen: qué usar según la situación

"Me equivoqué en el mensaje del último commit"

git commit --amend

"Quiero combinar los últimos 3 commits en uno"

git reset --soft HEAD~3 seguido de git commit

"Quiero deshacer los últimos commits pero revisar los cambios antes de decidir qué hacer con ellos"

git reset --mixed HEAD~N (o simplemente git reset HEAD~N)

"Quiero volver exactamente a un commit anterior y no me importa perder todo lo posterior"

git reset --hard <commit>

"Quiero deshacer un commit que ya subí al repositorio remoto"

git revert <commit>

"Edité un archivo por error y quiero descartar el cambio"

git restore archivo