592 Stimmen

Einen Verzweigungspunkt mit Git finden?

Ich habe ein Repository mit den Zweigen master und A und viele Merge-Aktivitäten zwischen den beiden. Wie kann ich die Übergabe in meinem Repository finden, wenn Zweig A auf der Grundlage von master erstellt wurde?

Mein Repository sieht im Wesentlichen wie folgt aus:

-- X -- A -- B -- C -- D -- F  (master) 
          \     /   \     /
           \   /     \   /
             G -- H -- I -- J  (branch A)

Ich bin auf der Suche nach Revision A, die nicht das ist, was git merge-base (--all) findet.

5voto

FelipeC Punkte 7866

Nach vielen Untersuchungen und Diskussionen ist klar, dass es kein Patentrezept gibt, das in allen Situationen funktioniert, zumindest nicht in der aktuellen Version von Git.

Deshalb habe ich ein paar Patches geschrieben, die das Konzept einer tail Branche. Jedes Mal, wenn eine Verzweigung erzeugt wird, wird auch ein Zeiger auf den ursprünglichen Punkt erzeugt, der tail ref. Diese Referenz wird jedes Mal aktualisiert, wenn der Zweig neu erstellt wird.

Um den Verzweigungspunkt des devel-Zweigs herauszufinden, müssen Sie nur devel@{tail} , das war's.

https://github.com/felipec/git/commits/fc/tail

4voto

Reck Punkte 6852

Ich musste dieses Problem vor kurzem auch lösen und habe dafür ein Ruby-Skript geschrieben: https://github.com/vaneyckt/git-find-branching-point

4voto

Tom Tanner Punkte 9075

Ich scheine etwas Freude zu haben mit

git rev-list branch...master

Die letzte Zeile, die Sie erhalten, ist die erste Übergabe auf dem Zweig, so dass es dann darum geht, den Elternteil davon zu erhalten. Also

git rev-list -1 `git rev-list branch...master | tail -1`^

Scheint bei mir zu funktionieren und benötigt keine Diffs und so weiter (was hilfreich ist, da wir diese Version von Diff nicht haben)

Berichtigung: Dies funktioniert nicht, wenn Sie auf dem Master-Zweig sind, aber ich tue dies in einem Skript, so dass dies weniger ein Problem ist

4voto

jgmjgm Punkte 3524

Manchmal ist es tatsächlich unmöglich (mit einigen Ausnahmen, bei denen Sie das Glück haben, zusätzliche Daten zu erhalten), und die hier vorgestellten Lösungen werden nicht funktionieren.

Git bewahrt die Ref-Historie (einschließlich der Zweige) nicht auf. Es speichert nur die aktuelle Position für jeden Zweig (den Kopf). Das bedeutet, dass man in Git im Laufe der Zeit einige Zweigverläufe verlieren kann. Wenn Sie zum Beispiel eine Verzweigung vornehmen, geht sofort verloren, welche Verzweigung die ursprüngliche war. Alles was ein Zweig tut ist:

git checkout branch1    # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1

Man könnte annehmen, dass der erste Commit to der Branch ist. Das ist in der Regel der Fall, aber nicht immer. Es gibt nichts, was Sie daran hindert, nach dem obigen Vorgang zuerst an einen der beiden Zweige zu committen. Außerdem sind die Zeitstempel von Git nicht garantiert zuverlässig. Erst wenn Sie in beide Zweige übertragen, werden sie strukturell zu Zweigen.

Während wir in Diagrammen dazu neigen, Commits konzeptionell zu nummerieren, hat Git kein wirklich stabiles Konzept für die Reihenfolge der Verzweigungen im Commit-Baum. In diesem Fall können Sie davon ausgehen, dass die Zahlen (die die Reihenfolge angeben) durch den Zeitstempel bestimmt werden (es wäre interessant zu sehen, wie eine Git-Benutzeroberfläche die Dinge handhabt, wenn Sie alle Zeitstempel auf denselben Wert setzen).

Das ist es, was ein Mensch begrifflich erwartet:

After branch:
       C1 (B1)
      /
    -
      \
       C1 (B2)
After first commit:
       C1 (B1)
      /
    - 
      \
       C1 - C2 (B2)

Dies ist das, was Sie tatsächlich bekommen:

After branch:
    - C1 (B1) (B2)
After first commit (human):
    - C1 (B1)
        \
         C2 (B2)
After first commit (real):
    - C1 (B1) - C2 (B2)

Man würde annehmen, dass B1 der ursprüngliche Zweig ist, aber es könnte sich auch einfach um einen toten Zweig handeln (jemand hat checkout -b durchgeführt, aber nie etwas daran gebunden). Erst wenn Sie an beide Zweige übertragen, erhalten Sie eine legitime Zweigstruktur in 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 z. B. die Uhrzeit auf Ihrem Arbeitsplatzrechner beliebig 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 in der Regel genaue Vermutung anstellen. Es ist ein bisschen wie bei einer Rennstrecke. Wenn bei den Autos alles gleich ist, kann man davon ausgehen, dass ein Auto, das eine Runde später kommt, eine Runde später gestartet ist. Wir haben auch Konventionen, die sehr zuverlässig sind, z. B. steht der Meister fast immer für die am längsten bestehenden Zweige, obwohl ich leider Fälle gesehen habe, in denen selbst das nicht der Fall war.

