Ich kann das Verhalten von Git Rebase --onto nicht verstehen

166

Ich habe festgestellt, dass die beiden Blöcke der folgenden Git-Befehle unterschiedliche Verhaltensweisen haben, und ich verstehe nicht, warum.

Ich habe einen Aund einen BZweig, der von einem abweichtcommit

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

Ich möchte den BZweig auf den letzten zurücksetzen A(und das Commit auf dem BZweig haben).

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

Kein Problem, wenn ich:

checkout B
rebase A

Aber wenn ich es mache:

checkout B
rebase --onto B A

Es funktioniert überhaupt nicht, nichts passiert. Ich verstehe nicht, warum die beiden Verhaltensweisen unterschiedlich sind.

Der Phpstorm Git-Client verwendet die zweite Syntax und scheint mir daher völlig kaputt zu sein. Deshalb frage ich nach diesem Syntaxproblem.

Xmanoux
quelle

Antworten:

409

tl; dr

Die richtige Syntax, die zusätzlich Bzur AVerwendung git rebase --ontoin Ihrem Fall neu erstellt werden muss, lautet:

git checkout B
git rebase --onto A B^

oder Rebase zusätzlich Bzu Adem Commit, das das übergeordnete Element von ist, auf dasB mit B^oder verwiesen wird B~1.

Wenn Sie sich für den Unterschied interessieren git rebase <branch>und git rebase --onto <branch>weiterlesen.

Die Quick: Git Rebase

git rebase <branch>wird den Zweig, auf den Sie gerade ausgecheckt haben und auf den verwiesen wird HEAD, zusätzlich zu dem letzten Commit, das von, <branch>aber nicht von erreichbar ist, neu starten HEAD.
Dies ist der häufigste Fall einer Neugründung und wahrscheinlich derjenige, der weniger Planung im Voraus erfordert.

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

In diesem Beispiel Fund Gsind verpflichtet , die von erreichbar ist , branchaber nicht aus HEAD. Das Sprichwort git rebase branchnimmt D, das ist das erste Commit nach dem Verzweigungspunkt, und setzt es neu (dh ändert sein übergeordnetes Element ) auf das letzte Commit, das von, branchaber nicht von erreichbar HEADist G.

The Precise: git rebase --onto mit 2 Argumenten

git rebase --ontoErmöglicht das erneute Basieren ab einem bestimmten Commit . Sie erhalten die genaue Kontrolle darüber, was und wo neu basiert wird. Dies ist für Szenarien gedacht, in denen Sie präzise sein müssen.

Stellen wir uns zum Beispiel vor, wir müssen HEADgenau auf der Grundlage von neu Fstarten E. Wir sind nur daran interessiert, Fin unsere Arbeitsbranche zu kommen, während wir gleichzeitig nicht behalten möchten, Dweil sie einige inkompatible Änderungen enthält.

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

In diesem Fall würden wir sagen git rebase --onto F D. Das heisst:

Rebase die aus erreichbar begehen , HEADderen Stamm Dauf der Oberseite F.

Mit anderen Worten, ändern Sie das übergeordnete Element von Evon Dnach F. Die Syntax von git rebase --ontoist dann git rebase --onto <newparent> <oldparent>.

Ein weiteres Szenario, in dem dies nützlich ist, besteht darin, dass Sie einige Commits schnell aus dem aktuellen Zweig entfernen möchten, ohne eine interaktive Rebase durchführen zu müssen :

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

In diesem Beispiel würden Sie sagen , um zu entfernen Cund Eaus der Sequenz zu entfernen git rebase --onto B E, oder HEADauf der Stelle, Ban der sich der alte Elternteil befand , neu zu gründen E.

Der Chirurg: Git Rebase - mit 3 Argumenten

git rebase --ontokann in Bezug auf Präzision noch einen Schritt weiter gehen. Tatsächlich können Sie einen beliebigen Bereich von Commits auf einen anderen zurücksetzen .

Hier ist ein Beispiel:

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

In diesem Fall möchten wir den genauen Bereich E---Hüber dem Basiswert neu festlegen Fund dabei ignorieren, auf welche Stelle HEADgerade verwiesen wird. Wir können das tun, indem wir sagen git rebase --onto F D H, was bedeutet:

Rebase den Bereich von Commits , dessen Stamm Dbis zu Hoben auf F.

Die Syntax von git rebase --ontomit einer Reihe von Commits wird dann git rebase --onto <newparent> <oldparent> <until>. Der Trick hier ist die Erinnerung , dass die von referenzierten Festschreibung <until>ist inbegriffen im Bereich und wird die neu worden , HEADnachdem das Fütterungsmaterial abgeschlossen ist.

