Si llevas un tiempo usando Git, seguramente te habrás encontrado con un historial lleno de commits poco útiles, mensajes tipo “WIP”, pruebas rápidas o incluso commits vacíos creados solo para disparar un hook o un pipeline. En ese momento te miras el log y piensas: “esto no hay quien lo entienda”. La buena noticia es que no estás solo: a todos nos pasa, y para eso existe git rebase.
Lo interesante es que, bien usado, git rebase te permite “limpiar” el historial de commits, dejarlo lineal, sin ruido y mucho más fácil de revisar. Puedes borrar commits de prueba, fusionar varios en uno solo, reordenarlos o corregir mensajes de commit, e incluso eliminar cambios del remoto mediante un push forzado. Vamos a ver con calma, y con ejemplos concretos, cómo usar rebase para que tu historial pase de caos total a algo ordenado y legible.
Qué es realmente Git Rebase y por qué afecta al historial
Cuando hablamos de rebase, hablamos literalmente de cambiar el punto de partida de una rama. Git toma los commits de tu rama y los “reproduce” uno a uno sobre otra base (normalmente la rama main o una rama remota actualizada). Es como si volvieras atrás en el tiempo y hubieras empezado tu trabajo desde un commit distinto, pero conservando los cambios.
Imagina que tu proyecto tiene esta historia en la rama principal: A – B – C. Tú creas una rama de funcionalidad y encima haces dos commits: D – E. Mientras tanto, en main alguien añade un commit nuevo F. Sin rebase, tus ramas quedarían algo así:
A---B---C---F (main)
A---B---C---D---E (feature)
Si haces un merge clásico de tu rama de feature en main, lo que obtienes es un commit extra de merge, que a menudo genera historial enmarañado con varias líneas cruzadas. Con rebase, en cambio, Git coge tus commits D y E y los vuelve a aplicar sobre F:
A---B---C---F---D'---E' (feature reescrita)
Esos D’ y E’ no son exactamente los mismos commits que D y E: son “copias” con nuevos identificadores (hashes). Por eso decimos que rebase reescribe el historial. El resultado es un log más lineal, sin commits de merge intermedios que solo añaden ruido.

Por qué te interesa tener un historial de commits limpio
Un historial ordenado no es solo cuestión de estética. Un log limpio facilita mucho el trabajo diario: puedes ver de un vistazo qué se hizo, cuándo y por qué, sin tener que saltar entre commits de merge vacíos o mensajes de tipo “arreglo rápido” sin contexto.
Cuando las ramas se mezclan mediante merges constantes desde main, acabas con un montón de commits como “Merge branch ‘main’ into feature-x” que no aportan información real sobre cambios de código. Esto complica tareas como:
- Localizar el commit donde se introdujo un bug usando herramientas para comparar archivos, porque el historial está lleno de merges irrelevantes.
- Revisar pull requests, ya que tienes que saltar entre commits redundantes para ver qué cambió de verdad.
- Entender la evolución de una funcionalidad, especialmente si se ha desarrollado a lo largo de muchos pequeños commits de prueba.
Git rebase es la herramienta ideal para “pulir” todo ese ruido antes de compartir la rama con el resto del equipo. Es como pasar el historial por la lavadora: te quedas con los cambios importantes, bien agrupados y con mensajes claros.
Diferencias clave entre git merge y git rebase
Para entender bien qué estás haciendo cuando usas rebase, merece la pena comparar su comportamiento con el de git merge. Ambos sirven para integrar cambios, pero de formas distintas y con implicaciones importantes para el historial.
Con merge, Git crea un nuevo commit de unión que tiene dos padres: la punta de tu rama y la de la rama con la que estás fusionando. El historial resultante preserva exactamente la secuencia original de commits, pero puede acabar lleno de ramas paralelas y merges.
Con rebase, en lugar de crear un commit de merge, Git toma tus commits y los aplica uno a uno sobre la nueva base. Eso genera nuevos commits con nuevos hashes, aunque los cambios de código sean los mismos.
En la práctica:
- Merge mantiene el historial tal cual sucedió, con todos sus cruces y merges.
- Rebase genera un historial lineal y limpio, como si todo hubiera ocurrido en línea recta.
La elección no es “uno es mejor que el otro” sino para qué situación es más adecuado cada uno. Para combinar ramas ya compartidas y cerradas, el merge suele ser más seguro. Para mantener ordenadas ramas de trabajo locales o antes de abrir un pull request, rebase brilla.

