Manchmal ist es effektiv unmöglich (mit einigen Ausnahmen, wo Sie möglicherweise Glück haben, zusätzliche Daten zu haben) und die Lösungen hier funktionieren nicht.
Git bewahrt den Ref-Verlauf (einschließlich Zweige) nicht auf. Es wird nur die aktuelle Position für jeden Zweig (den Kopf) gespeichert. Dies bedeutet, dass Sie mit der Zeit einige Zweigverläufe in Git verlieren können. Wenn Sie beispielsweise verzweigen, geht sofort verloren, welcher Zweig der ursprüngliche war. Alles, was ein Zweig tut, ist:
git checkout branch1 # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1
Sie können davon ausgehen, dass der erste Commit der Zweig ist. Dies ist in der Regel der Fall, aber nicht immer so. Nichts hindert Sie daran, sich nach dem obigen Vorgang zuerst in einen Zweig zu begeben. Außerdem ist nicht garantiert, dass Git-Zeitstempel zuverlässig sind. Erst wenn Sie sich zu beiden verpflichten, werden sie strukturell zu echten Zweigen.
Während wir in Diagrammen dazu neigen, Commits konzeptionell zu nummerieren, hat git kein wirklich stabiles Konzept der Sequenz, wenn sich der Commit-Baum verzweigt. In diesem Fall können Sie davon ausgehen, dass die Zahlen (die die Reihenfolge angeben) durch den Zeitstempel bestimmt werden (es kann Spaß machen zu sehen, wie eine Git-Benutzeroberfläche mit Dingen umgeht, wenn Sie alle Zeitstempel auf denselben Wert setzen).
Das erwartet ein Mensch konzeptionell:
After branch:
C1 (B1)
/
-
\
C1 (B2)
After first commit:
C1 (B1)
/
-
\
C1 - C2 (B2)
Das bekommen Sie tatsächlich:
After branch:
- C1 (B1) (B2)
After first commit (human):
- C1 (B1)
\
C2 (B2)
After first commit (real):
- C1 (B1) - C2 (B2)
Sie würden annehmen, dass B1 der ursprüngliche Zweig ist, aber es könnte sich einfach um einen toten Zweig handeln (jemand hat -b ausgecheckt, aber nie dazu verpflichtet). Erst wenn Sie sich zu beiden verpflichten, erhalten Sie eine legitime Zweigstruktur innerhalb von git:
Either:
/ - C2 (B1)
-- C1
\ - C3 (B2)
Or:
/ - C3 (B1)
-- C1
\ - C2 (B2)
Sie wissen immer, dass C1 vor C2 und C3 kam, aber Sie wissen nie zuverlässig, ob C2 vor C3 oder C3 vor C2 kam (weil Sie die Zeit auf Ihrer Workstation beispielsweise auf irgendetwas einstellen können). B1 und B2 sind ebenfalls irreführend, da Sie nicht wissen können, welcher Zweig zuerst kam. In vielen Fällen können Sie eine sehr gute und normalerweise genaue Vermutung anstellen. Es ist ein bisschen wie eine Rennstrecke. Wenn alle Dinge im Allgemeinen mit den Autos gleich sind, können Sie davon ausgehen, dass ein Auto, das in einer Runde dahinter kommt, eine Runde dahinter gestartet ist. Wir haben auch Konventionen, die sehr zuverlässig sind, zum Beispiel wird der Meister fast immer die langlebigsten Zweige darstellen, obwohl ich leider Fälle gesehen habe, in denen selbst dies nicht der Fall ist.
Das hier gegebene Beispiel ist ein geschichtserhaltendes Beispiel:
Human:
- X - A - B - C - D - F (B1)
\ / \ /
G - H ----- I - J (B2)
Real:
B ----- C - D - F (B1)
/ / \ /
- X - A / \ /
\ / \ /
G - H ----- I - J (B2)
Real hier ist auch irreführend, weil wir als Menschen es von links nach rechts lesen, Wurzel zu Blatt (ref). Git macht das nicht. Wo wir (A-> B) in unseren Köpfen tun, tut Git (A <-B oder B-> A). Es liest es von ref bis root. Refs können überall sein, sind aber eher Blätter, zumindest für aktive Zweige. Ein Ref verweist auf ein Commit und Commits enthalten nur ein Like für ihre Eltern, nicht für ihre Kinder. Wenn es sich bei einem Commit um ein Merge-Commit handelt, hat es mehr als ein übergeordnetes Element. Das erste übergeordnete Element ist immer das ursprüngliche Commit, mit dem es zusammengeführt wurde. Die anderen Eltern sind immer Commits, die mit dem ursprünglichen Commit zusammengeführt wurden.
Paths:
F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
Dies ist keine sehr effiziente Darstellung, sondern ein Ausdruck aller Pfade, die git von jeder Referenz (B1 und B2) nehmen kann.
Der interne Speicher von Git sieht eher so aus (nicht, dass A als übergeordnetes Element zweimal vorkommt):
F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A
Wenn Sie ein Raw-Git-Commit ausgeben, werden null oder mehr übergeordnete Felder angezeigt. Wenn es Null gibt, bedeutet dies, dass kein übergeordnetes Element vorhanden ist und das Commit eine Wurzel ist (Sie können tatsächlich mehrere Wurzeln haben). Wenn es eine gibt, bedeutet dies, dass keine Zusammenführung stattgefunden hat und es sich nicht um ein Root-Commit handelt. Wenn es mehr als eine gibt, bedeutet dies, dass das Commit das Ergebnis einer Zusammenführung ist und alle Eltern nach der ersten Zusammenführung Commits sind.
Paths simplified:
F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
F->D->C->B->A | J->I->->G->A | A->X
Topological:
- X - A - B - C - D - F (B1)
\
G - H - I - J (B2)
Wenn beide A treffen, ist ihre Kette dieselbe, davor ist ihre Kette völlig unterschiedlich. Das erste Commit, das zwei weitere Commits gemeinsam haben, ist der gemeinsame Vorfahr, von dem sie abweichen. Hier kann es zu Verwechslungen zwischen den Begriffen commit, branch und ref kommen. Sie können tatsächlich ein Commit zusammenführen. Das macht Merge wirklich. Ein Ref zeigt einfach auf ein Commit und ein Zweig ist nichts anderes als ein Ref im Ordner .git / refs / Heads. Der Ordnerspeicherort bestimmt, dass ein Ref ein Zweig ist und nicht etwas anderes wie ein Tag.
Wenn Sie die Geschichte verlieren, führt die Zusammenführung je nach den Umständen eines von zwei Dingen aus.
Erwägen:
/ - B (B1)
- A
\ - C (B2)
In diesem Fall wird durch eine Zusammenführung in beide Richtungen ein neues Commit erstellt, wobei das erste übergeordnete Element das Commit ist, auf das der aktuell ausgecheckte Zweig zeigt, und das zweite übergeordnete Element als Commit an der Spitze des Zweigs, den Sie mit Ihrem aktuellen Zweig zusammengeführt haben. Es muss ein neues Commit erstellt werden, da beide Zweige seit ihrem gemeinsamen Vorfahren Änderungen aufweisen, die kombiniert werden müssen.
/ - B - D (B1)
- A /
\ --- C (B2)
Zu diesem Zeitpunkt hat D (B1) nun beide Änderungssätze von beiden Zweigen (selbst und B2). Der zweite Zweig hat jedoch nicht die Änderungen von B1. Wenn Sie die Änderungen von B1 in B2 zusammenführen, damit sie synchronisiert werden, können Sie etwas erwarten, das so aussieht (Sie können git merge dazu zwingen, dies jedoch mit --no-ff zu tun):
Expected:
/ - B - D (B1)
- A / \
\ --- C - E (B2)
Reality:
/ - B - D (B1) (B2)
- A /
\ --- C
Sie erhalten dies auch dann, wenn B1 zusätzliche Commits hat. Solange es in B2 keine Änderungen gibt, die B1 nicht hat, werden die beiden Zweige zusammengeführt. Es führt einen schnellen Vorlauf durch, der einer Rebase ähnelt (Rebases fressen oder linearisieren den Verlauf), außer dass im Gegensatz zu einer Rebase, da nur ein Zweig einen Änderungssatz hat, kein Änderungssatz von einem Zweig auf den anderen angewendet werden muss.
From:
/ - B - D - E (B1)
- A /
\ --- C (B2)
To:
/ - B - D - E (B1) (B2)
- A /
\ --- C
Wenn Sie die Arbeit an B1 einstellen, sind die Dinge weitgehend in Ordnung, um die Geschichte langfristig zu bewahren. Nur B1 (möglicherweise Master) wird in der Regel weiterentwickelt, sodass die Position von B2 in der B2-Historie erfolgreich den Punkt darstellt, an dem es in B1 zusammengeführt wurde. Dies ist, was Git von Ihnen erwartet, um B von A zu verzweigen. Dann können Sie A nach Belieben mit B zusammenführen, wenn sich Änderungen ansammeln. Wenn Sie jedoch B wieder mit A zusammenführen, wird nicht erwartet, dass Sie an B und weiter arbeiten . Wenn Sie nach dem schnellen Vorlauf weiter an Ihrem Zweig arbeiten und ihn wieder mit dem Zweig zusammenführen, an dem Sie gearbeitet haben, löschen Sie jedes Mal den vorherigen Verlauf von B. Sie erstellen wirklich jedes Mal einen neuen Zweig, nachdem Sie die Quelle schnell vorgespult und dann den Zweig festgeschrieben haben.
0 1 2 3 4 (B1)
/-\ /-\ /-\ /-\ /
---- - - - -
\-/ \-/ \-/ \-/ \
5 6 7 8 9 (B2)
1 bis 3 und 5 bis 8 sind strukturelle Zweige, die angezeigt werden, wenn Sie dem Verlauf für 4 oder 9 folgen. Es gibt keine Möglichkeit in git zu wissen, zu welchen dieser unbenannten und nicht referenzierten strukturellen Zweige mit den genannten und Referenzzweigen als die gehören Ende der Struktur. Sie könnten aus dieser Zeichnung annehmen, dass 0 bis 4 zu B1 und 4 bis 9 zu B2 gehören, aber abgesehen von 4 und 9 konnte nicht wissen, welcher Zweig zu welchem Zweig gehört, ich habe es einfach so gezeichnet, dass das ergibt Illusion davon. 0 könnte zu B2 gehören und 5 könnte zu B1 gehören. In diesem Fall gibt es 16 verschiedene Möglichkeiten, zu welchem benannten Zweig jeder der strukturellen Zweige gehören könnte.
Es gibt eine Reihe von Git-Strategien, die dies umgehen. Sie können das Zusammenführen von Git erzwingen, um niemals schnell vorzuspulen und immer einen Zusammenführungszweig zu erstellen. Eine schreckliche Möglichkeit, den Zweigverlauf beizubehalten, besteht darin, Tags und / oder Zweige (Tags werden wirklich empfohlen) gemäß einer Konvention Ihrer Wahl zu verwenden. Ich würde wirklich keinen leeren Dummy-Commit in dem Zweig empfehlen, in den Sie zusammenführen. Eine sehr verbreitete Konvention besteht darin, erst dann in einen Integrationszweig zu verschmelzen, wenn Sie Ihren Zweig wirklich schließen möchten. Dies ist eine Praxis, an die sich die Leute halten sollten, da Sie sonst daran arbeiten, Zweige zu haben. In der realen Welt ist das Ideal jedoch nicht immer praktisch, was bedeutet, dass es nicht in jeder Situation möglich ist, das Richtige zu tun. Wenn was du '