Mit Git einen Verzweigungspunkt finden?


454

Ich habe ein Repository mit Filialmaster und A und viele Zusammenführungsaktivitäten zwischen den beiden. Wie kann ich das Commit in meinem Repository finden, wenn Zweig A basierend auf dem Master erstellt wurde?

Mein Repository sieht im Grunde so aus:

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

Ich suche nach Revision A, die ich nicht git merge-base (--all)finde.


Antworten:


498

Ich habe nach dem gleichen gesucht und diese Frage gefunden. Danke, dass du es gefragt hast!

Ich habe jedoch festgestellt, dass die Antworten, die ich hier sehe, nicht ganz die Antwort zu geben scheinen, nach der Sie gefragt haben (oder nach der ich gesucht habe) - sie scheinen das GCommit anstelle des ACommits zu geben.

Also habe ich den folgenden Baum erstellt (Buchstaben in chronologischer Reihenfolge zugewiesen), damit ich die Dinge testen kann:

A - B - D - F - G   <- "master" branch (at G)
     \   \     /
      C - E --'     <- "topic" branch (still at E)

Das sieht ein bisschen anders aus als deins, weil ich sicherstellen wollte, dass ich (bezogen auf dieses Diagramm, nicht deins) B, aber nicht A (und nicht D oder E) habe. Hier sind die Buchstaben, die an SHA-Präfixe und Commit-Nachrichten angehängt sind (mein Repo kann von hier aus geklont werden , wenn dies für jemanden interessant ist):

G: a9546a2 merge from topic back to master
F: e7c863d commit on master after master was merged to topic
E: 648ca35 merging master onto topic
D: 37ad159 post-branch commit on master
C: 132ee2a first commit on topic branch
B: 6aafd7f second commit on master before branching
A: 4112403 initial commit on master

So ist das Ziel: Finden Sie B . Hier sind drei Möglichkeiten, die ich nach einigem Basteln gefunden habe:


1. visuell mit gitk:

Sie sollten einen Baum wie diesen visuell sehen (vom Master aus gesehen):

Gitk-Screenshot vom Master

oder hier (vom Thema aus gesehen):

Gitk-Screenshot vom Thema

In beiden Fällen habe ich das Commit Bin meinem Diagramm ausgewählt. Sobald Sie darauf klicken, wird die vollständige SHA in einem Texteingabefeld direkt unter dem Diagramm angezeigt.


2. visuell, aber vom Terminal aus:

git log --graph --oneline --all

(Bearbeiten / Randnotiz: Hinzufügen --decoratekann auch interessant sein; es fügt eine Angabe von Zweignamen, Tags usw. hinzu. Dies wird nicht zur obigen Befehlszeile hinzugefügt, da die Ausgabe unten nicht die Verwendung widerspiegelt.)

was zeigt (vorausgesetzt git config --global color.ui auto):

Ausgabe von git log --graph --oneline --all

Oder im geraden Text:

* a9546a2 vom Thema zurück zum Master zusammenführen
| \  
| * 648ca35 Master zum Thema zusammenführen
| | \  
| * | 132ee2a erstes Commit für den Themenzweig
* | | e7c863d Commit für Master, nachdem Master zum Thema zusammengeführt wurde
| | /  
| / |   
* | 37ad159 Post-Branch-Commit für Master
| /  
* 6aafd7f zweite Festschreibung auf dem Master vor der Verzweigung
* 4112403 Erstes Commit auf dem Master

In beiden Fällen sehen wir das 6aafd7f-Commit als den niedrigsten gemeinsamen Punkt, dh Bin meinem Diagramm oder Ain Ihrem.


3. Mit Muschelmagie:

Sie geben in Ihrer Frage nicht an, ob Sie etwas wie das oben Genannte oder einen einzelnen Befehl wünschen, mit dem Sie nur die eine Revision erhalten, und sonst nichts. Nun, hier ist letzteres:

diff -u <(git rev-list --first-parent topic) \
             <(git rev-list --first-parent master) | \
     sed -ne 's/^ //p' | head -1
6aafd7ff98017c816033df18395c5c1e7829960d

Was Sie auch in Ihre ~ / .gitconfig einfügen können als (Hinweis: Der nachgestellte Bindestrich ist wichtig; danke Brian, dass er darauf aufmerksam gemacht hat) :

[alias]
    oldest-ancestor = !zsh -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -

Dies kann über die folgende (mit Anführungszeichen verwickelte) Befehlszeile erfolgen:

git config --global alias.oldest-ancestor '!zsh -c '\''diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne "s/^ //p" | head -1'\'' -'

Hinweis: zshkönnte genauso gut gewesen sein bash, aber shwird nicht Arbeit - die <()Syntax existiert nicht in Vanille sh. (Nochmals vielen Dank, @conny, dass Sie mich in einem Kommentar zu einer anderen Antwort auf dieser Seite darauf aufmerksam gemacht haben!)

Hinweis: Alternative Version der oben genannten:

Vielen Dank an liori für den Hinweis, dass das oben Gesagte beim Vergleich identischer Zweige herunterfallen könnte und eine alternative Diff-Form gefunden wird, die die sed-Form aus der Mischung entfernt und diese "sicherer" macht (dh ein Ergebnis zurückgibt (nämlich die letztes Commit), auch wenn Sie Master mit Master vergleichen):