Enrico Campidoglio
quelle
1
Gute Antwort. Nur eine kleine Ergänzung für den allgemeinen Fall: Der <oldparent>Name bricht zusammen, wenn sich die beiden Teile des Bereichs auf unterschiedlichen Zweigen befinden. Im Allgemeinen heißt es: "Schließen Sie jedes Commit ein, von dem aus Sie erreichbar sind, <until>aber schließen Sie jedes Commit aus, von dem aus Sie erreichen können <oldparent>."
musiKk
50
git rebase --onto <newparent> <oldparent>ist die beste Erklärung für --onto Verhalten, das ich gesehen habe!
Ronkot
4
Vielen Dank! Ich hatte ein bisschen Probleme mit der --ontoOption, aber das machte es kristallklar! Ich verstehe es nicht einmal so, wie ich es vorher nicht hätte verstehen können: D Danke für das exzellente "Tutorial" :-)
Grongor
3
Obwohl diese Antwort ausgezeichnet ist, deckt sie meines Erachtens nicht alle möglichen Fälle ab. Die letzte Syntaxform kann auch verwendet werden, um eine subtilere Art der Rebase auszudrücken. Nach dem Beispiel in Pro Git (2. Aufl.) Muss D nicht unbedingt ein Vorfahr von H sein. Stattdessen könnten D und H auch Commits mit einem gemeinsamen Vorfahren sein - in diesem Fall wird Git ihren gemeinsamen Vorfahren herausfinden und wiederhole von diesem Vorfahren zu H auf F.
Pastafarian
1
Das war hilfreich. Die Manpage erklärt die Argumente überhaupt nicht.
a544jh
61

Dies ist alles, was Sie wissen müssen, um zu verstehen --onto:

git rebase --onto <newparent> <oldparent>

Sie schalten ein übergeordnetes Element für ein Commit um, geben jedoch nicht das sha des Commits an, sondern nur das sha des aktuellen (alten) übergeordneten Elements.

trennen
quelle
4
Kurz und einfach. Tatsächlich hat es am längsten gedauert, bis mir klar wurde, dass ich das übergeordnete Commit desjenigen bereitstellen muss , das ich anstelle dieses Commits neu erstellen möchte.
Antoniossss
1
Ein wichtiges Detail ist, dass Sie ein Kind eines alten Elternteils aus dem aktuellen Zweig auswählen, da ein Commit für viele Commits übergeordnet sein kann. Wenn Sie sich jedoch auf den aktuellen Zweig beschränken, kann das Commit nur für einen Commit übergeordnet sein. Mit anderen Worten, das übergeordnete Beziehungsschiff ist in der Verzweigung eindeutig, muss es aber nicht sein, wenn Sie keine Verzweigung angeben.
Trismegistos
2
Zu beachten: Sie müssen in der Verzweigung sein oder den Verzweigungsnamen als 3. Parameter hinzufügen git rebase --onto <newparent> <oldparent> <feature-branch>
Jason Portnoy
1
Diese Antwort ist erstaunlich, direkt auf den Punkt, wie in diesem Thread benötigt
John Culviner
13

Kurz gesagt, gegeben:

      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

Welches ist das gleiche wie (weil --ontoein Argument benötigt):

git rebase D H --onto F

Mittel rebase Commits in Bereich (D, H] oben auf F. Hinweis wird der Bereich der linken Hand aus. Es ist exklusiv , weil es einfacher ist , 1. durch Eingabe zB angeben , begehen branchzu lassen , gitdie ersten finden weicht von begehen branchdh Dwas zuH .

OP-Fall

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

git checkout B
git rebase --onto B A

Kann in einen einzelnen Befehl geändert werden:

git rebase --onto B A B

Was hier wie ein Fehler aussieht, ist die Platzierung, Bwas bedeutet, dass "einige Commits verschoben werden, die zu einer Verzweigung Büber B" führen. Die Frage ist, was "einige Commits" sind. Wenn Sie ein -iFlag hinzufügen, sehen Sie, dass es sich um ein einzelnes Commit handelt, auf das gezeigt wird HEAD. Das Festschreiben wird übersprungen, da es bereits auf das --ontoZiel angewendet wird Bund daher nichts passiert.

Der Befehl ist in jedem Fall Unsinn, wenn der Zweigname so wiederholt wird. Dies liegt daran, dass der Bereich der Festschreibungen einige Festschreibungen sind, die sich bereits in diesem Zweig befinden, und während der erneuten Basisung werden alle übersprungen.

