Je ne comprends pas le comportement de git rebase --onto

169

J'ai remarqué que les deux blocs de commandes git suivantes ont des comportements différents et je ne comprends pas pourquoi.

J'ai Aune Bbranche et une branche qui divergent avec unecommit

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

Je veux rebaser la Bbranche sur le dernier A(et avoir le commit sur la Bbranche)

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

Pas de problème si je fais:

checkout B
rebase A

Mais si je fais:

checkout B
rebase --onto B A

Ça ne marche pas du tout, rien ne se passe. Je ne comprends pas pourquoi les deux comportements sont différents.

Le client Phpstorm git utilise la deuxième syntaxe, et me semble donc complètement cassé, c'est pourquoi je demande ce problème de syntaxe.

Xmanoux
la source

Réponses:

412

tl; dr

La syntaxe correcte pour rebaser Ben plus d' Autiliser git rebase --ontodans votre cas est:

git checkout B
git rebase --onto A B^

ou rebase Ben plus de Acommencer à partir du commit qui est le parent deB référencé avec B^ou B~1.

Si vous êtes intéressé par la différence entre git rebase <branch>et git rebase --onto <branch>lisez la suite.

Le rapide: git rebase

git rebase <branch>va rebaser la branche que vous avez actuellement extraite, référencée par HEAD, en plus du dernier commit accessible depuis <branch>mais pas depuis HEAD.
C'est le cas le plus courant de rebasage et sans doute celui qui nécessite moins de planification à l'avance.

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

Dans cet exemple, Fet Gsont des commits accessibles depuis branchmais pas depuis HEAD. Dire git rebase branchprendra D, c'est le premier commit après le point de branchement, et le rebase (c'est-à-dire changer son parent ) au-dessus du dernier commit accessible depuis branchmais pas depuis HEAD, c'est-à-dire G.

The Precise: git rebase --onto avec 2 arguments

git rebase --ontovous permet de rebaser à partir d'un commit spécifique . Cela vous donne un contrôle exact sur ce qui est rebasé et où. C'est pour les scénarios où vous devez être précis.

Par exemple, imaginons que nous ayons besoin de rebaser HEADprécisément en plus de Fpartir de E. Nous sommes uniquement intéressés par l'intégration Fdans notre branche de travail alors que, en même temps, nous ne voulons pas conserver Dcar il contient des modifications incompatibles.

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

Dans ce cas, dirions-nous git rebase --onto F D. Ça signifie:

Rebase le commit accessible à partir HEADduquel le parent est Dau-dessus F.

En d'autres termes, changez le parent de Ede Dà F. La syntaxe de git rebase --ontoest alors git rebase --onto <newparent> <oldparent>.

Un autre scénario dans lequel cela s'avère utile est lorsque vous souhaitez supprimer rapidement certains commits de la branche actuelle sans avoir à faire un rebase interactif :

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

Dans cet exemple, pour supprimer Cet Ede la séquence, vous diriez git rebase --onto B E, ou rebasez HEADpar-dessus l' Bemplacement de l'ancien parent E.

The Surgeon: git rebase --onto avec 3 arguments

git rebase --ontopeut aller plus loin en termes de précision. En fait, cela vous permet de rebaser une plage arbitraire de commits par-dessus une autre.

Voici un exemple:

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

Dans ce cas, nous voulons rebaser la plage exacte E---Hpar-dessus F, en ignorant où HEADpointe actuellement. Nous pouvons le faire en disant git rebase --onto F D H, ce qui signifie:

Rebasage la gamme de commits dont le parent est Djusqu'à Hau - dessus de F.

La syntaxe de git rebase --ontoavec une plage de commits devient alors git rebase --onto <newparent> <oldparent> <until>. L'astuce ici est de se souvenir que le commit référencé par <until>est inclus dans la plage et deviendra le nouveau une HEADfois le rebase terminé.

Enrico Campidoglio
la source
1
Bonne réponse. Juste un petit ajout pour le cas général: le <oldparent>nom se décompose si les deux parties de la gamme sont sur des branches différentes. En général, c'est: "Inclut chaque commit accessible <until>mais exclut chaque commit accessible depuis <oldparent>."
musiKk
50
git rebase --onto <newparent> <oldparent>est la meilleure explication du comportement --onto que j'ai vu!
ronkot
4
Merci! J'avais un peu de mal avec l' --ontooption mais cela le rendait clair! Je ne comprends même pas comment je n'aurais pas pu le comprendre auparavant: D Merci pour l'excellent "tutoriel" :-)
grongor
3
Bien que cette réponse soit excellente, j'estime qu'elle ne couvre pas tous les cas possibles. La dernière forme de syntaxe peut également être utilisée pour exprimer un type de rebase plus subtil. Partant de l'exemple de Pro Git (2e éd.), D ne doit pas nécessairement être un ancêtre de H. Au lieu de cela, D et H pourraient également être commits avec un ancêtre commun - dans ce cas, Git découvrira leur ancêtre commun et rejouer de cet ancêtre à H sur F.
Pastafarian
1
Cela a été utile. La page de manuel n'explique pas du tout les arguments.
a544jh
61

