git rebase -i: limpia tus commits antes de abrir la PR

Guía paso a paso de git rebase interactivo: qué hace pick, reword, squash, fixup, drop y edit, cómo juntar commits y cómo abortar sin romper nada.

git rebase -i: limpia tus commits antes de abrir la PR

git rebase -i HEAD~3 abre un editor con tus tres últimos commits, uno por línea, con la palabra pick delante de cada uno. Cambias esa palabra por otra (squash, reword, fixup, drop), guardas, cierras el editor y Git reescribe esos commits siguiendo tus instrucciones. Ese es todo el mecanismo.

El caso típico es este: llevas una tarde entera de commits con mensajes tipo “wip” y “ahora sí”, y quieres que la pull request se lea como si lo hubieras hecho bien a la primera. Doy por hecho que ya sabes qué hace rebase por debajo. Si no, empieza por qué hace rebase y cuándo usarlo y vuelve aquí después.

Cómo usar git rebase interactivo paso a paso

  1. Mira cuántos commits quieres tocar. Ejecuta git log --oneline -6 y cuenta desde arriba hasta el último commit que quieres modificar. Ese número es la N de HEAD~N.

  2. Lanza el rebase. La -i (de interactive) es lo que hace que se abra el editor en vez de aplicar el rebase directamente.

    # Abre la lista de instrucciones con los 3 últimos commits
    git rebase -i HEAD~3
  3. Lee la lista antes de tocar nada. Se te abrirá algo así, con comentarios de ayuda debajo:

    pick 8f3c1a2 añade endpoint de login
    pick 2b91d0e wip
    pick c4e7f10 typo en el mensaje de error

    Fíjate en el orden: el commit más antiguo va arriba. Es justo al revés que en git log, y es la primera fuente de líos. Esa lista es el guion que Git va a ejecutar de arriba abajo.

  4. Cambia los pick por lo que quieras hacer con cada commit. Puedes escribir el comando entero (squash) o su inicial (s). También puedes reordenar las líneas cortando y pegando, y Git aplicará los commits en el nuevo orden.

  5. Guarda y cierra. Ahí es cuando Git empieza a trabajar. Si has puesto algún reword o squash, se te abrirá un segundo editor para que escribas el mensaje final del commit resultante.

Cuando termina, git log --oneline te devuelve la historia ya limpia. Los commits nuevos tienen hashes distintos a los de antes aunque el contenido sea idéntico: Git no ha movido tus commits, ha creado copias.

Qué hace cada comando del editor

ComandoQué le pasa a ese commit
pick (p)Se aplica tal cual. Es el valor por defecto de todas las líneas.
reword (r)Se aplica igual, pero Git abre el editor para que cambies el mensaje. Perfecto para el clásico “arregla bug” sin más contexto.
squash (s)Se funde con el commit de la línea de arriba. Git abre un editor con los dos mensajes juntos para que escribas uno solo.
fixup (f)Se funde con el de arriba igual que squash, pero tira su mensaje a la basura sin preguntar.
drop (d)Ese commit desaparece de la historia, con sus cambios incluidos.
edit (e)Git para justo después de aplicarlo y te devuelve la terminal para que modifiques ficheros, dividas el commit en dos o lo que necesites. Sigues con git rebase --continue.

Hay más comandos en esa lista, como exec para ejecutar un comando de shell entre commit y commit o break para pausar el rebase donde tú digas [2]. Con los seis de la tabla resuelves cualquier limpieza de rama normal.

Ejemplo con squash y fixup: juntar cuatro commits en dos

Tienes esto en tu rama:

pick 8f3c1a2 añade endpoint de login
pick 2b91d0e wip
pick c4e7f10 typo
pick 5a0b3d9 añade tests del login

Los tres primeros son en realidad un solo trabajo, y el mensaje del primero está bien. Los dos de en medio no aportan nada al historial. El último merece vivir aparte. Lo dejas así:

pick 8f3c1a2 añade endpoint de login
fixup 2b91d0e wip
fixup c4e7f10 typo
reword 5a0b3d9 añade tests del login

Guardas, cierras, y Git te abre el editor una vez para reescribir el mensaje del commit de tests. Resultado: dos commits, cada uno con un mensaje que un revisor entiende sin abrir el diff.

Si haces esto a menudo hay un atajo que te ahorra el editor entero. Cuando corriges algo de un commit anterior, en vez de un commit nuevo haz git commit --fixup=8f3c1a2, y luego git rebase -i --autosquash HEAD~4 coloca esa corrección justo debajo de su commit y le pone fixup ya marcado.

Conflictos, —abort y la red de seguridad