Das hier angeführte Beispiel ist ein Beispiel für die Erhaltung der Geschichte:

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 ist auch hier irreführend, denn wir Menschen lesen von links nach rechts, von der Wurzel zum Blatt (ref). Git macht das nicht. Wo wir in unseren Köpfen (A->B) machen, macht Git (A<-B oder B->A). Es liest es von ref zu Root. Refs können überall sein, neigen aber dazu, Leafs zu sein, zumindest bei aktiven Zweigen. Ein Ref verweist auf einen Commit und Commits enthalten nur ein Like zu ihren Eltern, nicht zu ihren Kindern. Wenn ein Commit ein Merge-Commit ist, hat er mehr als einen Parent. Der erste Elternteil ist immer der ursprüngliche Commit, in den er 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 eher ein Ausdruck für alle Wege, die Git von jedem Ref (B1 und B2) aus nehmen kann.

Der interne Speicher von Git sieht eher so aus (nicht, dass A als Elternteil zweimal auftaucht):

    F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A

Wenn Sie einen rohen Git-Commit auslesen, werden Sie null oder mehr übergeordnete Felder sehen. Wenn es null Felder gibt, bedeutet das, dass es keine Eltern gibt und der Commit ein Root ist (Sie können sogar mehrere Roots haben). Wenn ein Feld vorhanden ist, bedeutet dies, dass es keine Zusammenführung gab und es sich nicht um einen Root-Commit handelt. Wenn es mehr als eins gibt, bedeutet es, dass der Commit das Ergebnis einer Zusammenführung ist und alle Eltern-Commits nach dem ersten sind Zusammenführungs-Commits.

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 auf A treffen, ist ihre Kette die gleiche, davor ist sie völlig unterschiedlich. Der erste Commit, den zwei andere Commits gemeinsam haben, ist der gemeinsame Vorfahre, von dem sie abgewichen sind. Es könnte hier eine Verwirrung zwischen den Begriffen Commit, Branch und Ref. Sie können tatsächlich einen Commit zusammenführen. Das ist es, was merge wirklich tut. Ein ref verweist einfach auf einen Commit, und ein Branch ist nichts anderes als ein ref im Ordner .git/refs/heads. Der Speicherort des Ordners bestimmt, dass es sich bei einem ref um einen Branch handelt und nicht etwa um ein Tag.

Wenn Sie die Geschichte verlieren, wird der Zusammenschluss je nach den Umständen eines von zwei Dingen tun.

Bedenken Sie:

      / - B (B1)
    - A
      \ - C (B2)

In diesem Fall erzeugt ein Zusammenführen in beide Richtungen einen neuen Commit, wobei der erste Elternteil der Commit ist, auf den der aktuelle ausgecheckte Zweig zeigt, und der zweite Elternteil der Commit an der Spitze des Zweiges, den Sie in Ihren aktuellen Zweig zusammengeführt haben. Es muss ein neuer Commit erstellt werden, da beide Zweige Änderungen seit ihrem gemeinsamen Vorgänger haben, die kombiniert werden müssen.

      / - B - D (B1)
    - A      /
      \ --- C (B2)

An diesem Punkt hat D (B1) nun beide Sätze von Änderungen aus beiden Zweigen (selbst und B2). Der zweite Zweig enthält jedoch nicht die Änderungen aus B1. Wenn Sie die Änderungen von B1 in B2 zusammenführen, so dass sie synchronisiert werden, könnten Sie etwas erwarten, das so aussieht (Sie können git merge jedoch mit --no-ff dazu zwingen, es so zu machen):

Expected:
      / - B - D (B1)
    - A      / \
      \ --- C - E (B2)
Reality:
      / - B - D (B1) (B2)
    - A      /
      \ --- C

