No puedo entender el comportamiento de git rebase --onto

169

He notado que los dos bloques de los siguientes comandos git tienen comportamientos diferentes y no entiendo por qué.

Tengo una Ay una Brama que divergen con unacommit

---COMMIT--- (A)
\
 --- (B)

Quiero cambiar la Brama en la última A(y tener el compromiso en la Brama)

---COMMIT--- (A)
         \
          --- (B)

No hay problema si lo hago:

checkout B
rebase A

Pero si lo hago:

checkout B
rebase --onto B A

No funciona en absoluto, no pasa nada. No entiendo por qué los dos comportamientos son diferentes.

Phpstorm git client usa la segunda sintaxis, y me parece completamente rota, por eso pido este problema de sintaxis.

Xmanoux
fuente

Respuestas:

412

tl; dr

La sintaxis correcta para rebase Bademás de Ausar git rebase --ontoen su caso es:

git checkout B
git rebase --onto A B^

o rebase Bencima de Acomenzar desde el commit que es el padre deB referencia con B^o B~1.

Si estás interesado en la diferencia entre git rebase <branch>y git rebase --onto <branch>sigue leyendo.

El rápido: git rebase

git rebase <branch>va a rebasar la rama que actualmente ha desprotegido, al que hace referencia HEAD, en la parte superior de la última cometen es accesible desde <branch>pero no de HEAD.
Este es el caso más común de rebase y podría decirse que requiere menos planificación por adelantado.

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                         \
              D---E (HEAD)                              D---E (HEAD)

En este ejemplo, Fy Gson confirmaciones a las que se puede acceder branchpero no desde HEAD. Decir git rebase branchque tomará D, esa es la primera confirmación después del punto de bifurcación, y volver a crearla (es decir, cambiar su padre ) encima de la última confirmación accesible desde, branchpero no desde HEAD, es decir G.

The Precise: git rebase --onto con 2 argumentos

git rebase --ontole permite volver a crear una base a partir de una confirmación específica . Le otorga un control exacto sobre lo que se está rebajando y dónde. Esto es para escenarios en los que necesita ser preciso.

Por ejemplo, imaginemos que necesitamos un rebase HEADprecisamente al Finicio E. Solo estamos interesados ​​en incorporar Fa nuestra rama de trabajo mientras que, al mismo tiempo, no queremos conservarla Dporque contiene algunos cambios incompatibles.

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                     \
              D---E---H---I (HEAD)                  E---H---I (HEAD)

En este caso, diríamos git rebase --onto F D. Esto significa:

Rebase el commit accesible desde HEADcuyo padre está Dencima F.

En otras palabras, cambie el padre de Ede Da F. La sintaxis de git rebase --ontoes entonces git rebase --onto <newparent> <oldparent>.

Otro escenario en el que esto es útil es cuando desea eliminar rápidamente algunas confirmaciones de la rama actual sin tener que hacer un rebase interactivo :

          Before                       After
    A---B---C---E---F (HEAD)        A---B---F (HEAD)

En este ejemplo, para eliminar Cy Ede la secuencia usted diría git rebase --onto B E, o volvería HEADa poner encima de Bdonde estaba el padre anterior E.

El cirujano: git rebase --onto con 3 argumentos

git rebase --ontopuede ir un paso más allá en términos de precisión. De hecho, le permite volver a crear una base arbitraria de confirmaciones encima de otra.

Aquí hay un ejemplo:

          Before                                     After
    A---B---C---F---G (branch)                A---B---C---F---G (branch)
             \                                             \
              D---E---H---I (HEAD)                          E---H (HEAD)

En este caso, queremos cambiar el rango exacto E---Hencima F, ignorando a dónde HEADapunta actualmente. Podemos hacerlo diciendo git rebase --onto F D H, lo que significa:

Rebase el rango de confirmaciones cuyo padre está Dpor Hencima F.

La sintaxis de git rebase --ontocon un rango de confirmaciones se convierte en git rebase --onto <newparent> <oldparent> <until>. El truco aquí es recordar que la confirmación a la que hace referencia <until>se incluye en el rango y se convertirá en la nueva una HEADvez que se complete el rebase.