Als .git-config-Zeile:

[alias]
    oldest-ancestor = !zsh -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1' -

Aus der Schale:

git config --global alias.oldest-ancestor '!zsh -c '\''diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1'\'' -'

In meinem Testbaum (der für eine Weile nicht verfügbar war, tut mir leid; er ist zurück) funktioniert dies nun sowohl für den Master als auch für das Thema (Festschreiben von G bzw. B). Nochmals vielen Dank, Liori, für die alternative Form.


Das habe ich mir [und Liori] ausgedacht. Es scheint für mich zu funktionieren. Es erlaubt auch ein paar zusätzliche Aliase, die sich als nützlich erweisen könnten:

git config --global alias.branchdiff '!sh -c "git diff `git oldest-ancestor`.."'
git config --global alias.branchlog '!sh -c "git log `git oldest-ancestor`.."'

Viel Spaß beim Spielen!


5
Dank lindes eignet sich die Shell-Option hervorragend für Situationen, in denen Sie den Verzweigungspunkt eines lang laufenden Wartungszweigs finden möchten. Wenn Sie nach einer Revision suchen, die in der Vergangenheit möglicherweise tausend Commits umfasst, werden die visuellen Optionen sie wirklich nicht beeinträchtigen. * 8 ')
Mark Booth

4
Bei Ihrer dritten Methode hängen Sie davon ab, dass der Kontext die erste unveränderte Zeile anzeigt. Dies ist in bestimmten Randfällen nicht der Fall oder wenn Sie etwas andere Anforderungen haben (z. B. muss nur einer der Historien --first-parent sein, und ich verwende diese Methode in einem Skript, das manchmal dieselbe verwendet Zweige auf beiden Seiten). Ich fand es sicherer, diffden Wenn-Dann-Sonst-Modus zu verwenden und geänderte / gelöschte Zeilen aus der Ausgabe zu löschen, anstatt auf einen ausreichend großen Kontext zu zählen diff --old-line-format='' --new-line-format='' <(git rev-list …) <(git rev-list …)|head -1.
Liori

3
git log -n1 --format=format:%H $(git log --reverse --format=format:%H master..topic | head -1)~wird auch funktionieren, denke ich
Tobias Schulte

4
@ JakubNarębski: Haben Sie eine Möglichkeit git merge-base --fork-point ..., Commit B (Commit 6aafd7f) für diesen Baum zu geben? Ich war mit anderen Dingen beschäftigt, als du das gepostet hast, und es klang gut, aber ich habe es endlich versucht, und ich bringe es nicht zum Laufen ... Ich bekomme entweder etwas Neueres oder nur einen stillen Fehler (kein Fehler) Nachricht, aber Exit - Status 1), Argumente wie der Versuch git co master; git merge-base --fork-point topic, git co topic; git merge-base --fork-point master, git merge-base --fork-point topic master(für beide Kasse) usw. gibt es etwas , was ich falsch mache oder fehlt?
Lindes

25
@ JakubNarębski @lindes --fork-pointbasiert auf dem Reflog, daher funktioniert es nur, wenn Sie die Änderungen lokal vorgenommen haben. Selbst dann könnten die Reflog-Einträge abgelaufen sein. Es ist nützlich, aber überhaupt nicht zuverlässig.
Daniel Lubarov

131

Sie suchen vielleicht git merge-base:

git merge-base findet die besten gemeinsamen Vorfahren zwischen zwei Commits, die für eine Drei-Wege-Zusammenführung verwendet werden sollen. Ein gemeinsamer Vorfahr ist besser als ein anderer gemeinsamer Vorfahr, wenn der letztere ein Vorfahr des ersteren ist. Ein gemeinsamer Vorfahr, der keinen besseren gemeinsamen Vorfahren hat, ist ein bester gemeinsamer Vorfahr , dh eine Zusammenführungsbasis . Beachten Sie, dass es für ein Commit-Paar mehr als eine Zusammenführungsbasis geben kann.


10
Beachten Sie auch die --allOption zu " git merge-base"
Jakub Narębski

10
Dies beantwortet nicht die ursprüngliche Frage, aber die meisten Leute stellen die viel einfachere Frage, auf die dies die Antwort ist :)
FelipeC

6
Er sagte, er wolle nicht das Ergebnis der Git Merge-Base sein
Tom Tanner

9
@ TomTanner: Ich habe mir gerade den Fragenverlauf angesehen und die ursprüngliche Frage wurde so bearbeitet, dass sie diese Notiz ungefähr git merge-basefünf Stunden nach dem Posten meiner Antwort enthält (wahrscheinlich als Antwort auf meine Antwort). Trotzdem werde ich diese Antwort unverändert lassen, da sie für andere Personen, die diese Frage über die Suche finden, möglicherweise noch nützlich ist.
Greg Hewgill

Ich glaube, Sie könnten die Ergebnisse von merge-base - all verwenden und dann die Liste der Rückgabe-Commits mit merge-base --is-ancestor durchsuchen, um den ältesten gemeinsamen Vorfahren aus der Liste zu finden, aber dies ist nicht der einfachste Weg .
Matt_G