Dies gilt auch dann, wenn B1 zusätzliche Übertragungen hat. Solange es in B2 keine Änderungen gibt, die B1 nicht hat, werden die beiden Zweige zusammengeführt. Es wird eine schnelle Vorwärtsbewegung durchgeführt, die einem Rebase ähnelt (Rebases fressen oder linearisieren auch die Historie), mit dem Unterschied, dass im Gegensatz zu einem Rebase, da nur ein Zweig einen Änderungssatz hat, kein Änderungssatz von einem Zweig auf den eines anderen Zweiges 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, dann ist es für die Bewahrung der Geschichte auf lange Sicht weitgehend in Ordnung. Nur B1 (das Master sein könnte) wird sich typischerweise weiterentwickeln, so dass die Position von B2 in der Historie von B2 erfolgreich den Punkt repräsentiert, an dem es in B1 zusammengeführt wurde. Das ist es, was Git von Ihnen erwartet, nämlich dass Sie B aus A verzweigen, dann können Sie A in B zusammenführen, so viel Sie wollen, wenn sich Änderungen ansammeln. Wenn Sie an Ihrem Zweig weiterarbeiten, nachdem Sie ihn im Schnelldurchlauf zurück in den Zweig, an dem Sie gearbeitet haben, zusammengeführt haben, löschen Sie jedes Mal die vorherige Geschichte von B. Sie erstellen wirklich jedes Mal einen neuen Zweig, nachdem Sie im Schnelldurchlauf in die Quelle und dann in den Zweig übertragen haben. Das Ergebnis sind viele Zweige, die Sie in der Historie und der Struktur sehen können, aber ohne die Möglichkeit, den Namen des Zweiges zu bestimmen oder ob das, was wie zwei getrennte Zweige aussieht, wirklich derselbe Zweig ist.

         0   1   2   3   4 (B1)
        /-\ /-\ /-\ /-\ /
    ----   -   -   -   -
        \-/ \-/ \-/ \-/ \
         5   6   7   8   9 (B2)

1 bis 3 und 5 bis 8 sind strukturelle Zweige, die auftauchen, wenn man den Verlauf von 4 oder 9 verfolgt. In Git gibt es keine Möglichkeit zu wissen, welche dieser unbenannten und nicht referenzierten Strukturzweige zu einem der benannten und referenzierten Zweige als Ende der Struktur gehören. Aus dieser Zeichnung könnte man schließen, dass 0 bis 4 zu B1 und 4 bis 9 zu B2 gehören, aber abgesehen von 4 und 9 kann man nicht wissen, welcher Zweig zu welchem Zweig gehört, ich habe es einfach so gezeichnet, dass es den Anschein erweckt. 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 Strukturzweige gehören könnte. Dies setzt voraus, dass keiner dieser strukturellen Zweige von einem gelöschten Zweig stammt oder als Ergebnis des Zusammenführens eines Zweiges in sich selbst, wenn er von Master gezogen wird (derselbe Zweigname auf zwei Repos ist in Wirklichkeit zwei Zweige, ein separates Repository ist wie eine Verzweigung aller Zweige).

Es gibt eine Reihe von Git-Strategien, mit denen sich dies umgehen lässt. Sie können Git Merge dazu zwingen, nie vorzuspulen und immer einen Merge-Zweig zu erstellen. Eine schreckliche Möglichkeit, die Historie von Zweigen zu bewahren, ist die Verwendung von Tags und/oder Zweigen (Tags sind wirklich zu empfehlen) nach einer von Ihnen gewählten Konvention. Ich würde wirklich nicht empfehlen, einen leeren Dummy-Commit in den Zweig zu setzen, in den man zusammenführt. Eine sehr gebräuchliche Konvention ist es, nicht in einen Integrationszweig einzubinden, bis Sie Ihren Zweig wirklich schließen wollen. Dies ist eine Praxis, die man versuchen sollte einzuhalten, da man sonst den Sinn von Zweigen umgeht. In der realen Welt ist das Ideal jedoch nicht immer praktikabel, d.h. das Richtige zu tun ist nicht in jeder Situation machbar. Wenn das was Sie auf einem Zweig machen isoliert ist, kann das funktionieren, aber ansonsten könnten Sie sich in einer Situation befinden, in der mehrere Entwickler an etwas arbeiten und ihre Änderungen schnell austauschen müssen (idealerweise sollten Sie wirklich an einem Zweig arbeiten, aber auch das passt nicht zu allen Situationen und im Allgemeinen ist die Arbeit von zwei Personen an einem Zweig etwas, was Sie vermeiden möchten).

3voto

FelipeC Punkte 7866

Hier ist eine verbesserte Version meiner vorherigen Antwort vorherige Antwort . Es stützt sich auf die Commit-Meldungen von Zusammenführungen, um herauszufinden, wo der Zweig zuerst erstellt wurde.

Es funktioniert mit allen hier erwähnten Repositories, und ich habe sogar einige schwierige angesprochen, die die auf der Mailingliste entstanden sind . Ich auch schrieb Tests für diese.

find_merge ()
{
    local selection extra
    test "$2" && extra=" into $2"
    git rev-list --min-parents=2 --grep="Merge branch '$1'$extra" --topo-order ${3:---all} | tail -1
}

branch_point ()
{
    local first_merge second_merge merge
    first_merge=$(find_merge $1 "" "$1 $2")
    second_merge=$(find_merge $2 $1 $first_merge)
    merge=${second_merge:-$first_merge}

    if [ "$merge" ]; then
        git merge-base $merge^1 $merge^2
    else
        git merge-base $1 $2
    fi
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X