Gibt es einen Unterschied zwischen Git Rebase und Git Merge --ff-only

73

Nach dem, was ich gelesen habe, helfen uns beide, eine lineare Geschichte zu erhalten.

Nach dem, was ich experimentiert habe, funktioniert Rebase die ganze Zeit. Merge --ff-only funktioniert jedoch nur in Szenarien, in denen es schnell weitergeleitet werden kann.

Mir ist auch aufgefallen, dass Git Merge ein Merge Commit erstellt, aber wenn wir nur -ff verwenden, ergibt sich ein linearer Verlauf, der im Wesentlichen gleichbedeutend mit Git Rebasing ist. Also --ff-only tötet den Zweck der Git-Zusammenführung, oder?

Was ist der tatsächliche Unterschied zwischen ihnen?

Phönix
quelle

Antworten:

150

Beachten Sie, dass dies git rebaseeinen anderen Job hat als git merge(mit oder ohne --ff-only). Sie rebasemüssen vorhandene Commits übernehmen und kopieren . Nehmen wir zum Beispiel an, Sie sind eingeschaltet branch1und haben zwei Commits durchgeführt Aund B:

...-o--o--A--B   <-- HEAD=branch1
        \
         o--C    <-- branch2

und Sie entscheiden, dass diese beiden Commits lieber aktiviert werden sollen branch2. Sie können:

  • Erhalten Sie eine Liste der Änderungen, die Sie vorgenommen haben A(Unterschied zu Aden übergeordneten Elementen ).
  • Holen Sie sich eine Liste der Änderungen, die Sie vorgenommen haben B(diff Bgegen A)
  • wechseln zu branch2
  • Nehmen Sie die gleichen Änderungen vor, die Sie vorgenommen haben, Aund übernehmen Sie sie, indem Sie Ihre Festschreibungsnachricht von kopieren A. Nennen wir dieses CommitA'
  • Nehmen Sie dann dieselben Änderungen vor, die Sie vorgenommen haben, Bund schreiben Sie sie fest, indem Sie Ihre Festschreibungsnachricht von kopieren B. Nennen wir das B'.

Es gibt einen git-Befehl, der dieses Diff-and-Then-Copy-and-Commit für Sie ausführt : git cherry-pick. Damit:

git checkout branch2      # switch HEAD to branch2 (commit C)
git cherry-pick branch1^  # this copies A to A'
git cherry-pick branch1   # and this copies B to B'

Jetzt haben Sie Folgendes:

...-o--o--A--B         <-- branch1
        \
         o--C--A'-B'   <-- HEAD=branch2

Jetzt können Sie zurück zu branch1Ihrem Original wechseln und es löschen Aund Bmit git reset(ich werde es --hardhier verwenden, ist es auf diese Weise bequemer, da es auch den Arbeitsbaum bereinigt):

git checkout branch1
git reset --hard HEAD~2

Dadurch wird das Original Aund B, 1 so jetzt haben Sie:

...-o--o               <-- HEAD=branch1
        \
         o--C--A'-B'   <-- branch2

Jetzt müssen Sie nur noch einmal auschecken, um dort branch2weiterarbeiten zu können.

Dies ist, was git rebasebewirkt: Es "verschiebt" Commits (allerdings nicht, indem es sie tatsächlich verschiebt, weil es nicht möglich ist: In git kann ein Commit niemals geändert werden. Selbst wenn Sie nur die übergeordnete ID ändern, müssen Sie sie in eine neue und etwas andere kopieren verpflichten).

Mit anderen Worten, während git cherry-pickes sich um ein automatisiertes Diff-and-Redo eines Commits handelt, git rebasehandelt es sich um einen automatisierten Prozess zum Wiederherstellen mehrerer Commits und zum Verschieben von Beschriftungen, um die Originale zu "vergessen" oder zu verbergen.