C'est tout ce que vous devez savoir pour comprendre --onto:

git rebase --onto <newparent> <oldparent>

Vous changez de parent sur un commit, mais vous ne fournissez pas le sha du commit, seulement le sha de son (ancien) parent actuel.

rompre
la source
4
Court et facile. En fait, réaliser que je devais fournir un commit parent de celui que je veux rebase au lieu de ce commit m'a pris le plus de temps.
Antoniossss
1
Un détail important est que vous choisissez un enfant d'un ancien parent dans la branche actuelle car un commit peut être le parent de plusieurs commits, mais lorsque vous vous limitez à la branche actuelle, le commit ne peut être parent que pour un commit. En d'autres termes, la relation de parenté est unique sur la branche mais ne doit pas nécessairement l'être si vous ne spécifiez pas de branche.
Trismegistos
2
A noter: vous devez être dans la branche, ou ajouter le nom de la branche comme 3ème paramètre git rebase --onto <newparent> <oldparent> <feature-branch>
Jason Portnoy
1
Cette réponse est incroyable, va droit au but comme nécessaire dans ce fil
John Culviner
13

En bref, étant donné:

      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

Ce qui est identique à (car --ontoprend un argument):

git rebase D H --onto F

Les moyens rebasage commits dans la gamme (D, H] au - dessus de F. Avis de la gamme est exclusive à la main gauche. Il est exclusif , car il est plus facile de spécifier le 1er commit en tapant par exemple branchlaisser gittrouver le 1er divergé engagement de branchdire ce Dqui conduit à H.

Cas OP

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

git checkout B
git rebase --onto B A

Peut être changé en commande unique:

git rebase --onto B A B

Ce qui ressemble à une erreur ici, c'est le placement de Bce qui signifie "déplacer certains commits qui mènent à une branche Bau-dessus de B". La question est de savoir ce que sont «certains commits». Si vous ajoutez un -idrapeau, vous verrez qu'il s'agit d'un commit unique pointé par HEAD. Le commit est ignoré car il est déjà appliqué à la --ontocible Bet donc rien ne se passe.

La commande n'a aucun sens dans tous les cas où le nom de la branche est répété comme ça. Ceci est dû au fait que la plage de commits sera constituée de certains commits qui sont déjà dans cette branche et pendant le rebase, tous seront ignorés.

Explication supplémentaire et utilisation applicable de git rebase <upstream> <branch> --onto <newbase>.

git rebase les valeurs par défaut.

git rebase master

S'étend à:

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

Vérification automatique après rebase.

Lorsqu'il est utilisé de manière standard, comme:

git checkout branch
git rebase master

Vous ne remarquerez pas qu'après le rebase gitse déplace branchvers le dernier commit rebasé et le fait git checkout branch(voir git refloghistorique). Ce qui est intéressant quand le 2ème argument est le hachage de validation au lieu de cela, le rebase de nom de branche fonctionne toujours mais il n'y a pas de branche à déplacer, donc vous vous retrouvez dans "HEAD détaché" au lieu d'être extrait vers la branche déplacée.

Omettre les commits divergents principaux.

L' masterentrée --ontoest tirée du 1er git rebaseargument.

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

Il peut donc s'agir de n'importe quel autre commit ou branche. De cette façon, vous pouvez limiter le nombre de commits de rebase en prenant les derniers et en laissant les commits principaux divergents.

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

Est-ce que rebasage simple commettras pointé par HEADà masteret se retrouvent dans « décollée HEAD ».

Évitez les caisses explicites.

La valeur par défaut HEADou l' current_branchargument est contextuellement pris à partir de l'endroit où vous vous trouvez. C'est pourquoi la plupart des gens achètent vers la branche qu'ils veulent rebaser. Mais lorsque le deuxième argument de rebase est donné explicitement, vous n'avez pas besoin de vérifier avant le rebase pour le transmettre de manière implicite.

(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.

Cela signifie que vous pouvez rebaser les commits et les branches depuis n'importe quel endroit. Donc, avec le paiement automatique après rebase. vous n'avez pas à extraire séparément la branche rebasée avant ou après le rebase.

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

En termes simples, git rebase --ontosélectionne une plage de commits et les rebase sur le commit donné en paramètre.

Lisez les pages de manuel pour git rebase, recherchez "sur". Les exemples sont très bons:

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

Dans ce cas, vous dites à git de rebaser les commits de topicAà topicBpar-dessus master.

Gauthier
la source
8

Pour mieux comprendre la différence entre git rebaseet git rebase --ontoil est bon de savoir quels sont les comportements possibles pour les deux commandes. git rebasenous permettent de déplacer nos commits au-dessus de la branche sélectionnée. Comme ici:

git rebase master

et le résultat est:

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 --ontoest plus précis. Cela nous permet de choisir un commit spécifique où nous voulons commencer et aussi où nous voulons terminer. Comme ici:

git rebase --onto F D

et le résultat est:

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)

Pour obtenir plus de détails, je vous recommande de consulter mon propre article sur git rebase - vue d'ensemble