Enrico Campidoglio
fuente
1
Buena respuesta. Solo una pequeña adición para el caso general: el <oldparent>nombre se desglosa si las dos partes del rango están en ramas diferentes. Generalmente es: "Incluya todas las confirmaciones a las que se pueda acceder, <until>pero excluya todas las confirmaciones a las que se pueda acceder <oldparent>".
musiKk
50
git rebase --onto <newparent> <oldparent>es la mejor explicación del comportamiento de Toronto que he visto.
ronkot
44
¡Gracias! Estaba un poco luchando con la --ontoopción, ¡pero esto lo hizo muy claro! Ni siquiera lo entiendo como no podría haberlo entendido antes: D Gracias por el excelente "tutorial" :-)
grongor
3
Si bien esta respuesta es excelente, creo que no cubre todos los casos posibles. La última forma de sintaxis también se puede usar para expresar un tipo de rebase más sutil. Saliendo del ejemplo en Pro Git (2ª Ed.), D no necesariamente tiene que ser un antepasado de H. En cambio, D y H también podrían estar comprometidos con un antepasado común; en este caso, Git descubrirá su antepasado común y repetición de ese ancestro a H en F.
Pastafarian
1
Esto fue útil. La página del manual no explica los argumentos en absoluto.
a544jh
61

Esto es todo lo que necesita saber para comprender --onto:

git rebase --onto <newparent> <oldparent>

Estás cambiando un padre en un commit, pero no estás proporcionando el sha del commit, solo el sha de su padre actual (antiguo).

cortar
fuente
44
Corto y fácil. En realidad, darme cuenta de que tengo que proporcionar el compromiso de los padres del que quiero rebajar en lugar de ese compromiso me llevó más tiempo.
Antoniossss
1
Un detalle importante es que elige un hijo de un padre antiguo de la rama actual porque una confirmación puede ser principal para muchas confirmaciones, pero cuando se restringe a la rama actual, la confirmación puede ser principal solo para una confirmación. En otras palabras, la relación de padres es única en la sucursal, pero no tiene que serlo si no especifica la sucursal.
Trismegistos
2
Para tener en cuenta: debe estar en la rama o agregar el nombre de la rama como el tercer parámetro git rebase --onto <newparent> <oldparent> <feature-branch>
Jason Portnoy
1
Esta respuesta es sorprendente, directo al punto según sea necesario en este hilo
John Culviner
13

En breve, dado:

      Before rebase                             After rebase
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                         \   \
          D---E---H---I (HEAD)                      \   E'---H' (HEAD)
                                                     \
                                                      D---E---H---I

git rebase --onto F D H

Que es lo mismo que (porque --ontotoma un argumento):

git rebase D H --onto F

Significa las confirmaciones de rebase en el rango (D, H] sobre F. Observe que el rango es exclusivo para la izquierda. Es exclusivo porque es más fácil especificar la primera confirmación escribiendo, por ejemplo, branchpara gitencontrar la primera confirmación divergente, branches decir, a Dcuál conduce H.

Caso de OP

    o---o (A)
     \
      o (B)(HEAD)

git checkout B
git rebase --onto B A

Se puede cambiar a un solo comando:

git rebase --onto B A B

Lo que parece un error aquí es la ubicación de lo Bque significa "mover algunas confirmaciones que conducen a la ramificación Ben la parte superior deB ". La pregunta es qué son "algunos commits". Si agrega una -ibandera, verá que es una confirmación única señalada por HEAD. La confirmación se omite porque ya está aplicada al --ontoobjetivo By, por lo tanto, no sucede nada.

El comando no tiene sentido en ningún caso donde el nombre de la rama se repite así. Esto se debe a que el rango de confirmaciones serán algunas confirmaciones que ya están en esa rama y durante el rebase se omitirán todas.

Explicación adicional y uso aplicable de git rebase <upstream> <branch> --onto <newbase>.

git rebase valores predeterminados

git rebase master

Se expande a:

git rebase --onto master master HEAD
git rebase --onto master master current_branch

Pago automático después de rebase.

Cuando se usa de manera estándar, como:

git checkout branch
git rebase master