Un conflicto no cancela nada: el rebase se detiene en la línea de la lista que lo ha provocado y se queda esperando. Resuelves ahí mismo y le dices que siga con las líneas que quedan:

git add src/auth.ts       # marca el conflicto como resuelto
git rebase --continue     # retoma la lista en la línea siguiente

Si prefieres salir, git rebase --abort deshace la lista entera y deja la rama exactamente como estaba antes de empezar, lleves resueltos tres commits o ninguno.

Cuando el rebase ya ha terminado, --abort deja de estar disponible y la red de seguridad pasa a ser git reflog, que te deja volver a cualquier posición por la que haya pasado la rama. Cómo leerlo lo tienes en la guía de git reflog.

Errores comunes

Rebasear commits que ya has subido

Es la única regla de esta página que no admite matices: si esos commits ya están en una rama que comparte otra gente, no los toques. Cada línea que marques con squash, fixup o reword cambia el hash de ese commit, y los demás se quedan con una historia que ya no coincide con la del servidor. La documentación oficial de Git lo dice sin rodeos [1].

Sobre tu propia rama de feature, aunque esté subida, sí puedes hacerlo: al terminar la subes con git push --force-with-lease. Dónde está exactamente la frontera lo desgloso en merge vs rebase.

Poner squash en la primera línea

Si marcas squash o fixup en la línea de arriba del todo, Git falla con un cannot 'squash' without a previous commit. Tiene sentido: esos dos comandos funden el commit con el de la línea anterior, y ahí no hay ninguna. Si lo que buscas es fundir el primero de la lista con el que viene antes, incluye un commit más en el rango: HEAD~4 en vez de HEAD~3.

Contar mal la N de HEAD~N

Pasarse de commits es la manera más rápida de acabar reescribiendo trabajo que ya estaba en main. Mira siempre el git log --oneline antes y cuenta las líneas.

Usar —skip para salir de un conflicto

git rebase --skip no se salta el conflicto, se salta el commit entero que lo estaba provocando. Si lo usas para quitarte el mensaje rojo de encima, te quedas sin esos cambios y no te enteras hasta bastante después.

Checklist antes de lanzar el rebase

  • Los commits que voy a tocar no están en main ni en ninguna rama compartida
  • He contado la N con git log --oneline y no de memoria
  • El árbol de trabajo está limpio (git status sin cambios sin commitear)
  • Sé cómo guardar y salir del editor que tengo configurado
  • Tengo claro que ante cualquier duda, git rebase --abort me devuelve al punto de partida

Con esto ya puedes dejar cualquier rama presentable en un par de minutos. Si Git en general te sigue pareciendo una caja negra que solo funciona cuando copias comandos de Stack Overflow, en el curso Git: de cero a profesional recorremos esto mismo con ejercicios interactivos, rebase incluido.

Fuentes

  1. Git Tools: Rewriting History — Pro Git (Chacon & Straub) — advertencia oficial sobre no reescribir commits ya subidos a un repositorio compartido.
  2. Documentación de git-rebase — git-scm.com — definición de los comandos de la lista interactiva (pick, reword, edit, squash, fixup, drop, exec, break) y de --continue, --abort, --skip y --autosquash.

Preguntas frecuentes

¿Qué diferencia hay entre squash y fixup en git rebase -i?

Los dos funden el commit con el de la línea anterior; la diferencia es que squash te deja escribir el mensaje final y fixup descarta el del commit absorbido sin preguntar.

¿Cómo salgo del editor de rebase sin romper nada?

Depende del editor que tenga Git configurado. En vim es Esc, luego :wq y Enter para guardar y salir; en nano, Ctrl+O, Enter y Ctrl+X. Si borras todas las líneas de la lista y guardas, Git responde “Nothing to do” y cancela el rebase sin tocar tu rama, así que esa es la salida de emergencia limpia. Y si el rebase ya está en marcha, git rebase --abort te devuelve al estado inicial.

¿Puedo hacer git rebase interactivo sobre commits que ya he subido?

Sobre tu rama personal sí, subiéndola después con git push --force-with-lease. Sobre una rama compartida no, por lo que explico arriba en los errores comunes.

¿Cómo deshago un git rebase interactivo que salió mal?

Si sigue en curso, git rebase --abort. Si ya ha terminado, busca en el reflog la posición previa y vuelve a ella:

git reflog                     # localiza la línea anterior al rebase
git reset --hard HEAD@{5}      # devuelve la rama a esa posición

¿Puedo reordenar commits con git rebase -i?

Sí: cortas y pegas las líneas del editor en el orden que quieras y Git aplica los commits siguiendo la lista nueva, con un conflicto justo donde uno dependa de otro que ahora va detrás.