Casos en los que rebase brilla: actualizar y pulir ramas
Hay dos escenarios en los que la mayoría de desarrolladores usan rebase a diario: mantener su rama de trabajo al día con main y limpiar commits antes de compartirlos. Vamos a verlos con más detalle.
Actualizar tu rama de feature con los últimos cambios de main
Estás trabajando en una nueva funcionalidad en tu rama, pero mientras tanto tus compañeros siguen haciendo commits en main. Si quieres integrar sus cambios, una opción es hacer:
git checkout tu-rama-feature
git merge main
Esto funciona, pero genera un commit de merge cada vez que sincronizas, y a la larga provoca un historial lleno de merges repetitivos. En cambio, si haces:
git checkout tu-rama-feature
git rebase main
Git moverá tus commits encima del último estado de main, como si hubieras empezado la rama después de esos cambios. Obtendrás un historial lineal, sin commits de merge intermedios, y la revisión será más clara.
Limpiar tus commits antes de abrir un pull request
Es muy habitual que, durante el desarrollo de una funcionalidad, termines con commits como “arreglo typo”, “prueba pipeline”, “más cambios en login”, etc. Eso está bien mientras trabajas, pero no es el tipo de historial que quieres enseñar cuando abres una PR.
Rebase interactivo te permite reorganizar y agrupar esos commits en algo más lógico. Por ejemplo, en lugar de esto:
- WIP: añadir login
- Más cambios login
- Corregir tests login
- Arreglar typo variable
puedes terminar con un único commit bien descrito:
- Añadir funcionalidad completa de login de usuario con tests
Ese “pulido” del historial hace que los revisores entiendan mucho mejor qué aporta cada commit y simplifica la búsqueda de problemas en el futuro.
Rebase interactivo para reescribir historial
La versión básica de rebase simplemente mueve tus commits a otra base. Pero la joya de la corona es el rebase interactivo, que te abre un editor con la lista de commits recientes para que decidas qué hacer con cada uno.
La forma típica de iniciarlo es indicando cuántos commits hacia atrás quieres “tocar” desde tu HEAD actual. Por ejemplo:
git rebase -i HEAD~6
Este comando le dice a Git: “coge los últimos 6 commits de esta rama y prepárame un rebase interactivo”. Git abrirá tu editor por defecto (Vim, Nano, VS Code, etc.) con algo parecido a:
pick 7ed9c6e update version
pick ecb7ef3 empty commit 1
pick a323615 empty commit 2
pick 2c3d41d empty commit 3
pick d53c00f empty commit 4
pick 22dcc79 empty commit 5
# Rebase 549dd76..22dcc79 onto 549dd76 (6 commands)
#
# Commands:
# p, pick <commit> = usar commit
# r, reword <commit> = usar commit, pero editar el mensaje
# e, edit <commit> = usar commit y parar para modificarlo
# s, squash <commit> = mezclar en el commit anterior
# f, fixup <commit> = como squash, pero descartando el mensaje
# x, exec <command> = ejecutar comando de shell
# b, break = parar aquí el rebase
# d, drop <commit> = eliminar commit
# …
Fíjate en un detalle importante: el orden en este archivo es inverso a lo que ves en git log. En el fichero, el primer commit listado (7ed9c6e) es el más antiguo de los 6, y el último (22dcc79) es el más reciente. Esto es clave para evitar confusiones cuando vayas a borrar o reordenar.
Eliminar commits de prueba o vacíos del historial local
Supongamos que, para probar un hook de Git, has estado haciendo commits vacíos con la opción –allow-empty. Por ejemplo:
git commit -m "commit with no changes" --allow-empty
Esta opción permite crear un commit incluso cuando no hay cambios en los archivos, algo útil para pruebas, pero que ensucia el historial con commits sin valor real. Imagina que tu log se ve así:
git log --pretty=oneline --abbrev-commit
Y obtienes:
22dcc79 (HEAD -> main, origin/main, origin/HEAD) empty commit 5
d53c00f empty commit 4
2c3d41d empty commit 3
a323615 empty commit 2
ecb7ef3 empty commit 1
7ed9c6e update version
En este escenario, quieres quedarte solo con el commit útil “update version” y quitar del medio todos esos empty commit que solo eran pruebas. Para ello, lanzas el rebase interactivo sobre los últimos 6 commits:
git rebase -i HEAD~6
Se abre el editor con los 6 commits. Tu objetivo es eliminar las líneas que corresponden a los commits vacíos. Es decir, dejas solo algo así:
pick 7ed9c6e update version
# Rebase 549dd76..22dcc79 onto 549dd76 (6 commands)
# … resto de comentarios …
Tal y como indica la ayuda que ves en el propio fichero, si borras una línea, ese commit desaparecerá del historial en la nueva versión reescrita. Al guardar y cerrar el editor, Git reproducirá solo el commit “update version” y descartará los demás.
Subir la nueva historia a origin: el uso del push forzado
Hasta ahora, todos los cambios de rebase se han hecho en tu copia local del repositorio. Si quieres que esa limpieza se refleje también en el remoto (por ejemplo, en origin/main), tendrás que sobrescribir el historial remoto con el nuevo.
Esto se hace con un push forzado. Una forma común de indicarlo es usando el signo “+” delante del nombre de la rama al hacer push:
git push origin +main
Ese signo más le dice a Git que ignore la divergencia de historial y sobrescriba la rama remota con tu versión reescrita. Si intentaras hacer simplemente:
git push origin main
Git te advertiría de que tu historial local no es descendiente directo del remoto (porque has reescrito commits con rebase) y rechazaría el push para evitar pérdidas de información.
Es importante tener claro que, tras este push forzado, los commits eliminados dejarán de estar accesibles desde origin/main. Siguen pudiendo estar en reflogs o copias locales de otros compañeros, pero a efectos prácticos has limpiado el historial remoto.
Advertencia seria: reescribir ramas compartidas no es un juego
Reescribir historial suena muy bien para ordenarlo todo, pero tiene una consecuencia clave: al crear nuevos commits con nuevos hashes, desincronizas a cualquiera que ya hubiera basado su trabajo en los commits antiguos. Por eso existe la famosa regla de oro:
No hagas rebase de ramas que ya estén compartidas y que otros estén usando activamente.
En la práctica, esto significa que es seguro usar rebase sobre:
- Ramas locales de feature que aún no has publicado o que solo usas tú.
- Commits recientes de los que sabes que nadie más depende todavía.
Y es bastante mala idea hacerlo sobre:
- main, develop o cualquier rama “oficial” del repositorio que sirva de base a varias personas.
- Ramas de trabajo compartidas en las que haya más de un desarrollador haciendo commits.
Si reescribes el historial de una rama compartida y luego haces un push forzado, los demás compañeros se encontrarán con que sus ramas apuntan a commits que ya no existen en el remoto. Tendrán que hacer operaciones más avanzadas (como rebase sobre la nueva historia o reset) para arreglar el lío.
Force push: mejor con cinturón de seguridad
En muchos casos, después de un rebase tendrás que hacer un push forzado. La opción clásica es:
git push --force origin tu-rama
Sin embargo, esta variante pisa lo que haya en el remoto sin preguntar. Para evitar sobrescribir por accidente trabajo de otros, Git ofrece una opción mucho más recomendable: –force-with-lease.
Cuando haces:
git push --force-with-lease origin tu-rama
Git comprueba primero que el remoto sigue estando en el estado que tú creías cuando hiciste tu último pull o fetch. Si detecta que alguien ha hecho push de nuevos commits en esa rama desde entonces, el push se rechaza y no se fuerza, evitando así cargarte cambios ajenos.
La idea es combinar rebase + force push siempre con cabeza: solo en ramas que controles tú y usando, siempre que sea posible, –force-with-lease como capa adicional de seguridad.
Qué hacer cuando un rebase sale mal: reflog al rescate
A todos nos ha pasado: empiezas un rebase interactivo, borras o cambias algo sin querer, resuelves un conflicto mal y de repente parece que has perdido parte de tu trabajo. Antes de entrar en pánico, recuerda que Git tiene un as en la manga: git reflog.
El reflog es un registro local de todos los movimientos de HEAD: commits nuevos, resets, rebases, merges… Gracias a él, puedes localizar dónde estaba tu rama antes de liarla y volver a ese punto con un reset duro.
El flujo típico sería:
git reflog
Ahí verás una lista de entradas con algo como:
abc1234 HEAD@{0}: rebase terminado
def5678 HEAD@{1}: checkout: moving from main to main
...
Identificas el commit donde estabas antes de empezar el rebase (por ejemplo, def5678) y vuelves a él con:
git reset --hard def5678
De este modo, deshaces por completo el rebase y recuperas el estado anterior. También puedes abortar un rebase en curso (si estás en medio del proceso y no has terminado) simplemente con:
git rebase --abort
Este comando deja tu rama como estaba justo antes de iniciar el rebase actual, perfecto para cuando aparezcan conflictos que no quieres o ves que no es buen momento para seguir.
Git pull vs git pull –rebase: pequeñas diferencias, gran impacto
Otro punto que suele generar dudas es la diferencia entre git pull normal y git pull –rebase. Ambos actualizan tu rama local desde el remoto, pero lo hacen de forma distinta.
Cuando ejecutas:
git pull
Git hace dos pasos: git fetch para traer los cambios del remoto y luego un git merge para combinarlos con tu rama actual. Esto puede crear un commit de merge adicional si tienes commits locales por encima del remoto, lo que genera de nuevo historial con merges innecesarios.
En cambio, si ejecutas:
git pull --rebase
Git hace el fetch y luego reproduce tus commits locales por encima de la versión actualizada del remoto. El resultado es similar a si hubieras hecho git fetch seguido de git rebase origin/main, y mantiene un historial más limpio y lineal.
Por eso muchos equipos configuran Git para que, por defecto, use rebase al hacer pull. Es una forma sencilla de evitar commits de merge automáticos que no aportan gran cosa.
Eliminar archivos o imágenes del historial: idea general
A veces el problema no es un commit feo, sino un archivo que nunca debió llegar al repositorio: una imagen equivocada, credenciales, ficheros enormes, etc. La tentación es ir a GitHub y buscar el icono de la papelera, pero si este aparece desactivado y muestra mensajes como “debes estar en una rama”, es porque GitHub no te permite borrar así el historial.
La forma correcta suele pasar por usar reescritura de historial desde tu máquina local (con rebase interactivo o herramientas como git filter-repo) y luego hacer un push forzado. En GitHub Desktop también puedes gestionar ramas y commits, pero la operación de eliminar completamente un archivo de todos los commits implica reescribir el historial de manera similar a lo que estamos viendo aquí.
En resumen, si subes una imagen equivocada o un archivo sensible y quieres que “desaparezca” de la historia, no basta con borrarlo en el último commit: hay que reescribir los commits donde se introdujo y después forzar el push. Es una operación delicada y conviene hacerla con calma y coordinándote con tu equipo.
Flujo práctico para practicar rebase sin romper nada
La mejor forma de adquirir soltura con rebase es practicar en un entorno controlado, sin miedo a romper el trabajo de nadie. Un flujo sencillo sería:
- Crea una rama nueva desde main con git checkout -b mi-rama-pruebas.
- Haz varios commits pequeños (pueden ser cambios triviales o archivos de prueba).
- Simula que main avanza haciendo nuevos commits allí (o pidiéndole a alguien que los haga).
- Vuelve a tu rama de pruebas y ejecuta git rebase main para ver cómo se recolocan tus commits.
- Prueba un git rebase -i HEAD~3 para combinar, renombrar o eliminar alguno de los últimos commits.
Con este ejercicio comprobarás cómo cambia el historial antes y después, cómo responde Git en caso de conflictos y cómo puedes recuperar estados anteriores con reflog si hace falta. Cuanta más práctica tengas en local, más confianza tendrás cuando toque aplicar estas técnicas en ramas reales de proyecto.
Dominar Git rebase te permite transformar un historial caótico, lleno de commits vacíos, merges innecesarios y mensajes sin sentido, en una secuencia clara y legible de cambios significativos; usando rebase interactivo, borrando commits de prueba, agrupando trabajo disperso, actualizando ramas de forma lineal y apoyándote en herramientas como reflog y el push forzado con –force-with-lease, puedes mantener tu repositorio en un estado mucho más sano y profesional, siempre que recuerdes aplicar estas técnicas solo en ramas bajo tu control y avisar al equipo antes de reescribir la historia compartida.