42

Ich habe git rev-listfür so etwas verwendet. Zum Beispiel (beachten Sie die 3 Punkte)

$ git rev-list --boundary branch-a...master | grep "^-" | cut -c2-

wird den Verzweigungspunkt ausspucken. Jetzt ist es nicht perfekt; Da Sie Master ein paar Mal in Zweig A zusammengeführt haben, werden einige mögliche Verzweigungspunkte aufgeteilt (im Grunde der ursprüngliche Verzweigungspunkt und dann jeder Punkt, an dem Sie Master in Zweig A zusammengeführt haben). Es sollte jedoch zumindest die Möglichkeiten einschränken.

Ich habe diesen Befehl zu meinen Aliasnamen hinzugefügt ~/.gitconfigals:

[alias]
    diverges = !sh -c 'git rev-list --boundary $1...$2 | grep "^-" | cut -c2-'

so kann ich es nennen als:

$ git diverges branch-a master

Hinweis: Dies scheint eher das erste Commit für den Zweig als den gemeinsamen Vorfahren zu geben. (dh es gibt Gstatt A, gemäß der Grafik in der ursprünglichen Frage.) Ich habe eine Antwort, die bekommt A, dass ich gleich posten werde.
Lindes

@ Lindes: Es gibt den gemeinsamen Vorfahren in jedem Fall, in dem ich es versucht habe. Haben Sie ein Beispiel, wo dies nicht der Fall ist?
Mipadi

Ja. In meiner Antwort (die einen Link zu einem Repo enthält, das Sie klonen können, git checkout topicund das Sie dann topicanstelle von ausführen können branch-a) werden 648ca357b946939654da12aaf2dc072763f3caeeund 37ad15952db5c065679d7fc31838369712f0b338- beide aufgelistet 37ad159und 648ca35befinden sich im Vorfahren der aktuellen Zweige (letzterer ist der aktuelle HEAD vontopic ). aber es ist auch nicht der Punkt vor der Verzweigung. Bekommst du etwas anderes
Lindes

@lindes: Ich konnte Ihr Repo nicht klonen (möglicherweise ein Berechtigungsproblem?).
Mipadi

Ups, tut mir Leid! Danke, dass Sie mich informiert haben. Ich habe vergessen, git update-server-info auszuführen. Es sollte gut sein, jetzt zu gehen. :)
Lindes

31

Wenn Sie knappe Befehle mögen,

git rev-list $ (git rev-liste --first-parent ^ branch_name master | tail -n1) ^^! 

Hier ist eine Erklärung.

Mit dem folgenden Befehl erhalten Sie eine Liste aller Commits im Master, die nach dem Erstellen von branch_name aufgetreten sind

git rev-list --first-parent ^ branch_name master 

Da Sie sich nur um die frühesten dieser Commits kümmern, möchten Sie die letzte Zeile der Ausgabe:

git rev-list ^ branch_name --first-parent master | Schwanz -n1

Das übergeordnete Element des frühesten Commits, das kein Vorfahr von "branch_name" ist, befindet sich per Definition in "branch_name" und in "master", da es ein Vorfahr von etwas in "master" ist. Sie haben also das früheste Commit in beiden Branchen.

Der Befehl

git rev-list commit ^^!

ist nur eine Möglichkeit, die übergeordnete Commit-Referenz anzuzeigen. Du könntest benutzen

git log -1 commit ^

oder Wasauchimmer.

PS: Ich bin mit dem Argument nicht einverstanden, dass die Reihenfolge der Vorfahren irrelevant ist. Es kommt darauf an, was Sie wollen. Zum Beispiel in diesem Fall

_C1___C2_______ Master
  \ \ _XXXXX_ Zweig A (die Xs bezeichnen willkürliche Überkreuzungen zwischen Master und A)
   \ _____ / Zweig B.

Es ist absolut sinnvoll, C2 als "Verzweigungs" -Commit auszugeben. Dies ist der Zeitpunkt, an dem der Entwickler von "master" abzweigte. Als er verzweigte, wurde Zweig "B" nicht einmal in seinem Zweig zusammengeführt! Dies ist, was die Lösung in diesem Beitrag gibt.

Wenn Sie das letzte Commit C wünschen, sodass alle Pfade vom Ursprung bis zum letzten Commit in Zweig "A" durch C verlaufen, möchten Sie die Reihenfolge der Vorfahren ignorieren. Das ist rein topologisch und gibt Ihnen eine Vorstellung davon, wann zwei Versionen des Codes gleichzeitig ausgeführt werden. Dann würden Sie sich für merge-basierte Ansätze entscheiden, und in meinem Beispiel wird C1 zurückgegeben.


3
Dies ist bei weitem die sauberste Antwort. Lassen Sie uns dies nach oben abstimmen. Eine vorgeschlagene Änderung: git rev-list commit^^!kann vereinfacht werden alsgit rev-parse commit^
Russell Davis

Dies sollte die Antwort sein!
Trinth