No notará que después de que rebase se gitmueva brancha la confirmación de rebase más reciente y lo haga git checkout branch(vea el git refloghistorial). Lo que es interesante cuando el segundo argumento es commit hash cambio, el rebase del nombre de la rama todavía funciona, pero no hay una rama para mover, por lo que terminas en "HEAD separada" en lugar de ser extraído de la rama movida.

Omitir confirmaciones divergentes primarias.

El masteren --ontose toma desde el 1 de git rebaseargumento.

                   git rebase master
                              /    \
         git rebase --onto master master

Entonces, prácticamente puede ser cualquier otro commit o rama. De esta manera, puede limitar el número de confirmaciones de rebase al tomar las últimas y dejar las confirmaciones divergentes principales.

git rebase --onto master HEAD~
git rebase --onto master HEAD~ HEAD  # Expanded.

Se rebase única confirmación en punta por HEADa mastery terminan en "cabeza separada".

Evite los pagos explícitos.

El valor predeterminado HEADo el current_branchargumento se toma contextualmente del lugar en el que se encuentra. Es por eso que la mayoría de las personas pagan para bifurcar, lo que quieren volver a redactar. Pero cuando el segundo argumento de rebase se da explícitamente, no tiene que pagar antes de rebase para pasarlo de manera implícita.

(branch) $ git rebase master
(branch) $ git rebase master branch  # Expanded.
(branch) $ git rebase master $(git rev-parse --abbrev-ref HEAD)  # Kind of what git does.

Esto significa que puede volver a crear commits y ramas desde cualquier lugar. Entonces, junto con el pago automático después del rebase. no tiene que pagar por separado la rama rebaseada antes o después de la rebase

(master) $ git rebase master branch
(branch) $ # Rebased. Notice checkout.
WloHu
fuente
8

En pocas palabras, git rebase --ontoselecciona un rango de confirmaciones y las reajusta en la confirmación dada como parámetro.

Lea las páginas del manual git rebase, busque "en". Los ejemplos son muy buenos:

example of --onto option is to rebase part of a branch. If we have the following situation:

                                   H---I---J topicB
                                  /
                         E---F---G  topicA
                        /
           A---B---C---D  master

   then the command

       git rebase --onto master topicA topicB

   would result in:

                        H'--I'--J'  topicB
                       /
                       | E---F---G  topicA
                       |/
           A---B---C---D  master

En este caso le dice a git rebase las confirmaciones de topicAque topicBen la parte superior de master.

Gauthier
fuente
8

Para comprender mejor la diferencia entre git rebasey git rebase --ontoes bueno saber cuáles son los posibles comportamientos para ambos comandos. git rebasepermítanos mover nuestros commits encima de la rama seleccionada. Como aquí:

git rebase master

y el resultado es:

Before                              After
A---B---C---F---G (master)          A---B---C---F---G (master)
         \                                           \
          D---E (HEAD next-feature)                   D'---E' (HEAD next-feature)

git rebase --ontoEs más precisa. Nos permite elegir un compromiso específico donde queremos comenzar y también dónde queremos terminar. Como aquí:

git rebase --onto F D

y el resultado es:

Before                                    After
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                             \
          D---E---H---I (HEAD my-branch)                E'---H'---I' (HEAD my-branch)

Para obtener más detalles, le recomiendo que consulte mi propio artículo sobre git rebase - descripción general de Toronto

mujeronrails
fuente
@Makyen Claro, lo tendré en cuenta en el futuro :)
womanonrails
Entonces, podemos leer git rebase --onto F Dcomo hijo secundario del padre de D como F , ¿no?
Prihex
2

Para ontousted necesita dos ramas adicionales. Con ese comando, puede aplicar confirmaciones branchBque se basan en branchAotra rama, por ejemplo master. En el ejemplo a continuación branchBse basa branchAy desea aplicar los cambios de branchBon mastersin aplicar los cambios de branchA.

o---o (master)
     \
      o---o---o---o (branchA)
                   \
                    o---o (branchB)

mediante el uso de los comandos:

checkout branchB
rebase --onto master branchA 

obtendrá la siguiente jerarquía de confirmación.

      o'---o' (branchB)
     /
o---o (master)
     \
      o---o---o---o (branchA)