femme
la source
@Makyen Bien sûr, je garderai cela à l'esprit à l'avenir :)
womanonrails
Donc, nous pouvons lire git rebase --onto F Dcomme enfant défini du parent de D comme F , n'est-ce pas?
Prihex le
2

Car ontovous avez besoin de deux branches supplémentaires. Avec cette commande, vous pouvez appliquer des validations branchBbasées branchAsur une autre branche, par exemple master. Dans l'exemple ci branchB- dessous est basé sur branchAet vous souhaitez appliquer les modifications de branchBsur mastersans appliquer les modifications de branchA.

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

en utilisant les commandes:

checkout branchB
rebase --onto master branchA 

vous obtiendrez la hiérarchie de validation suivante.

      o'---o' (branchB)
     /
o---o (master)
     \
      o---o---o---o (branchA)
Michael Mairegger
la source
1
Pouvez-vous expliquer un peu plus, si nous voulons rebaser sur master, comment se fait-il que cela devienne la branche actuelle? Si vous le faites, est- rebase --onto branchA branchBce que cela mettrait toute la branche principale à la tête de la branche A?
Polymerase
8
ça ne devrait pas être checkout branchB: rebase --onto master branchA ?
goldenratio
4
Pourquoi cela a-t-il été voté? cela ne fait pas ce qu'il dit.
De Novo
J'ai modifié et corrigé la réponse, pour que les gens n'aient pas à casser d'abord leurs branches de dépôt et juste après cela venez lire les commentaires ... 🙄
Kamafeather
0

Il y a un autre cas où il git rebase --ontoest difficile de saisir: lorsque vous rebasez sur un commit résultant d'un sélecteur de différence symétrique (les trois points ' ...')

Git 2.24 (Q4 2019) gère mieux ce cas:

Voir commit 414d924 , commit 4effc5b , commit c0efb4c , commit 2b318aa (27 août 2019) et commit 793ac7e , commit 359eceb (25 août 2019) par Denton Liu ( Denton-L) .
Aide: Eric Sunshine ( sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) et Johannes Schindelin ( dscho) .
Voir commit 6330209 , commit c9efc21 (27 août 2019) et commit 4336d36 (25 août 2019) par Ævar Arnfjörð Bjarmason ( avar).
Aide: Eric Sunshine ( sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) et Johannes Schindelin ( dscho) .
(Fusionné par Junio ​​C Hamano - gitster- dans commit 640f9cd , 30 sept. 2019)

rebase: avance rapide --ontodans plus de cas

Avant, quand nous avions le graphique suivant,

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

en cours d'exécution ' git rebase --onto master... master side' aurait pour conséquence d' Dêtre toujours rebasé, quoi qu'il arrive.

À ce stade, lisez " Quelles sont les différences entre le double point ' ..' et le triple point" ..."dans les plages de validation Git diff? "

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

Ici: " master..." fait référence à master...HEAD, qui est B: HEAD est le côté HEAD (actuellement extrait): vous rebasez sur B.
Que rebasez-vous? Tout commit qui n'est pas dans master, et accessible depuis la sidebranche: il n'y a qu'un seul commit correspondant à cette description: D... qui est déjà au dessus de B!

Encore une fois, avant Git 2.24, cela rebase --ontoaurait pour conséquence d' Dêtre toujours rebasé, quoi qu'il arrive.

Cependant, le comportement souhaité est que le rebase doit remarquer qu'il s'agit d'une avance rapide et le faire à la place.

Cela s'apparente à celui rebase --onto B Ade l'OP, qui n'a rien fait.

Ajoutez la détection à can_fast_forwardafin que ce cas puisse être détecté et une avance rapide sera effectuée.
Tout d'abord, réécrivez la fonction pour utiliser gotos ce qui simplifie la logique.
Ensuite, depuis le

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

conditions ont été supprimées cmd_rebase, nous réintroduisons un substitut dans can_fast_forward.
En particulier, vérifier les bases de fusion de upstreamethead corriger un cas d'échec dans t3416.

Le graphique abrégé pour t3416 est le suivant:

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

et la commande défaillante était

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

Auparavant, Git voyait qu'il y avait une base de fusion ( C, résultat de master...topic), et la fusion et sur étaient les mêmes donc il retournait incorrectement 1, indiquant que nous pouvions avancer rapidement. Cela ferait en sorte que le graphique rebasé soit ABCFG«quand on attendait ABCG».

A rebase --onto C F topicsignifie tout commit après F , accessible par topicHEAD: c'est Gseulement, pas Flui-même.
Dans ce cas, une avance rapide inclurait Fla branche rebasée, ce qui est faux.

Avec la logique supplémentaire, nous détectons que la base de fusion en amont et en tête est F. Comme on ne l'est pas F, cela signifie que nous ne rebasons pas l'ensemble complet des commits à partir de master..topic.
Puisque nous excluons certains commits, une avance rapide ne peut pas être effectuée et nous renvoyons donc correctement 0.

Ajoutez « -f» aux cas de test qui ont échoué à la suite de ce changement car ils ne s'attendaient pas à une avance rapide afin qu'un rebase soit forcé.

VonC
la source