2
Diese Antwort ist schön, ich habe gerade ersetzt git rev-list --first-parent ^branch_name mastermit , git rev-list --first-parent branch_name ^masterdenn wenn der Meister Zweig 0 Commits vor dem anderen Zweig (schnell weiterleitbares es), so würde kein Ausgang erzeugt werden. Mit meiner Lösung wird keine Ausgabe erstellt, wenn der Master streng voraus ist (dh der Zweig wurde vollständig zusammengeführt), was ich möchte.
Michael Schmeißer

3
Dies wird nicht funktionieren, wenn mir etwas völlig fehlt. In den Beispielzweigen gibt es Zusammenführungen in beide Richtungen. Es hört sich so an, als hätten Sie versucht, dies zu berücksichtigen, aber ich glaube, dies wird dazu führen, dass Ihre Antwort fehlschlägt. git rev-list --first-parent ^topic masterbringt Sie erst nach dem letzten Zusammenführen von masterin zum ersten Commit zurück topic(falls das überhaupt existiert).
Jerry

1
@ Jerry Du bist richtig, diese Antwort ist Müll; Für den Fall, dass gerade ein Backmerge stattgefunden hat (Master in Topic zusammengeführt) und Master danach keine neuen Commits mehr hat, gibt der erste Befehl git rev-list --first-parent überhaupt nichts aus.
TamaMcGlinn

19

Da so viele der Antworten in diesem Thread nicht die Antwort geben, nach der die Frage gefragt wurde, finden Sie hier eine Zusammenfassung der Ergebnisse jeder Lösung sowie das Skript, mit dem ich das in der Frage angegebene Repository repliziert habe.

Das Protokoll

Wenn wir ein Repository mit der angegebenen Struktur erstellen, erhalten wir das Git-Protokoll von:

$ git --no-pager log --graph --oneline --all --decorate
* b80b645 (HEAD, branch_A) J - Work in branch_A branch
| *   3bd4054 (master) F - Merge branch_A into branch master
| |\  
| |/  
|/|   
* |   a06711b I - Merge master into branch_A
|\ \  
* | | bcad6a3 H - Work in branch_A
| | * b46632a D - Work in branch master
| |/  
| *   413851d C - Merge branch_A into branch master
| |\  
| |/  
|/|   
* | 6e343aa G - Work in branch_A
| * 89655bb B - Work in branch master
|/  
* 74c6405 (tag: branch_A_tag) A - Work in branch master
* 7a1c939 X - Work in branch master

Meine einzige Ergänzung ist das Tag, das explizit den Punkt angibt, an dem wir den Zweig erstellt haben, und damit das Commit, das wir finden möchten.

Die Lösung, die funktioniert

Die einzige Lösung, die funktioniert, ist die von lindes bereitgestellte, die korrekt zurückgibt A:

$ diff -u <(git rev-list --first-parent branch_A) \
          <(git rev-list --first-parent master) | \
      sed -ne 's/^ //p' | head -1
74c6405d17e319bd0c07c690ed876d65d89618d5

Wie Charles Bailey jedoch betont, ist diese Lösung sehr spröde.

Wenn Sie branch_Ain masterund dann verschmelzen masterin branch_Aohne Lösung Commits dann Lindes' dazwischen gibt Ihnen nur die neueste zuerst divergance .

Das bedeutet, dass ich mich für meinen Workflow an das Markieren des Verzweigungspunkts lang laufender Zweige halten muss, da ich nicht garantieren kann, dass sie später zuverlässig gefunden werden können.

Das alles läuft darauf hinaus, dass es an dem gitmangelt, was benannte Zweigehg nennt . Der Blogger jhw nennt diese Abstammungslinien vs. Familien in seinem Artikel Warum ich Mercurial mehr als Git mag und seinem Folgeartikel More On Mercurial vs. Git (mit Grafiken!) . Ich würde den Leuten empfehlen, sie zu lesen, um zu sehen, warum einige Quecksilberkonvertierte es vermissen, keine Zweige benannt zu habengit .

Die Lösungen, die nicht funktionieren

Die Lösung von mipadi gibt zwei Antworten, Iund C:

$ git rev-list --boundary branch_A...master | grep ^- | cut -c2-
a06711b55cf7275e8c3c843748daaa0aa75aef54
413851dfecab2718a3692a4bba13b50b81e36afc

Die von Greg Hewgill bereitgestellte Lösung kehrt zurückI

$ git merge-base master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54
$ git merge-base --all master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54

Die von Karl bereitgestellte Lösung kehrt zurück X:

$ diff -u <(git log --pretty=oneline branch_A) \
          <(git log --pretty=oneline master) | \
       tail -1 | cut -c 2-42
7a1c939ec325515acfccb79040b2e4e1c3e7bbe5

Das Skript

mkdir $1
cd $1
git init
git commit --allow-empty -m "X - Work in branch master"
git commit --allow-empty -m "A - Work in branch master"
git branch branch_A
git tag branch_A_tag     -m "Tag branch point of branch_A"
git commit --allow-empty -m "B - Work in branch master"
git checkout branch_A
git commit --allow-empty -m "G - Work in branch_A"
git checkout master
git merge branch_A       -m "C - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "H - Work in branch_A"
git merge master         -m "I - Merge master into branch_A"
git checkout master
git commit --allow-empty -m "D - Work in branch master"
git merge branch_A       -m "F - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "J - Work in branch_A branch"