Weitere Erklärung und anwendbare Verwendung von git rebase <upstream> <branch> --onto <newbase>.

git rebase Standardeinstellungen.

git rebase master

Erweitert auf:

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

Automatisches Auschecken nach Rebase.

Bei normaler Verwendung wie:

git checkout branch
git rebase master

Sie werden nicht merken , dass nach Fütterungsmaterial gitbewegt sich branchauf die zuletzt begehen indexiert und hat git checkout branch(siehe git reflogGeschichte). Was interessant ist, wenn das zweite Argument Commit-Hash ist , stattdessen funktioniert die Rebase des Zweignamens immer noch, aber es gibt keinen Zweig zum Verschieben, sodass Sie in "losgelöstem KOPF" landen und stattdessen zum verschobenen Zweig ausgecheckt werden.

Lassen Sie primär divergierende Commits aus.

Das masterIn --ontostammt aus dem 1. git rebaseArgument.

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

So praktisch kann es jedes andere Commit oder jeder andere Zweig sein. Auf diese Weise können Sie die Anzahl der Rebase-Commits begrenzen, indem Sie die neuesten Commits verwenden und primär divergierende Commits belassen.

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

Wird ein einzelnes Commit zurückweisen, auf das verwiesen wird HEAD, masterund in "losgelöstem KOPF" enden.

Vermeiden Sie explizite Kassen.

Die Standardeinstellung HEADoder das current_branchArgument wird kontextabhängig von der Stelle übernommen, an der Sie sich befinden. Aus diesem Grund checken die meisten Benutzer zu einem Zweig aus, den sie neu aufbauen möchten. Wenn das 2. Rebase-Argument explizit angegeben wird, müssen Sie vor dem Rebase nicht auschecken, um es implizit zu übergeben.

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

Dies bedeutet, dass Sie Commits und Zweige von jedem Ort aus neu starten können . Also zusammen mit der automatischen Kaufabwicklung nach dem Rebase. Sie müssen den neu basierten Zweig vor oder nach dem erneuten Basieren nicht separat auschecken.

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

Einfach ausgedrückt, git rebase --ontowählt eine Reihe von Commits aus und basiert sie auf dem als Parameter angegebenen Commit.

Lesen Sie die Manpages für git rebase, suchen Sie nach "auf". Die Beispiele sind sehr gut:

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

In diesem Fall weisen Sie git an, die Commits von oben topicAnach topicBoben neu zu starten master.

Gauthier
quelle
7

Um den Unterschied zwischen git rebaseund besser zu verstehen git rebase --onto, ist es gut zu wissen, welche Verhaltensweisen für beide Befehle möglich sind. git rebaseErmöglichen Sie uns, unsere Commits über den ausgewählten Zweig zu verschieben. Wie hier:

git rebase master

und das Ergebnis ist:

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 --ontoist präziser. Es ermöglicht uns, ein bestimmtes Commit auszuwählen, wo wir beginnen und wo wir enden möchten. Wie hier:

git rebase --onto F D

und das Ergebnis ist:

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)

Um weitere Informationen zu erhalten, empfehle ich Ihnen, meinen eigenen Artikel über git rebase --onto Übersicht zu lesen

Womanonrails
quelle
@ Makyen Klar, ich werde es mir in Zukunft
merken
Wir können also git rebase --onto F Dals gesetztes Kind von Ds Eltern als F lesen , nicht wahr?
Prihex
2

Dafür ontobenötigen Sie zwei zusätzliche Filialen. Mit diesem Befehl können Sie Commits anwenden branchB, die branchAauf einem anderen Zweig basieren, z master. Im folgenden Beispiel branchBbasiert auf branchAund Sie möchten die Änderungen von branchBon masteranwenden, ohne die Änderungen von anzuwenden branchA.

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

mit den Befehlen:

checkout branchB
rebase --onto master branchA 

Sie erhalten die folgende Festschreibungshierarchie.

      o'---o' (branchB)
     /
o---o (master)
     \
      o---o---o---o (branchA)
Michael Mairegger
quelle
1
Können Sie bitte etwas mehr erklären, wenn wir auf Master zurückgreifen möchten, wie kommt es, dass es der aktuelle Zweig wird? Wenn Sie dies tun rebase --onto branchA branchBwürden, würde dies den gesamten Hauptzweig auf den Kopf von Zweig A setzen?
Polymerase
8
sollte nicht das checkout branchB: rebase --onto master branchA?
Goldenratio
4
Warum wurde dies positiv bewertet? das macht nicht das, was es sagt.
De Novo
Ich habe die Antwort bearbeitet und korrigiert, damit die Leute nicht zuerst ihre Repo-Zweige aufbrechen müssen und gleich danach kommen und die Kommentare lesen ... 🙄
Kamafeather
0

