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.

629voto

lindes Punkte 8956

Ich habe das Gleiche gesucht und bin auf diese Frage gestoßen. Danke, dass Sie sie gestellt haben!

Ich habe jedoch festgestellt, dass die Antworten, die ich hier finde, nicht den Tatsachen entsprechen. ziemlich die Antwort geben, nach der Sie gefragt haben (oder nach der ich gesucht habe) - sie scheinen die G anstelle der A verpflichten.

Ich habe also den folgenden Baum erstellt (die Buchstaben sind in chronologischer Reihenfolge zugeordnet), 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 wenig anders aus als bei Ihnen, denn ich wollte sicherstellen, dass ich (bezogen auf dieses Diagramm, nicht auf Ihres) B, aber nicht A (und nicht D oder E) erhalte. Hier sind die Buchstaben, die an SHA-Präfixe und Commit-Nachrichten angehängt sind (mein Repo kann geklont werden von aquí (falls das jemanden interessiert):

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

Also, die Ziel: B finden . Hier sind drei Möglichkeiten, die ich nach einigem Herumprobieren gefunden habe:


1. visuell, mit gitk:

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

gitk screen capture from master

oder hier (vom Thema her gesehen):

gitk screen capture from topic

in beiden Fällen habe ich die Übergabe ausgewählt, die B in meinem Diagramm. 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

(Bearbeitung/Nebenbemerkung: Hinzufügen --decorate kann auch interessant sein; es fügt einen Hinweis auf Zweignamen, Tags usw. hinzu. Ich füge dies nicht zur obigen Befehlszeile hinzu, da die untenstehende Ausgabe die Verwendung nicht widerspiegelt).

die zeigt (unter der Annahme, dass git config --global color.ui auto ):

output of git log --graph --oneline --all

Oder, im Klartext:

\*   a9546a2 merge from topic back to master
|\\  
| \*   648ca35 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

in beiden Fällen ist die Übergabe 6aafd7f der niedrigste gemeinsame Punkt, d. h. B in meinem Diagramm, oder A in Ihrem.


3. Mit Muschelzauber:

Sie haben in Ihrer Frage nicht angegeben, ob Sie etwas wie das oben beschriebene wollen oder einen einzelnen Befehl, der Ihnen nur die eine Revision liefert und sonst nichts. Nun, hier ist das Letztere:

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

Das können Sie auch in Ihre ~/.gitconfig einfügen als (Hinweis: Der Bindestrich am Ende ist wichtig; danke Brian dafür, dass Sie darauf aufmerksam gemacht haben) :

[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 könnte über die folgende (mit Anführungszeichen versehene) Befehlszeile geschehen:

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'\'' -'

Anmerkung: zsh hätte genauso gut lauten können bash aber sh se no Arbeit - die <() Syntax existiert nicht in Vanille sh . (Nochmals vielen Dank, @conny, dass du mich in einem Kommentar zu einer anderen Antwort auf dieser Seite darauf aufmerksam gemacht hast!)

Anmerkung: Alternative Version des obigen Textes:

Dank an liori para hinweisend dass das obige Verfahren beim Vergleich identischer Zweige scheitern könnte, und eine alternative diff-Form zu entwickeln, die die sed-Form aus dem Mix entfernt und das Verfahren "sicherer" macht (d.h. es liefert ein Ergebnis (nämlich die jüngste Übergabe), auch wenn man Master mit Master vergleicht):

Als eine .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'\'' -'

Also, in meinem Testbaum (der für eine Weile nicht verfügbar war, sorry; er ist wieder da), funktioniert das jetzt sowohl im Master als auch im Topic (mit den Commits G bzw. B). Nochmals vielen Dank, liori, für die alternative Form.


Das ist es, was ich [und Iiori] mir ausgedacht haben. Es scheint für mich zu funktionieren. Es erlaubt auch ein paar zusätzliche Aliasnamen, 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 Stöbern!

161voto

Greg Hewgill Punkte 882617

Sie suchen vielleicht nach git merge-base :

git merge-base findet den oder die besten gemeinsamen Vorfahren zwischen zwei Commits, um sie in einem dreifachen Merge zu verwenden. Ein gemeinsamer Vorfahre ist besser als ein anderer gemeinsamer Vorfahre, wenn letzterer ein Vorfahre des ersteren ist. Ein gemeinsamer Vorfahre, der keinen besseren gemeinsamen Vorfahre hat, ist ein bester gemeinsamer Vorfahre d.h. a Basis zusammenführen . Beachten Sie, dass es mehr als eine Merge-Basis für ein Paar von Commits geben kann.

46voto

mipadi Punkte 377834

Ich habe die git rev-list für diese Art von Dingen. Zum Beispiel (beachten Sie die 3 Punkte)

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

wird den Verzweigungspunkt ausspucken. Nun, es ist nicht perfekt; da Sie Master in Zweig A ein paar Mal zusammengeführt haben, wird das ein paar möglich Verzweigungspunkte (im Wesentlichen 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 eingrenzen.

Ich habe diesen Befehl zu meinen Aliasen in ~/.gitconfig als:

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

so kann ich es als aufrufen:

$ git diverges branch-a master

37voto

Lionel Punkte 30442

Wenn Sie knappe Befehle mögen,

git rev-list $(git rev-list --first-parent ^branch\_name master | tail -n1)^^! 

Hier ist eine Erklärung.

Mit dem folgenden Befehl erhalten Sie eine Liste aller Übertragungen in master, die nach der Erstellung von branch_name erfolgt sind

git rev-list --first-parent ^branch\_name master 

Da Sie sich nur für die frühesten dieser Übertragungen interessieren, benötigen Sie die letzte Zeile der Ausgabe:

git rev-list ^branch\_name --first-parent master | tail -n1

Der Elternteil der frühesten Übergabe, der kein Vorfahre von "branch_name" ist, ist es per Definition, in "branch_name" und ist in "master", da er ein Vorfahre von etwas in "master" ist. Sie haben also den frühesten Commit, der sich in beiden Zweigen befindet.

Der Befehl

git rev-list commit^^!

ist nur eine Möglichkeit, die übergeordnete Commit-Referenz anzuzeigen. Sie könnten verwenden

git log -1 commit^

oder was auch immer.

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

\_C1\_\_\_C2\_\_\_\_\_\_\_ master
  \\    \\\_XXXXX\_ branch A (the Xs denote arbitrary cross-overs between master and A)
   \\\_\_\_\_\_/ branch B

macht es durchaus Sinn, C2 als "branching" commit auszugeben. Das ist der Zeitpunkt, an dem der Entwickler von "Master" verzweigt hat. Als er sich verzweigte, war der Zweig "B" noch nicht einmal in seinem Zweig zusammengeführt! Das ist es, was die Lösung in diesem Beitrag bietet.

Wenn Sie den letzten Commit C so wollen, dass alle Pfade vom Ursprung bis zum letzten Commit auf Zweig "A" durch C gehen, dann wollen Sie die Reihenfolge der Vorfahren ignorieren. Das ist rein topologisch und gibt Ihnen eine Vorstellung davon, seit wann Sie zwei Versionen des Codes zur gleichen Zeit haben. Das ist, wenn Sie mit Merge-Basis-basierte Ansätze gehen würde, und es wird C1 in meinem Beispiel zurückgeben.

28voto

Mark Booth Punkte 7224

Zweck: Diese Antwort testet die verschiedenen Antworten, die in diesem Thread gegeben wurden.

Test-Repository

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

$ 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

Richtige Lösungen

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

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

Als Charles Bailey weist jedoch darauf hin, dass diese Lösung sehr brüchig ist.

Si vous branch_A in master und dann zusammenführen master in branch_A ohne zwischengeschaltete Übertragungen, dann gibt Ihnen Lindes Lösung nur die jüngste erste Abweichung .

Das bedeutet, dass ich für meinen Arbeitsablauf wohl dabei bleiben muss, den Verzweigungspunkt langlaufender Zweige zu markieren, da ich nicht garantieren kann, dass sie später zuverlässig gefunden werden können.

Im Grunde läuft alles auf Folgendes hinaus git s Mangel an was hg ruft auf. benannte Zweige . Der Blogger jhw nennt diese Abstammungen vs. Familien in seinem Artikel Warum ich Mercurial mehr mag als Git und sein Folgeartikel Mehr zu Mercurial vs. Git (mit Diagrammen!) . Ich würde den Leuten empfehlen, sie zu lesen, um zu verstehen, warum einige sprunghafte Konvertiten es vermissen, keine benannte Zweige en git .

Falsche Lösungen

Die Lösung, die von mipadi liefert zwei Antworten, I y C :

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

Die Lösung, die von Greg Hewgill return I

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

Die Lösung, die von Karl gibt zurück. X :

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

Reproduktion des Test-Repository

So erstellen Sie ein Test-Repository:

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"

Mein einziger Zusatz ist der Tag, der den Zeitpunkt angibt, an dem wir den Zweig erstellt haben, und damit die Übergabe, die wir finden wollen.

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

$ git --version
git version 1.7.1

Dank an Charles Bailey dafür, dass Sie mir einen kompakteren Weg zum Skript des Beispiel-Repositorys gezeigt haben.

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