git rebase --onto parece una variante complicada de git rebase. Durante mucho tiempo lo evité por esa razón. Sin embargo, esta opción responde a una pregunta muy concreta: ¿cómo se mueve solo una parte de una rama a una base nueva sin arrastrar los commits que están debajo?

Este caso aparece cuando una rama se creó desde la rama equivocada, cuando hay que reorganizar una pila de ramas o cuando varios commits útiles están atrapados en medio de un historial. Un rebase normal decide de forma implícita qué debe repetir. Con --onto, se definen explícitamente la base nueva, el límite de los commits que se excluyen y la rama que se mueve.

Por qué un rebase normal no siempre basta

Tomemos dos ramas de funcionalidad apiladas. feature/ui se creó encima de feature/auth porque al principio la interfaz dependía de la autenticación:

A---B                    main
     \
      C---D              feature/auth
           \
            E---F        feature/ui

Los commits E y F finalmente se vuelven independientes. Queremos abrir su pull request sin los commits C y D de autenticación.

Con este grafo concreto, ejecutar git rebase main desde feature/ui no repite ningún commit. main todavía apunta a B, que ya es un ancestro de feature/ui. Por tanto, Git indica que la rama está actualizada y deja su punta en F. La orden no separa feature/ui de feature/auth.

Si main hubiera avanzado mientras tanto, el comportamiento sería diferente:

A---B---G---H            main
     \
      C---D              feature/auth
           \
            E---F        feature/ui

En este segundo grafo, git rebase main sí seleccionaría C, D, E y F, porque esos cuatro commits no están en main, y los repetiría después de H:

      C---D                      feature/auth
     /
A---B---G---H                    main
             \
              C'---D'---E'---F' feature/ui

El historial cambiaría de lugar, pero la dependencia de feature/auth seguiría presente. En ambas situaciones, tanto si la rama principal no cambió como si avanzó, un rebase normal no puede seleccionar únicamente E y F.

La necesidad real es más precisa: tomar los commits posteriores a feature/auth, que son E y F, y repetirlos sobre main. Ese es exactamente el papel de --onto.

git switch feature/ui
git rebase --onto main feature/auth

El resultado es:

      C---D              feature/auth
     /
A---B                    main
     \
      E'---F'            feature/ui

Los apóstrofos importan. E' y F' contienen los mismos cambios que E y F, pero son commits nuevos con identificadores nuevos. Un rebase reescribe el historial. No mueve físicamente los commits existentes.

La fórmula que conviene recordar

La forma completa es:

git rebase --onto <base-nueva> <frontera> <rama>

Se puede leer así:

Toma los commits accesibles desde <rama> pero no desde <frontera>, repítelos sobre <base-nueva> y mueve <rama> al resultado.

En otras palabras, como primera aproximación, Git selecciona este rango:

<frontera>..<rama>

Después lo aplica sobre <base-nueva>. Esta diferencia es el verdadero valor de --onto: la frontera que determina qué se repite puede ser distinta de la base que determina dónde se repite.

En nuestro ejemplo:

git rebase --onto main feature/auth feature/ui
  • main es el destino;
  • feature/auth es la frontera excluida;
  • feature/ui es la rama que se mueve.

El tercer argumento es opcional. Sin él, Git utiliza la rama actual. Prefiero escribirlo en un script o en una orden copiada en la documentación, porque la intención queda visible sin depender del HEAD actual.

La documentación oficial de git rebase describe --onto como el punto de partida donde se crean los commits nuevos. «Frontera» no es un término oficial de Git, pero es el modelo mental más útil para no invertir los argumentos.

Caso 1: separar una rama de una pila

Es el caso anterior y probablemente el más habitual en un equipo que utiliza ramas apiladas.

main:          A---B
feature/auth:       \---C---D
feature/ui:                  \---E---F

Para separar feature/ui de feature/auth:

git rebase --onto main feature/auth feature/ui

El rango feature/auth..feature/ui solo contiene E y F. Git los repite sobre main y después actualiza feature/ui.

Esta operación es útil cuando se apilan dos pull requests para avanzar más rápido y más tarde la segunda puede integrarse de forma independiente. Evita cerrar la rama, crear otra desde main y seleccionar los commits uno a uno con cherry-pick.

Caso 2: corregir una rama creada desde la base equivocada

La rama feature/export debía partir de main, pero se creó desde release/1.x:

A---B---C                    main
     \
      R1---R2               release/1.x
             \
              X---Y         feature/export

Queremos conservar X e Y sin transportar R1 y R2 a main:

git rebase --onto main release/1.x feature/export

El rango release/1.x..feature/export aísla X e Y. Después del rebase:

A---B---C---X'---Y'         feature/export
     \
      R1---R2               release/1.x

Hay que elegir una frontera que corresponda realmente al punto por debajo del cual se quiere cortar. Si release/1.x recibió commits nuevos después de crear feature/export, su nombre puede haber dejado de representar ese límite. En ese caso, utiliza el commit explícito de divergencia o compruébalo con:

git merge-base release/1.x feature/export

Una referencia de rama resulta práctica, pero un identificador de commit puede ser más exacto.

Caso 3: extraer los últimos commits hacia otra base

Supongamos que una rama contiene cinco commits, pero los tres últimos forman una corrección autónoma que debe publicarse en release/2.x:

A---B                         release/2.x
     \
      C---D---E---F---G       feature/payments