Ich bezweifle, dass die Git-Version einen großen Unterschied macht, aber:

$ git --version
git version 1.7.1

Vielen Dank an Charles Bailey , der mir eine kompaktere Möglichkeit gezeigt hat, das Beispiel-Repository zu skripten.


2
Die Lösung von Karl ist leicht zu beheben : diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1. Vielen Dank für die Bereitstellung von Anweisungen zum Erstellen des
Repos

Ich denke, Sie meinen "Die bereinigte Variante der von Karl bereitgestellten Lösung gibt X zurück". Das Original hat gut funktioniert, es war einfach hässlich :-)
Karl

Nein, dein Original funktioniert nicht gut. Zugegeben, die Variation funktioniert noch schlimmer. Aber wenn Sie die Option --topo-order hinzufügen, funktioniert Ihre Version :)
FelipeC

@felipec - Siehe meinen letzten Kommentar zur Antwort von Charles Bailey . Leider wurde unser Chat (und damit alle alten Kommentare) jetzt gelöscht. Ich werde versuchen, meine Antwort zu aktualisieren, wenn ich Zeit habe.
Mark Booth

Interessant. Ich hatte angenommen, dass topologisch die Standardeinstellung war. Blöd mich :-)
Karl

10

Dies ist im Allgemeinen nicht möglich. In einer Zweighistorie wurde ein Zweig-und-Zusammenführen vor dem Verzweigen eines benannten Zweigs und ein Zwischenzweig von zwei benannten Zweigen gleich aussehen.

In git sind Zweige nur die aktuellen Namen der Spitzen von Abschnitten der Geschichte. Sie haben nicht wirklich eine starke Identität.

Dies ist normalerweise kein großes Problem, da die Zusammenführungsbasis (siehe Greg Hewgills Antwort) von zwei Commits normalerweise viel nützlicher ist und das letzte Commit ergibt, das die beiden Zweige gemeinsam genutzt haben.

Eine Lösung, die sich auf die Reihenfolge der Eltern eines Commits stützt, funktioniert offensichtlich nicht in Situationen, in denen ein Zweig zu einem bestimmten Zeitpunkt in der Geschichte des Zweigs vollständig integriert wurde.

git commit --allow-empty -m root # actual branch commit
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git merge --ff-only master
git commit --allow-empty -m "More work on branch_A"
git checkout master
git commit --allow-empty -m "More work on master"

Diese Technik fällt auch dann aus, wenn eine Integrationszusammenführung mit den Eltern in umgekehrter Reihenfolge durchgeführt wurde (z. B. wurde ein temporärer Zweig verwendet, um eine Testzusammenführung in den Master durchzuführen, und dann in den Feature-Zweig vorgespult, um weiter darauf aufzubauen).

git commit --allow-empty -m root # actual branch point
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git commit --allow-empty -m "More work on branch_A"

git checkout -b tmp-branch master
git merge -m "Merge branch_A into tmp-branch (master copy)" branch_A
git checkout branch_A
git merge --ff-only tmp-branch
git branch -d tmp-branch

git checkout master
git commit --allow-empty -m "More work on master"


3
Danke Charles, Sie haben mich überzeugt, wenn ich wissen will, an welchem ​​Punkt der Zweig ursprünglich auseinander gegangen ist, muss ich ihn markieren. Ich wünschte wirklich, das githätte ein Äquivalent zu hgden genannten Filialen, was die Verwaltung langlebiger Wartungsfilialen so viel einfacher machen würde.
Mark Booth

1
"In Git sind Zweige nur die aktuellen Namen der Tipps von Abschnitten der Geschichte. Sie haben nicht wirklich eine starke Identität." Das ist beängstigend zu sagen und hat mich überzeugt, dass ich Git-Zweige besser verstehen muss - danke (+ 1)
Dumbledad

In einer Zweighistorie wurde ein Zweig-und-Zusammenführen vor dem Verzweigen eines benannten Zweigs und ein Zwischenzweig von zwei benannten Zweigen gleich aussehen. Ja. +1.
user1071847

5

Wie wäre es mit so etwas

git log --pretty=oneline master > 1
git log --pretty=oneline branch_A > 2

git rev-parse `diff 1 2 | tail -1 | cut -c 3-42`^

Das funktioniert. Es ist wirklich umständlich, aber es ist das einzige, was ich gefunden habe, das tatsächlich den Job zu machen scheint.
GaryO

1
Git-Alias-Äquivalent: diverges = !bash -c 'git rev-parse $(diff <(git log --pretty=oneline ${1}) <(git log --pretty=oneline ${2}) | tail -1 | cut -c 3-42)^'(ohne temporäre Dateien)
Conny

@conny: Oh, wow - ich habe die <(foo) -Syntax noch nie gesehen ... das ist unglaublich nützlich, danke! (Funktioniert auch in zsh, FYI.)
Lindes

Dies scheint mir eher das erste Commit für den Zweig zu geben als den gemeinsamen Vorfahren. (dh es gibt Gstatt Agemäß der Grafik in der ursprünglichen Frage.) Ich denke, ich habe jedoch eine Antwort gefunden, die ich gleich veröffentlichen werde.
Lindes