Das Obige zeigt das Verschieben von Commits von einem lokalen Zweig branch1in einen anderen lokalen Zweig branch2, aber git verwendet genau den gleichen Prozess , um Commits zu verschieben, wenn Sie einen Remote-Tracking-Zweig haben, der einige neue Commits erhält, wenn Sie einen ausführen git fetch(einschließlich fetchdes ersten Schritts von git pull). Sie können zunächst an einem Zweig arbeiten feature, der einen Upstream von hat origin/feature, und einige eigene Commits vornehmen:

...-o        <-- origin/feature
     \
      A--B   <-- HEAD=feature

Aber dann entscheidest du, dass du sehen sollst, was stromaufwärts passiert ist, also rennst du git fetch, 2 und aha, jemand stromaufwärts hat ein Commit geschrieben C:

...-o--C     <-- origin/feature
     \
      A--B   <-- HEAD=feature

An dieser Stelle können Sie einfach Ihre rebase feature‚s Aund Bauf C, geben:

...-o--C     <-- origin/feature
        \
         A'-B'  <-- HEAD=feature

Dies sind Kopien Ihres Originals Aund Bdie Originale werden nach Abschluss der Kopien weggeworfen (siehe jedoch Fußnote 1).


Manchmal gibt es nichts zu widerlegen, dh keine Arbeit, die Sie selbst geleistet haben. Das heißt, die Grafik vor dem fetchsieht folgendermaßen aus:

...-o      <-- origin/feature
           `-- HEAD=feature

Wenn Sie dann git fetchund Commit Ceingehen, bleibt Ihr feature Zweig auf dem alten Commit, während Sie origin/featurevorwärts gegangen sind :

...-o--C   <-- origin/feature
     `---- <-- HEAD=feature

Dies ist , wo git merge --ff-onlykommt in: Wenn Sie fragen Sie Ihren aktuellen Zweig verschmelzen featuremit origin/feature, git sieht , dass es möglich ist , nur nach vorne auf den Pfeil schieben, wie es war, so dass featurePunkte direkt zu begehen C. Es ist keine tatsächliche Zusammenführung erforderlich.

Wenn Sie Ihre eigenen Commits hätten Aund BSie darum gebeten hätten, diese zusammenzuführen C, würde git eine echte Zusammenführung durchführen und ein neues Zusammenführungs-Commit durchführen M:

...-o--C        <-- origin/feature
     \   `-_
      A--B--M   <-- feature

Hier --ff-onlywird angehalten und Sie erhalten einen Fehler. Fütterungsmaterial, auf der anderen Seite, kopiert Aund Bzu A'und B'dann das Original verstecken , Aund B.

Kurz gesagt (ok, zu spät :-)) machen sie einfach verschiedene Dinge. Manchmal ist das Ergebnis das gleiche und manchmal nicht. Wenn es in Ordnung ist, Aund zu kopieren B, können Sie verwenden git rebase; Wenn es jedoch einen guten Grund gibt, sie nicht zu kopieren, können Sie sie git mergemöglicherweise verwenden --ff-only, um sie nach Bedarf zusammenzuführen oder fehlzuschlagen.


1 Git bewahrt die Originale tatsächlich einige Zeit auf - in diesem Fall normalerweise einen Monat -, versteckt sie jedoch. Der einfachste Weg, sie zu finden, sind die "Reflogs" von git, die HEADvor jeder Änderung, die den Zweig und / oder aktualisiert hat , einen Verlauf darüber führen, wohin und wo jeder Zweig zeigte HEAD.

Schließlich werden die reflog History - Einträge verfallen, an welcher Stelle diese Commits für in Frage kommende werden Garbage Collection .

2 Sie können auch git pullein praktisches Skript verwenden , das zunächst ausgeführt wird git fetch. Sobald der Abruf abgeschlossen ist, wird das Convenience-Skript entweder git mergeoder ausgeführt git rebase, je nachdem, wie Sie es konfigurieren und ausführen.

torek
quelle
3
Gute Antwort. Vielen Dank.
Phoenix
4
Ich mag die Art und Weise, wie Sie git cherry-pickerklären git rebase. Sieht großartig aus!
Kaihua
Welche Strategie wird empfohlen, wenn Sie eine PR aus dem Entwicklungszweig mit dem Master (Produkt) zusammenführen?
Shanika Ediriweera
@ShanikaEdiriweera: Von wem und zu welchem ​​Zweck empfohlen? (Verschiedene Orte, an denen ich gewesen bin, hatten unterschiedliche Ziele und verwendeten daher unterschiedliche Mechanismen.) Ohne Ihre Situation zu kennen, kann ich keine nützlichen Ratschläge geben.
Torek
1
Das klingt nach einem Ort, an dem eine Standardzusammenführung durchgeführt werden kann, wahrscheinlich auch --no-ffhier.
Torek
10

Ja, da gibt es einen Unterschied. git merge --ff-onlywird abgebrochen, wenn es nicht schnell vorspulen kann, und es wird ein Commit (normalerweise ein Zweig) zum Zusammenführen benötigt. Es wird nur ein Commit-Commit erstellt, wenn es nicht schnell vorspulen kann (dh niemals mit --ff-only).

git rebaseSchreibt den Verlauf des aktuellen Zweigs neu oder kann verwendet werden, um einen vorhandenen Zweig auf einen vorhandenen Zweig umzustellen. In diesem Fall wird kein Zusammenführungs-Commit erstellt, da es neu basiert und nicht zusammengeführt wird.

abligh
quelle
Das heißt, --ff-only prüft und kann nicht zusammengeführt werden, wenn dies nicht möglich ist, ohne den Verlauf neu zu schreiben. Rebase tut dies, indem es die Geschichte neu schreibt. Ist es?
Phoenix
2
Nicht ganz. Zusammenführungen schreiben die Geschichte nicht neu und schreiben sie daher git mergeniemals neu (mit oder ohne --ff-only). --ff-onlyVerhindert eine andere Zusammenführung als eine Schnellvorlaufzusammenführung (keine Zusammenführung schreibt den Verlauf neu). Bei anderen Zusammenführungen als Schnellvorlauf-Zusammenführungen werden die Änderungssätze im Allgemeinen einfach in der falschen Reihenfolge angewendet (dies ist eine Vereinfachung, aber gut genug), und es werden Metadaten aufgezeichnet, mit denen ein Änderungssatz zusammengeführt wurde. Keine Umschreibung der Geschichte erforderlich.
Abligh
7

Ja, --ff-onlywird immer dort versagen, wo eine Ebene git mergeversagen würde, und könnte dort versagen, wo eine Ebene erfolgreich sein git mergewürde. Das ist der Punkt - wenn Sie versuchen, einen linearen Verlauf beizubehalten, und die Zusammenführung nicht auf diese Weise durchgeführt werden kann, möchten Sie, dass sie fehlschlägt.

Eine Option, die einem Befehl Fehlerfälle hinzufügt, ist nicht nutzlos. Auf diese Weise können Sie eine Vorbedingung überprüfen. Wenn der aktuelle Status des Systems nicht Ihren Erwartungen entspricht, verschlimmern Sie das Problem nicht.


quelle
1
Es ist also so, dass --ff-only verwendet wird, um das Szenario zu validieren und wenn möglich einen linearen Verlauf zu erstellen. Während Rebase eher so wäre, erstellen Sie unabhängig vom Szenario einen linearen Verlauf. Ist es?
Phoenix
4
Ja, und wenn Sie etwas neu gründen, das für Sie nicht sauber genug war, merge --ff-onlyentfernen Sie Commits von einer Stelle im Verlaufsbaum und fügen sie (mit Änderungen) an einer anderen Stelle wieder ein. Dies wird als "Verlauf neu schreiben" bezeichnet und ist in Ordnung, wenn Sie sich in einem unveröffentlichten Zweig befinden. Nachdem Sie Ihren Zweig veröffentlicht haben und andere Personen ihn geklont haben, verursacht das Umschreiben des Verlaufs Probleme für sie.