Desde feature/payments, la frontera HEAD~3 identifica el padre de los tres commits que se van a repetir:

git switch feature/payments
git rebase --onto release/2.x HEAD~3

Git repite E, F y G sobre release/2.x. Después, la rama feature/payments apunta a esa serie nueva.

Este caso se parece a un cherry-pick de varios commits, pero la intención es distinta. cherry-pick copia commits sobre la rama actual sin mover la rama de origen. rebase --onto trasplanta un rango y actualiza la rama reorganizada. Si el objetivo es conservar intacta la rama original, crea primero una rama nueva o utiliza cherry-pick.

Caso 4: quitar un rango de commits intermedios

--onto también puede eliminar varios commits contiguos de una rama sin abrir un rebase interactivo.

A---B---C---D---E            topic

Imaginemos que C y D son pruebas que deben desaparecer, mientras que E debe conservarse. Hay que repetir todo lo que viene después de D sobre B:

git rebase --onto B D topic

El resultado es:

A---B---E'                   topic

La orden no dice directamente «elimina C y D». Dice «toma D..topic y repite ese rango sobre B». Como solo E pertenece al rango, C y D dejan de formar parte del nuevo historial de topic.

Para eliminar commits dispersos, reordenar commits o cambiar sus mensajes, git rebase -i sigue siendo más claro. --onto resulta especialmente adecuado cuando el corte forma un rango continuo y sus dos extremos están claros.

Comprobar la selección antes de reescribir

La dificultad de --onto no está en el destino. Está en la selección. Antes de ejecutar la orden, siempre muestro el rango que Git debe repetir:

git log --oneline --reverse feature/auth..feature/ui

Si la salida contiene E y F, y nada más, la frontera es correcta. Después se puede inspeccionar el grafo completo:

git log --oneline --graph --decorate --all

También creo una referencia de seguridad antes de una reescritura importante:

git branch backup/feature-ui feature/ui
git rebase --onto main feature/auth feature/ui

Esta rama no copia archivos. Solo conserva un puntero a la punta anterior, suficiente para comparar o recuperar el historial previo.

Después de la operación, git range-diff permite comparar las dos series de parches en lugar de sus identificadores:

git range-diff feature/auth..backup/feature-ui main..feature/ui

Deberían aparecer los mismos cambios, representados por commits nuevos y por las posibles resoluciones de conflictos.

Resolver conflictos y volver atrás

Como cualquier rebase, --onto repite los commits uno a uno. Puede aparecer un conflicto si la base nueva modificó las mismas líneas.

Después de resolver los archivos:

git add <archivos-resueltos>
git rebase --continue

Para cancelar la operación en curso y recuperar el estado inicial:

git rebase --abort

Si el rebase ya terminó, la rama de seguridad es la forma más sencilla de recuperarse. Sin ella, git reflog suele permitir encontrar la punta anterior de la rama:

git reflog feature/ui

También conviene empezar con un árbol de trabajo limpio. Mezclar un rebase con cambios sin confirmar añade un riesgo innecesario. Existe --autostash, pero prefiero un commit temporal o un stash explícito cuya existencia no dependa de una opción olvidada.

El coste real: los commits cambian de identidad

Repetir un commit sobre una base diferente crea un commit nuevo, aunque su diff sea idéntico. Su identificador cambia y todos sus descendientes cambian con él.

En una rama personal, normalmente eso es lo que se busca. En una rama que ya se comparte, hay que avisar a los demás colaboradores. Su historial local todavía apunta a los commits anteriores.

Si hay que actualizar la rama remota, utiliza:

git push --force-with-lease origin feature/ui

--force-with-lease rechaza la sobrescritura si la rama remota avanzó de una forma que el repositorio local desconoce. Esta protección no convierte la reescritura de un historial público en una operación trivial, pero evita borrar en silencio el trabajo que otra persona haya enviado.

Hay otro límite. Un rebase normal no conserva automáticamente la topología de los commits de merge. Si la rama contiene merges que aportan información útil, estudia --rebase-merges y comprueba con cuidado el grafo resultante.

Cuándo usar –onto y cuándo elegir otra cosa

NecesidadOrden adecuada
Actualizar todos los commits de una rama sobre maingit rebase main
Mover solo los commits posteriores a una fronteragit rebase --onto
Copiar algunos commits sin mover su rama de origengit cherry-pick
Reordenar, fusionar o editar commitsgit rebase -i
Deshacer un cambio sin reescribir un historial compartidogit revert

Por tanto, --onto no es tanto una versión avanzada de rebase como una herramienta de selección. Resulta útil en cuanto la base de destino deja de ser la misma referencia que la frontera de selección.

Esta precisión importa especialmente cuando Git es la fuente de verdad de los despliegues, como explico en GitOps sin Kubernetes: Django con doco-cd, SOPS y age. Un historial legible permite saber qué cambios llegaron realmente a una rama de entrega, sin arrastrar commits de una dependencia temporal.

Conclusión

La forma más fiable de entender git rebase --onto cabe en una frase: repetir <frontera>..<rama> sobre <base-nueva>.

Con este modelo, los casos principales se vuelven mecánicos: separar una rama apilada, corregir una base equivocada, trasplantar los últimos commits o retirar un rango intermedio. La orden sigue siendo potente porque reescribe el historial. Muestra el rango antes del rebase, conserva una rama de seguridad, compara los parches al terminar y reserva la reescritura para ramas cuyo uso compartido controles.