Anstelle von 'git log --pretty = oneline' können Sie einfach 'git rev-list' ausführen, dann können Sie auch den Schnitt überspringen. Außerdem gibt dies dem übergeordneten Commit den Punkt der Divergenz, also nur tail -2 | Kopf 1. Also:diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1
FelipeC

5

Sicherlich fehlt mir etwas, aber IMO, alle oben genannten Probleme werden verursacht, weil wir immer versuchen, den Verzweigungspunkt zu finden, der in den Verlauf zurückgeht, und das verursacht aufgrund der verfügbaren Zusammenführungskombinationen alle möglichen Probleme.

Stattdessen habe ich einen anderen Ansatz gewählt, der auf der Tatsache basiert, dass beide Zweige viel Geschichte gemeinsam haben. Genau die gesamte Geschichte vor der Verzweigung ist zu 100% gleich. Statt zurückzugehen, geht es in meinem Vorschlag darum, vorwärts zu gehen (ab dem 1.) begehen) und suchen nach dem 1. Unterschied in beiden Zweigen. Der Verzweigungspunkt ist einfach das übergeordnete Element des ersten gefundenen Unterschieds.

In der Praxis:

#!/bin/bash
diff <( git rev-list "${1:-master}" --reverse --topo-order ) \
     <( git rev-list "${2:-HEAD}" --reverse --topo-order) \
--unified=1 | sed -ne 's/^ //p' | head -1

Und es löst alle meine üblichen Fälle. Sicher gibt es Grenzabdeckungen, die nicht abgedeckt sind, aber ... ciao :-)


diff <(Git-Rev-Liste "$ {1: -master}" - First-Parent) <(Git-Rev-Liste "$ {2: -HEAD}" - First-Parent) -U1 | Schwanz -1
Alexander Bird

Ich fand das schneller (2-100x):comm --nocheck-order -1 -2 <(git rev-list --reverse --topo-order topic) <(git rev-list --reverse --topo-order master) | head -1
Andrei Neculau


4

Nach vielen Recherchen und Diskussionen ist klar, dass es kein Wundermittel gibt, das in allen Situationen funktionieren würde, zumindest nicht in der aktuellen Version von Git.

Deshalb habe ich ein paar Patches geschrieben, die das Konzept eines tailZweigs hinzufügen . Jedes Mal, wenn ein Zweig erstellt wird, wird auch ein Zeiger auf den ursprünglichen Punkt erstellt, die tailReferenz. Diese Referenz wird jedes Mal aktualisiert, wenn der Zweig neu basiert.

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

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


1
Könnte die einzige stabile Lösung sein. Hast du gesehen, ob das in den Arsch geraten könnte? Ich habe keine Pull-Anfrage gesehen.
Alexander Klimetschek

@AlexanderKlimetschek Ich habe die Patches nicht gesendet und ich glaube nicht, dass diese akzeptiert werden. Ich habe jedoch eine andere Methode ausprobiert: einen "Update-Branch" -Hook, der etwas sehr Ähnliches tut. Auf diese Weise würde Git standardmäßig nichts tun, aber Sie könnten den Hook aktivieren, um den Endzweig zu aktualisieren. Du hättest zwar nicht devel @ {tail}, aber es wäre nicht so schlecht, stattdessen tails / devel zu verwenden.
FelipeC

3

Hier ist eine verbesserte Version meiner vorherigen Antwort . Es stützt sich auf die Festschreibungsnachrichten aus Zusammenführungen, um herauszufinden, wo der Zweig zuerst erstellt wurde.

Es funktioniert auf allen hier erwähnten Repositories, und ich habe sogar einige knifflige angesprochen, die auf der Mailingliste aufgetaucht sind . Ich habe auch Tests dafür geschrieben.

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
}

3

Der folgende Befehl zeigt den SHA1 von Commit A.

git merge-base --fork-point A


Dies ist nicht der Fall, wenn Eltern- und Kinderzweige dazwischen Zwischenverschmelzungen aufweisen.
Nawfal

2

Ich scheine etwas Freude damit zu haben

git rev-list branch...master

Die letzte Zeile, die Sie erhalten, ist das erste Commit für den Zweig. Dann geht es darum, das übergeordnete Element zu ermitteln. Damit

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

Scheint für mich zu funktionieren und benötigt keine Unterschiede usw. (was hilfreich ist, da wir diese Version von diff nicht haben)

Korrektur: Dies funktioniert nicht, wenn Sie sich im Hauptzweig befinden, aber ich mache dies in einem Skript, sodass dies weniger problematisch ist


2

Sie können dies verwenden, um Commits vom Verzweigungspunkt aus zu finden.

git log --ancestry-path master..topicbranch

Dieser Befehl funktioniert bei dem angegebenen Beispiel nicht. Bitte was würden Sie als Parameter für den Festschreibungsbereich angeben?
Jesper Rønn-Jensen

2

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 '


"Git bewahrt den Ref-Verlauf nicht auf" Das tut es, aber nicht standardmäßig und nicht für längere Zeit. Siehe man git-reflogund den Teil über Daten: "master@{one.week.ago} bedeutet" wo der Master vor einer Woche in diesem lokalen Repository darauf hingewiesen hat "". Oder die Diskussion am <refname>@{<date>}in man gitrevisions. Und core.reflogExpirein man git-config.
Patrick Mevzek

