Tarde o temprano pasa: haces un commit y te das cuenta de que algo está mal. El mensaje está mal escrito, te olvidaste de guardar un fichero, o directamente el cambio entero es un error.
Git tiene herramientas para cada situación. Lo que importa es saber en qué punto estás cuando te das cuenta del error, porque la solución es distinta según si ya subiste el commit a GitHub o no.
Vamos a ver los tres escenarios más habituales practicando cada uno desde cero.
Seguimos en mi-proyecto. Para poder practicar necesitamos un commit malo. Abrimos index.js y añadimos una línea de debug que no debería estar en el código:
index.js
console.log("Iniciando aplicación...");
+ console.log("DEBUG: esto no debería estar aquí");
const saludo = (nombre) => {
return `Hola, ${nombre}`;
};
const despedida = (nombre) => {
return `Hasta luego, ${nombre}`;
};Hacemos el commit:
git commit -am "Añade logs de depuración"[main f3a1b2c] Añade logs de depuración
1 file changed, 1 insertion(+)Comprobamos el historial para ver dónde estamos:
git log --onelinef3a1b2c (HEAD -> main) Añade logs de depuración
a1b2c3d Merge feature/despedida: resuelve conflicto en console.log
3f8a21c Añade hoja de estilos y la enlaza en el HTML
7d9e0f1 primer commitEl commit malo está ahí arriba. Aún no hemos hecho push. Ahora vamos a ver las distintas formas de deshacerlo.
Esta es la opción más habitual. Deshace el commit pero deja el código intacto en el fichero, como si nunca hubieras llegado a hacer git commit.
El comando es git reset HEAD~1. Antes de ejecutarlo, vamos a entender qué significa:
HEADes el commit en el que estás ahora mismo, el último que hiciste.~1significa "retrocede uno".- Juntos: "deshaz el último commit".
git reset HEAD~1Comprobamos el historial:
git log --onelinea1b2c3d (HEAD -> main) Merge feature/despedida: resuelve conflicto en console.log
3f8a21c Añade hoja de estilos y la enlaza en el HTML
7d9e0f1 primer commitEl commit malo ha desaparecido. Ahora miramos el estado:
git statusOn branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
modified: index.jsEl código sigue en el fichero, pero el commit ya no existe. Ahora puedes corregir lo que necesites y hacer el commit correctamente.
Para practicar la siguiente opción vuelve a hacer el commit malo:
git commit -am "Añade logs de depuración"A veces el problema no es el código sino el mensaje que pusiste. Para ese caso existe --amend, que reescribe el último commit sin tocar el código:
git commit --amend -m "Elimina console.log de debug"Verificamos:
git log --onelinee7c9d1a (HEAD -> main) Elimina console.log de debug
a1b2c3d Merge feature/despedida: resuelve conflicto en console.log
3f8a21c Añade hoja de estilos y la enlaza en el HTML
7d9e0f1 primer commitEl mensaje ha cambiado. Fíjate que el hash también ha cambiado (f3a1b2c → e7c9d1a): --amend no modifica el commit, crea uno nuevo en su lugar.
--amendtambién sirve si te olvidaste de incluir un fichero en el commit. Editas el fichero, hacesgit addy luegogit commit --amendsin-mpara que se abra el editor con el mensaje anterior y puedas dejarlo igual.
Usa
--amendsolo antes de hacer push. Si ya subiste el commit, cambiar el hash causará problemas.
Para practicar la siguiente opción necesitas volver a tener el commit malo. Deshaz el amend y recréalo:
git reset HEAD~1
git commit -am "Añade logs de depuración"Si el commit era tan malo que no quieres conservar ni el código, añadimos --hard:
git reset --hard HEAD~1HEAD is now at a1b2c3d Merge feature/despedida: resuelve conflicto en console.logVerificamos el historial:
git log --onelinea1b2c3d (HEAD -> main) Merge feature/despedida: resuelve conflicto en console.log
3f8a21c Añade hoja de estilos y la enlaza en el HTML
7d9e0f1 primer commitY el estado:
git statusOn branch main
nothing to commit, working tree cleanEl commit ha desaparecido y los cambios también. Si abres index.js, la línea de debug ya no está. --hard no pregunta ni avisa: borra y punto. Úsalo solo cuando estés seguro de que no necesitas ese código para nada.
Para este caso vamos a trabajar en una rama. Primero nos aseguramos de que index.js tiene la línea de debug, la añadimos si no está, y creamos la rama:
git switch -c feature/experimento
git commit -am "Añade logs de depuración"Subimos la rama a GitHub. Hasta ahora siempre hemos usado git push a secas, pero eso solo funciona cuando la rama ya existe en el remoto y Git sabe a dónde enviarla. Para una rama nueva que acabamos de crear en local, GitHub todavía no la conoce, así que tenemos que decirle explícitamente dónde subirla:
git push -u origin feature/experimentoorigines el nombre del remoto, que es como Git llama a GitHub en este proyecto.feature/experimentoes el nombre que tendrá la rama en GitHub.-ule dice a Git que recuerde esta conexión, de forma que la próxima vez que estés en esta rama puedas usargit pusha secas y ya sepa adónde ir.
To github.com:tu-usuario/mi-proyecto.git
* [new branch] feature/experimento -> feature/experimentoEl commit malo ya está en GitHub. Comprobamos el historial para verlo:
git log --onelinef3a1b2c (HEAD -> feature/experimento, origin/feature/experimento) Añade logs de depuración
a1b2c3d Merge feature/despedida: resuelve conflicto en console.log
3f8a21c Añade hoja de estilos y la enlaza en el HTML
7d9e0f1 primer commitComo esta rama es solo nuestra, nadie más la está usando, así que podemos reescribir su historia sin afectar a nadie. Deshacemos el commit en local:
git reset HEAD~1git log --onelinea1b2c3d (HEAD -> feature/experimento) Merge feature/despedida: resuelve conflicto en console.log
3f8a21c Añade hoja de estilos y la enlaza en el HTML
7d9e0f1 primer commitEl commit malo ha desaparecido en local. Ahora le decimos a GitHub que actualice la rama con nuestra versión:
git push --force-with-leaseTo github.com:tu-usuario/mi-proyecto.git
+ f3a1b2c...a1b2c3d feature/experimento -> feature/experimento (forced update)El --force-with-lease es un push forzado con un seguro: antes de sobrescribir el remoto, comprueba que nadie haya subido algo a esa rama desde la última vez que hiciste pull. Si hubiera cambios remotos que no tienes, te avisa en lugar de machacarlos sin más. Es la forma segura de forzar, mucho mejor que usar --force a secas.
Nunca uses
--forceni--force-with-leaseenmaino en ramas que compartan con otras personas.
Limpiamos la rama de prueba. Primero volvemos a main y borramos la rama en local, como ya vimos en la guía de ramas. Luego borramos también la rama en GitHub, porque al crearla con -u se quedó ahí:
git switch main
git branch -d feature/experimento
git push origin --delete feature/experimentoEste es el más delicado. En este punto index.js todavía tiene la línea de debug en el directorio de trabajo porque el git reset HEAD~1 del caso anterior la dejó ahí sin staging. Así que podemos hacer el commit directamente:
git commit -am "Añade logs de depuración"
git pushgit log --onelinef3a1b2c (HEAD -> main, origin/main) Añade logs de depuración
a1b2c3d Merge feature/despedida: resuelve conflicto en console.log
3f8a21c Añade hoja de estilos y la enlaza en el HTML
7d9e0f1 primer commitEl commit malo está en main y en GitHub. Si alguien del equipo hace git pull ahora, lo recibirá. En este caso usar reset y forzar el push rompería la historia de todos los que ya lo tienen, así que no podemos hacerlo.
La solución es git revert. En lugar de borrar el commit malo, crea uno nuevo que deshace exactamente lo que hizo el anterior. La historia no se reescribe, se añade encima.
Primero necesitamos el hash del commit que queremos deshacer. Lo vemos en el git log --oneline que acabamos de hacer: es f3a1b2c.
git revert --no-edit f3a1b2c--no-edit le dice a Git que use el mensaje automático que genera él solo, sin abrir el editor. Sin ese flag, Git intentaría abrir el editor de texto para que confirmes el mensaje, y si no tienes configurado VS Code como editor por defecto podría abrirse vim, que tiene sus propios comandos para cerrar y puede resultar confuso.
[main 8e4d5f6] Revert "Añade logs de depuración"
Date: ...
1 file changed, 1 deletion(-)Subimos:
git pushMiramos el historial:
git log --oneline8e4d5f6 (HEAD -> main, origin/main) Revert "Añade logs de depuración"
f3a1b2c Añade logs de depuración
a1b2c3d Merge feature/despedida: resuelve conflicto en console.logEl commit malo sigue visible porque la historia no se borra, pero hay uno nuevo encima que deshace su efecto. Si abres index.js, la línea de debug ha desaparecido. El código está correcto y el historial del equipo sigue intacto.
| Situación | Solución |
|---|---|
| Commit en local, quiero corregir el mensaje | git commit --amend -m "..." |
| Commit en local, quiero deshacerlo y conservar el código | git reset HEAD~1 |
| Commit en local, quiero deshacerlo y borrar el código | git reset --hard HEAD~1 |
| Push en mi rama personal | git reset HEAD~1 + git push --force-with-lease |
| Push en main o rama compartida | git revert --no-edit <hash> + git push |
La regla de oro: antes del push puedes reescribir lo que quieras. Después del push en una rama compartida, crea historia nueva en lugar de borrar la que ya existe.
