Nehmen wir an, ich mache eine interaktive Rebase mit git rebase -i
. Wenn ein Konflikt auftritt, wird mir möglicherweise ein Zusammenführungskonflikt angezeigt und ich werde aufgefordert, eine 3-Wege-Zusammenführung durchzuführen. Verwenden meld
werden mir drei Fenster angezeigt: LOCAL
(links), ???
(Mitte) und REMOTE
(rechts). Hier vorbei ???
Ich meine einfach das meld
stellt keinen speziellen Namen bereit, der an die Datei angehängt werden kann.
Während einer normalen Zusammenführung ist dies sinnvoll, da die Mitte der gemeinsame Vorfahre ist und Sie die lokalen und entfernten Änderungen an diesem Vorfahren zusammenführen. Dies scheint jedoch während einer interaktiven Rebase nicht der Fall zu sein – es ist unklar, was jede Datei darstellt.
Was stellen diese Dateien in der 3-Wege-Zusammenführung jeweils während einer interaktiven Rebase dar? Und was ist mein Ziel, wenn ich diese Dateien bearbeite?
Aktualisieren: Basierend auf den Kommentaren und Experimenten, die ich sehe:
- Links (
LOCAL
): Ihre lokale Version der Datei an diesem Punkt in der Commit-Wiedergabesequenz.
- Rechts (
REMOTE
): Der Zustand der Datei unmittelbar nachdem der aktuelle Commit ursprünglich angewendet wurde.
- Mitte: Das übergeordnete Element des Rechts in der ursprünglichen Commit-Sequenz.
Meine Aufgabe ist es also, das Delta von Mitte nach rechts zu bestimmen und dieses Delta dann nach links anzuwenden. Die Mitte sollte den Status der Datei widerspiegeln, nachdem das aktuelle Commit-Delta in der neuen Commit-Sequenz angewendet wurde.
Beachten Sie, dass diese Konfiguration zumindest bis zu einem gewissen Grad spezifisch für die Verschmelzung zu sein scheint. Das 3-Wege-Merge-Verhalten von Git kann für andere Editoren abweichen.
Die mittlere Version ist die Zusammenführungsbasis, genau wie bei a git merge
.
(Der Name „other“ könnte angemessener sein als „remote“, da es nicht erforderlich ist, dass die andere Seite einer Zusammenführung eine Remote ist, und da Mercurial konsequent den Namen „other“ dafür verwendet, muss Git nicht mit Mercurial übereinstimmen , aber etwas Konsistenz könnte nett sein. Beachten Sie, dass Git hier auch die Namen “ours” und “theirs” verwendet, also werden wir niemals 100% Konsistenz von Git bekommen. 🙂 )
Aber warten Sie, wie gibt es eine Merge-Basis?
Es gibt immer eine Merge-Basis.
Normalerweise müssen wir es nicht einmal finden, da jeder Patch sauber angewendet wird, wenn er als Patch behandelt wird (ohne eine Drei-Wege-Zusammenführung zu versuchen). Aber manchmal wird der Patch nicht sauber angewendet, und wir tun auf die Drei-Wege-Mischung zurückgreifen müssen.
(Übrigens können Sie diesen Fallback deaktivieren. Siehe --3way
, --no-3way
und am.threeWay
in die Git-Am-Dokumentationobwohl die hier verlinkte Seite bereits veraltet ist, da sich diese Steuerelemente kürzlich geändert haben.)
$ git rebase -i
pick aaaaaaa first commit
pick bbbbbbb second commit
pick ccccccc third commit
Lassen Sie uns auch das Commit-Diagramm zeichnen, damit wir sehen können, wovon und wohin wir rebasen:
A - B - C <-- branch
/
... - o - *
\
G - H <-- origin/branch
Wir werden Commits herauspicken A
, B
und C
(A
= aaaaaaa
etc), sodass wir am Ende dieses Ergebnis erhalten:
A - B - C [abandoned]
/
... - o - * A' - B' - C' <-- branch
\ /
G - H <-- origin/branch
Schauen wir uns den ersten Rosinenpick genau an A
.
Dies vergleicht (diffs) A
gegen seinen Elternteil, der begangen wird *
und versucht, den resultierenden Diff zum Festschreiben anzuwenden H
.
Begehen H
ist jedoch etwas von commit abgedriftet *
. Tatsächlich können wir eine Zusammenführungsbasis dazwischen finden A
und H
und es ist … verpflichten *
. Dies ist eigentlich eine ziemlich anständige Merge-Basis, obwohl es am besten ist, wenn Git den Patch einfach so anwenden kann, wie er ist, ohne auf den Drei-Wege-Merge-Code zurückgreifen zu müssen.
Also verpflichte dich *
ist die Zusammenführungsbasis beim Rosinenpicken A
auf zu H
. Wenn die Zusammenführung abgeschlossen ist, erhalten wir einen neuen Commit A'
. (Die neue SHA-1-ID könnte lauten aaaaaa1
zum Beispiel. Wahrscheinlich nicht; nennen wir es einfach A'
.)
Jetzt werden wir Rosinen pflücken B
. Dies unterscheidet sich B
gegenüber seinem Elternteil, das ist A
und versucht, das diff auf anzuwenden A'
.
Begehen A'
ist jedoch etwas von commit abgedriftet B
. Tatsächlich können wir eine Zusammenführungsbasis dazwischen finden B
und A'
und das ist … verpflichten *
aufs Neue. Leider ist dies eine erbärmliche Merge-Basis. Glücklicherweise greift Git nur dann darauf zurück, wenn der Patch nicht unverändert angewendet werden kann, und normalerweise ist dies möglich. Aber wenn es nicht kann, Git unterscheidet sich *
vs B
und *
vs A'
und versuchen Sie, diese beiden Unterschiede zusammenzuführen. Beachten Sie, dass *
vs B
enthält alle Änderungen, die wir in vorgenommen haben A
aber *
vs A'
enthält auch alle diese gleichen A
Wenn wir also Glück haben, bemerkt Git die bereits eingearbeiteten Änderungen und dupliziert sie nicht. bearbeiten Git-Cheats. (Dieser Code wurde kürzlich in Version 2.6 geändert, obwohl die Gesamtstrategie dieselbe bleibt.)
Betrachten Sie die tatsächliche Ausgabe von git diff
wenn es verwendet wird, um nur die Änderung vom Commit anzuzeigen A
begehen B
. Dazu gehört ein index
Linie:
diff --git a/foo b/foo
index f0b98f8..0ea3286 100644
Der Wert auf der linken Seite ist der (abgekürzte) Hash für die Version der Datei foo
im Einsatz A
. Der Wert auf der rechten Seite ist der Hash für die Version der Datei im Commit B
.
Git täuscht einfach eine Zusammenführungsbasis aus dem Hash der linken Seite vor. Mit anderen Worten, die Dateiversion im Commit A
wird zur gefälschten Merge-Basis. (Git besteht --build-fake-ancestor
zu git apply
. Dies erfordert, dass sich die bestimmten Datei-Blob-Objekte im Repository befinden, aber sie sind es, da sie festgeschrieben sind A
. Für Patches per E-Mail verwendet Git denselben Code, aber der Blob kann vorhanden sein oder nicht.)
Beachten Sie, dass Git dies tatsächlich beim Rosinenpicken von Commit tut A
auch, aber dieses Mal ist die Merge-Basisdatei die Version von Commit *
was wirklich ist die Merge-Basis.
Schließlich pflücken wir Rosinen C
. Dies unterscheidet sich B
vs C
so wie wir uns unterschieden A
vs B
letztes Mal. Wenn wir den Patch so anwenden können, wie er ist, gut; wenn nicht, fallen wir zurück Commit verwenden *
wieder als Zusammenführungsbasis. Es ist wieder einmal eine ziemlich erbärmliche Merge-Basis. auf die gleiche Weise wie zuvor, indem Sie vorgeben, dass die Version in B
war die gemeinsame Basis.
Dies erklärt übrigens auch, warum Sie bei diesen Rebases dazu neigen, immer wieder dieselben Merge-Konflikte zu sehen: Wir verwenden jedes Mal dieselbe Merge-Basis. (Aktivieren git rerere
kann helfen.)
Ein Merge und ein Rebase sind in dieser Hinsicht identisch. Der einzige Unterschied zwischen einem Merge und einem Rebase besteht darin, dass der Verlauf mit einem Rebase schöner (linearer) aussieht. Aber was die Konflikte betrifft, die entstehen und die Sie lösen müssen, sind sie gleich.