2

Keine Lösung für die Frage, aber ich dachte, es lohnt sich, den Ansatz zu erwähnen, den ich verwende, wenn ich einen langlebigen Zweig habe:

Während ich den Zweig erstelle, erstelle ich auch ein Tag mit demselben Namen, aber mit einem -initSuffix, zum Beispiel feature-branchund feature-branch-init.

(Es ist irgendwie bizarr, dass diese Frage so schwer zu beantworten ist!)


1
In Anbetracht der verblüffenden Dummheit, ein Konzept des "Zweigs" zu entwerfen, ohne zu wissen, wann und wo es erstellt wurde ... und der immensen Komplikationen anderer Lösungsvorschläge - von Menschen, die versuchen, diesen viel zu intelligenten Begriff zu übertreffen Ich denke, ich würde Ihre Lösung vorziehen. Nur belastet es einen mit der Notwendigkeit, sich daran zu erinnern, dies jedes Mal zu tun, wenn Sie einen Zweig erstellen - eine Sache, die Git-Benutzer sehr oft machen. Außerdem habe ich irgendwo gelesen, dass "Tags" die Strafe haben, "schwer" zu sein. Trotzdem denke ich, dass ich das tun werde.
Motti Shneor

1

Eine einfache Möglichkeit, das Erkennen des Verzweigungspunkts zu vereinfachen, git log --graphist die Verwendung der Option --first-parent.

Nehmen Sie zum Beispiel das Repo aus der akzeptierten Antwort :

$ git log --all --oneline --decorate --graph

*   a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
|\  
| *   648ca35 (origin/topic) merging master onto topic
| |\  
| * | 132ee2a first commit on topic branch
* | | e7c863d commit on master after master was merged to topic
| |/  
|/|   
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

Fügen Sie nun hinzu --first-parent:

$ git log --all --oneline --decorate --graph --first-parent

* a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
| * 648ca35 (origin/topic) merging master onto topic
| * 132ee2a first commit on topic branch
* | e7c863d commit on master after master was merged to topic
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

Das macht es einfacher!

Beachten Sie, dass Sie, wenn das Repo viele Zweige hat, die 2 Zweige angeben möchten, die Sie vergleichen, anstatt Folgendes zu verwenden --all:

$ git log --decorate --oneline --graph --first-parent master origin/topic

0

Das Problem scheint darin zu bestehen, den jüngsten Single-Commit- Schnitt zwischen beiden Zweigen auf der einen Seite und dem frühesten gemeinsamen Vorfahren auf der anderen Seite zu finden (wahrscheinlich das anfängliche Commit des Repos). Dies entspricht meiner Intuition, was der "Verzweigungspunkt" ist.

Vor diesem Hintergrund ist dies mit normalen Git-Shell-Befehlen überhaupt nicht einfach zu berechnen, da git rev-listwir - unser leistungsstärkstes Tool - den Pfad, über den ein Commit erreicht wird, nicht einschränken können . Das nächste, das wir haben, ist das git rev-list --boundary, was uns eine Reihe aller Verpflichtungen geben kann, die "unseren Weg blockiert" haben. (Hinweis: git rev-list --ancestry-pathist interessant, aber ich weiß nicht, wie ich es hier nützlich machen kann.)

Hier ist das Skript: https://gist.github.com/abortz/d464c88923c520b79e3d . Es ist relativ einfach, aber aufgrund einer Schleife ist es kompliziert genug, um einen Kern zu rechtfertigen.

Beachten Sie, dass die meisten anderen hier vorgeschlagenen Lösungen aus einem einfachen Grund möglicherweise nicht in allen Situationen funktionieren können: Sie git rev-list --first-parentsind bei der Linearisierung des Verlaufs nicht zuverlässig, da bei beiden Ordnungen Zusammenführungen auftreten können.

git rev-list --topo-orderAuf der anderen Seite ist es sehr nützlich - für das Gehen von Commits in topografischer Reihenfolge -, aber Unterschiede zu machen ist spröde: Es gibt mehrere mögliche topografische Ordnungen für ein bestimmtes Diagramm, sodass Sie von einer bestimmten Stabilität der Ordnungen abhängig sind. Trotzdem funktioniert die Lösung von strongk7 wahrscheinlich die meiste Zeit verdammt gut. Es ist jedoch langsamer als meine, weil ich die gesamte Geschichte des Repos zweimal durchlaufen muss. :-)


0

Im Folgenden wird das Git-Äquivalent von svn log --stop-on-copy implementiert und kann auch zum Ermitteln des Verzweigungsursprungs verwendet werden.

Ansatz

  1. Holen Sie sich Kopf für alle Branchen
  2. sammle mergeBase für Zielzweig einander Zweig
  3. git.log und iterieren
  4. Beenden Sie beim ersten Festschreiben, das in der mergeBase-Liste angezeigt wird

Wie alle Flüsse, die zum Meer fließen, laufen alle Zweige, um zu meistern, und daher finden wir eine Verbindungsbasis zwischen scheinbar nicht verwandten Zweigen. Wenn wir vom Astkopf durch die Vorfahren zurückgehen, können wir an der ersten möglichen Zusammenführungsbasis anhalten, da dies theoretisch der Ursprungspunkt dieses Zweigs sein sollte.