Michael Mairegger
fuente
1
¿Puede explicar un poco más, si queremos volver a crear un master, cómo es que se convierte en la rama actual? Si lo hiciera, rebase --onto branchA branchB¿eso pondría toda la rama maestra en la cabecera de la rama A?
Polimerasa
8
no debería ser esto checkout branchB: rebase --onto master branchA?
goldenratio
44
¿Por qué se ha votado esto? Esto no hace lo que dice que hace.
De Novo
Edité y arreglé la respuesta, para que la gente no tenga que romper primero sus ramas de repositorio y justo después de eso venga y lea los comentarios ... Ka
Kamafeather
0

Hay otro caso en el que git rebase --ontoes difícil de entender: cuando rebase en una confirmación resultante de un selector de diferencia simétrica (los tres puntos ' ...')

Git 2.24 (Q4 2019) hace un mejor trabajo al administrar ese caso:

Ver commit 414d924 , commit 4effc5b , commit c0efb4c , commit 2b318aa (27 de agosto de 2019) y commit 793ac7e , commit 359eceb (25 de agosto de 2019) por Denton Liu ( Denton-L) .
Ayudado por: Eric Sunshine ( sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) y Johannes Schindelin ( dscho) .
Ver commit 6330209 , commit c9efc21 (27 de agosto de 2019) y commit 4336d36 (25 de agosto de 2019) por Ævar Arnfjörð Bjarmason ( avar).
Ayudado por: Eric Sunshine ( sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) y Johannes Schindelin ( dscho) .
(Fusionada por Junio ​​C Hamano - gitster- en commit 640f9cd , 30 sep 2019)

rebase: avance rápido --ontoen más casos

Antes, cuando teníamos el siguiente gráfico,

A---B---C (master)
     \
      D (side)

correr ' git rebase --onto master... master side' daría como resultado Dque siempre se vuelva a rebajar, pase lo que pase.

En este punto, lea " ¿Cuáles son las diferencias entre doble punto ' ..' y triple punto" ..."en los rangos de compromiso de Git diff? "

https://sphinx.mythic-beasts.com/~mark/git-diff-help.png

Aquí: " master..." se refiere a master...HEAD, que es B: HEAD es un HEAD lateral (actualmente desprotegido): está haciendo un rebase en B.
¿Qué estás rebaseando? Cualquier commit que no esté en master, y accesible desde la siderama: solo hay un commit que se ajusta a esa descripción: D... que ya está encima de B!

Una vez más, antes de Git 2.24, tal rebase --ontoresultado Dresultaría siempre rebajado, pase lo que pase.

Sin embargo, el comportamiento deseado es que rebase debería notar que esto es rápido reenviable y hacer eso en su lugar.

Eso es similar a la rebase --onto B Adel OP, que no hizo nada.

Agregue detección a can_fast_forwardpara que este caso pueda detectarse y se realice un avance rápido.
En primer lugar, reescribe la función para usar gotos que simplifica la lógica.
A continuación, desde el

options.upstream &&
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)

se eliminaron las condiciones cmd_rebase, reintroducimos un sustituto en can_fast_forward.
En particular, verifica las bases de fusión de upstreamy headcorrige un caso de error en t3416.

El gráfico abreviado para t3416 es el siguiente:

        F---G topic
       /
  A---B---C---D---E master

y el comando fallido era

git rebase --onto master...topic F topic

Antes, Git vería que había una base de fusión ( C, resultado de master...topic), y la fusión y el uso eran iguales, por lo que devolvería incorrectamente 1, lo que indica que podríamos avanzar rápidamente. Esto provocaría que el gráfico rebase sea ABCFG"cuando esperábamos ABCG".

A rebase --onto C F topicsignifica cualquier commit posterior F , accesible por topicHEAD: eso es Gsolo, no en Fsí mismo.
El avance rápido en este caso incluiría Fen la rama rebaseada, lo cual está mal.

Con la lógica adicional, detectamos que la base de fusión ascendente y principal es F. Dado que into no lo es F, significa que no estamos cambiando el conjunto completo de commits de master..topic.
Como estamos excluyendo algunas confirmaciones, no se puede realizar un avance rápido y, por lo tanto, devolvemos 0 correctamente.

Agregue ' -f' a los casos de prueba que fallaron como resultado de este cambio porque no esperaban un avance rápido para que se forzara un rebase.

VonC
fuente