Es gibt einen anderen Fall, der git rebase --ontoschwer zu verstehen ist: Wenn Sie auf ein Commit zurückgreifen, das sich aus einem symmetrischen Differenzwähler ergibt (die drei Punkte ' ...')

Git 2.24 (Q4 2019) verwaltet diesen Fall besser:

Siehe Commit 414d924 , Commit 4effc5b , Commit c0efb4c , Commit 2b318aa (27. August 2019) und Commit 793ac7e , Commit 359eceb (25. August 2019) von Denton Liu ( Denton-L) .
Unterstützt von: Eric Sunshine ( sunshineco) , Junio ​​C. Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) und Johannes Schindelin ( dscho) .
Siehe Commit 6330209 , Commit c9efc21 (27. August 2019) und Commit 4336d36 (25. August 2019) von Ævar Arnfjörð Bjarmason ( avar).
Unterstützt von: Eric Sunshine (sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) und Johannes Schindelin ( dscho) .
(Zusammengeführt von Junio ​​C Hamano - gitster- in Commit 640f9cd , 30. September 2019)

rebase: schneller Vorlauf --onto in mehr Fällen

Vorher, als wir das folgende Diagramm hatten,

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

Laufen ' git rebase --onto master... master side' würde dazu führen, Ddass immer neu basiert wird, egal was passiert.

Lesen Sie an dieser Stelle " Was sind die Unterschiede zwischen Doppelpunkt- ..und Dreifachpunkt ..." in Git-Diff-Festschreibungsbereichen? "

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

Hier: „ master...“ bezieht sich auf master...HEAD, das ist B: HEAD ist Seite HEAD (derzeit ausgecheckt): Sie Rebasing auf B.
Was gründen Sie neu? Jedes Commit, das nicht im Master enthalten ist und vom sideZweig aus erreichbar ist : Es gibt nur ein Commit, das zu dieser Beschreibung passt: D... das bereits oben ist B!

Wiederum rebase --ontowürde ein solches vor Git 2.24 dazu führen, Ddass es immer neu basiert, egal was passiert.

Das gewünschte Verhalten ist jedoch, dass die Rebase feststellen sollte, dass dies schnell vorspulbar ist, und dies stattdessen tun sollte.

Das ist vergleichbar mit dem rebase --onto B Ader OP, die nichts getan hat.

Fügen Sie die Erkennung hinzu, can_fast_forwarddamit dieser Fall erkannt werden kann und ein schneller Vorlauf durchgeführt wird.
Schreiben Sie zunächst die Funktion neu, um gotos zu verwenden, was die Logik vereinfacht.
Weiter, seit dem

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

Bedingungen wurden entfernt cmd_rebase, wir führen einen Ersatz in wieder ein can_fast_forward.
Insbesondere das Überprüfen der Zusammenführungsgrundlagen von upstreamund das headBeheben eines fehlerhaften Falls in t3416.

Das abgekürzte Diagramm für t3416 lautet wie folgt:

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

und der fehlerhafte Befehl war

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

Zuvor hatte Git festgestellt, dass es eine Zusammenführungsbasis ( C, Ergebnis von master...topic) gab, und die Zusammenführung und Weiter waren gleich, sodass fälschlicherweise 1 zurückgegeben wurde, was darauf hinweist, dass wir schnell vorspulen konnten. Dies würde dazu führen, dass der neu basierte Graph " ABCFG" ist, als wir erwartet hatten ABCG".

A rebase --onto C F topicbedeutet jedes Commit danach F , das von topicHEAD erreicht werden kann: das ist Gnur, nicht sich Fselbst.
Ein schneller FVorlauf würde in diesem Fall in den neu basierten Zweig einbezogen, was falsch ist.

Mit der zusätzlichen Logik erkennen wir, dass die Zusammenführungsbasis von Upstream und Head ist F. Da dies Fnicht der Fall ist , bedeutet dies, dass wir nicht alle Commits von neu festlegen master..topic.
Da wir einige Commits ausschließen, kann kein schneller Vorlauf durchgeführt werden, sodass wir 0 korrekt zurückgeben.

Fügen Sie ' -f' zu Testfällen hinzu, die aufgrund dieser Änderung fehlgeschlagen sind, weil sie keinen schnellen Vorlauf erwartet haben, sodass eine erneute Basis erzwungen wird.

VonC
quelle