Anmerkungen

  • Ich habe diesen Ansatz nicht ausprobiert, bei dem Geschwister- und Cousin-Zweige miteinander verschmolzen sind.
  • Ich weiß, dass es eine bessere Lösung geben muss.

Details: https://stackoverflow.com/a/35353202/9950


-1

Mit dem folgenden Befehl können Sie das älteste Commit in branch_a zurückgeben, das vom Master nicht erreichbar ist:

git rev-list branch_a ^master | tail -1

Vielleicht mit einer zusätzlichen Plausibilitätsprüfung , dass die Eltern davon zu begehen ist tatsächlich erreichbar von Master ...


1
Das funktioniert nicht. Wenn branch_a einmal mit master zusammengeführt wird und dann fortgesetzt wird, werden die Commits bei dieser Zusammenführung als Teil von master betrachtet, sodass sie nicht in ^ master angezeigt werden.
FelipeC

-2

Sie können das Reflog von Zweig A untersuchen, um herauszufinden, aus welchem ​​Commit er erstellt wurde, sowie den vollständigen Verlauf, auf den Commits in diesem Zweig verwiesen haben. Reflogs sind in .git/logs.


2
Ich denke nicht, dass dies im Allgemeinen funktioniert, da das Reflog beschnitten werden kann. Und ich denke auch nicht, dass (?) Reflogs gepusht werden, also würde dies nur in einer Single-Repo-Situation funktionieren.
GaryO

-2

Ich glaube, ich habe einen Weg gefunden, der mit allen hier genannten Eckfällen umgeht:

branch=branch_A
merge=$(git rev-list --min-parents=2 --grep="Merge.*$branch" --all | tail -1)
git merge-base $merge^1 $merge^2

Charles Bailey hat völlig Recht, dass Lösungen, die auf der Reihenfolge der Vorfahren basieren, nur einen begrenzten Wert haben. Am Ende des Tages benötigen Sie eine Art Datensatz "Dieses Commit stammt aus Zweig X", aber ein solcher Datensatz ist bereits vorhanden. Standardmäßig verwendet 'git merge' eine Festschreibungsnachricht wie "Zweig 'branch_A' in master zusammenführen". Dies zeigt an, dass alle Festschreibungen des zweiten übergeordneten Elements (Festschreiben ^ 2) von 'branch_A' stammen und mit dem ersten zusammengeführt wurden parent (commit ^ 1), das ist 'master'.

Mit diesen Informationen können Sie die erste Zusammenführung von 'branch_A' finden (als 'branch_A' wirklich entstand) und die Zusammenführungsbasis finden, die der Verzweigungspunkt wäre :)

Ich habe es mit den Repositories von Mark Booth und Charles Bailey versucht und die Lösung funktioniert. wie konnte es nicht? Dies funktioniert nur, wenn Sie die Standard-Festschreibungsnachricht für Zusammenführungen manuell geändert haben, sodass die Zweiginformationen wirklich verloren gehen.

Für die Nützlichkeit:

[alias]
    branch-point = !sh -c 'merge=$(git rev-list --min-parents=2 --grep="Merge.*$1" --all | tail -1) && git merge-base $merge^1 $merge^2'

Dann kannst du machen 'git branch-point branch_A ' machen.

Genießen ;)


4
Unter Berufung auf den Merge - Nachrichten ist mehr zerbrechlicher als Hypothesen aufstellen über Mutterauftrag. Es ist auch nicht nur eine hypothetische Situation; Ich git merge -msage häufig , was ich zusammengeführt habe, anstatt den Namen eines potenziell kurzlebigen Zweigs (z. B. "Änderungen der Hauptlinie in Feature-XYZ-Refaktor zusammenführen"). Angenommen, ich wäre -min meinem Beispiel weniger hilfreich gewesen ? Das Problem ist in seiner Gesamtheit einfach nicht lösbar, da ich mit einem oder zwei temporären Zweigen dieselbe Geschichte schreiben kann und es keine Möglichkeit gibt, den Unterschied zu erkennen.
CB Bailey

1
@ CharlesBailey Das ist dann dein Problem. Sie sollten diese Zeilen nicht aus der Festschreibungsnachricht entfernen, sondern den Rest der Nachricht unter der ursprünglichen Nachricht hinzufügen . Heutzutage öffnet 'git merge' automatisch einen Editor, in dem Sie hinzufügen können, was Sie möchten, und für alte Versionen von git können Sie 'git merge --edit' ausführen. In beiden Fällen können Sie einen Commit-Hook verwenden, um jedem Commit ein "Commited on Branch 'foo'" hinzuzufügen, wenn Sie dies wirklich möchten. Diese Lösung funktioniert jedoch für die meisten Menschen .
FelipeC

2
Hat bei mir nicht funktioniert. Der branch_A wurde aus dem Master gegabelt, der bereits viele Zusammenführungen hatte. Diese Logik gab mir nicht den genauen Commit-Hash, in dem branch_A erstellt wurde.
Venkat Kotra
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.