Finden, aus welchem ​​Zweig ein Git-Commit stammt

649

Gibt es eine Möglichkeit herauszufinden, von welchem ​​Zweig ein Commit aufgrund seines SHA-1- Hashwerts stammt?

Bonuspunkte, wenn Sie mir sagen können, wie dies mit Ruby Grit erreicht werden kann.

Ethan Gunderson
quelle
4
Die verschiedenen Methoden unten sind pragmatische, nützliche und funktionierende Methoden, um auf eine wahrscheinliche Antwort zu schließen. Beachten wir jedoch, dass die Frage selbst in git ein Missverständnis darstellt und Commits nicht aus Zweigen stammen. Zweige kommen und gehen, sie bewegen sich, nur Commits repräsentieren die wahre Repo-Geschichte. Andererseits ist dies keine Möglichkeit zu sagen, dass die folgenden Lösungen schlecht sind. Nur wissen, dass keiner von ihnen eine absolut vertrauenswürdige Antwort gibt, die von Design in Git nicht erreichbar ist. (Ein einfacher Fall ist das Löschen von Zweigen: Ich verzweige, ich schreibe zweimal fest, ich verschmelze in einen anderen Zweig, ich lösche den ersten Zweig. Woher kommt das
Festschreiben
1
Ich habe einen Fall, in dem ich explizit einen flachen Klon der Tiefe 1 aus einem Tag ziehe. Es ist billig und einfach und effizient ... und hat bis jetzt alles wunderbar gemacht, was ich wollte. Jetzt, da ich wissen möchte, auf welchem ​​Zweig sich das Tag befand, bin ich zumindest für dieses Detail abgespritzt. Sie können nicht immer nach Hause gehen, lol
Paul Hodges

Antworten:

864

Während Dav richtig ist, dass die Informationen nicht direkt gespeichert sind, heißt das nicht, dass Sie es nie herausfinden können. Hier sind einige Dinge, die Sie tun können.

Suchen Sie nach Zweigen, für die das Commit aktiviert ist

git branch -a --contains <commit>

Hier erfahren Sie alle Zweige, die in ihrer Geschichte das angegebene Commit haben. Dies ist natürlich weniger nützlich, wenn das Commit bereits zusammengeführt wurde.

Durchsuchen Sie die Reflogs

Wenn Sie in dem Repository arbeiten, in dem das Commit durchgeführt wurde, können Sie die Reflogs nach der Zeile für dieses Commit durchsuchen. Reflogs, die älter als 90 Tage sind, werden von git-gc beschnitten. Wenn das Commit also zu alt ist, werden Sie es nicht finden. Das heißt, Sie können dies tun:

git reflog show --all | grep a871742

Commit finden a871742. Beachten Sie, dass Sie die abgekürzten 7 ersten Ziffern des Commits verwenden MÜSSEN. Die Ausgabe sollte ungefähr so ​​aussehen:

a871742 refs/heads/completion@{0}: commit (amend): mpc-completion: total rewrite

Dies zeigt an, dass das Commit für den Zweig "Abschluss" vorgenommen wurde. In der Standardausgabe werden abgekürzte Commit-Hashes angezeigt. Suchen Sie daher nicht nach dem vollständigen Hash, da Sie sonst nichts finden.

git reflog showist eigentlich nur ein Alias ​​für git log -g --abbrev-commit --pretty=oneline. Wenn Sie also mit dem Ausgabeformat herumspielen möchten, um verschiedene Dinge zur Verfügung zu stellen, nach denen Sie suchen können, ist dies Ihr Ausgangspunkt!

Wenn Sie nicht in dem Repository arbeiten, in dem das Commit durchgeführt wurde, können Sie in diesem Fall am besten die Reflogs untersuchen und feststellen, wann das Commit zum ersten Mal in Ihr Repository eingeführt wurde. Mit etwas Glück haben Sie den Zweig geholt, für den er bestimmt war. Dies ist etwas komplexer, da Sie nicht gleichzeitig den Commit-Baum und die Reflogs durchlaufen können. Sie möchten die Reflog-Ausgabe analysieren und jeden Hash untersuchen, um festzustellen, ob er das gewünschte Commit enthält oder nicht.

Suchen Sie ein nachfolgendes Merge-Commit

Dies ist vom Workflow abhängig. Bei guten Workflows werden jedoch Commits für Entwicklungszweige vorgenommen, die dann zusammengeführt werden. Sie können dies tun:

git log --merges <commit>..

um Merge-Commits zu sehen, die das angegebene Commit als Vorfahren haben. (Wenn das Commit nur einmal zusammengeführt wurde, sollte das erste das Zusammenführen sein, nach dem Sie suchen. Andernfalls müssen Sie vermutlich einige untersuchen.) Die Commit-Nachricht zum Zusammenführen sollte den zusammengeführten Zweignamen enthalten.

Wenn Sie sich darauf verlassen möchten, können Sie die --no-ffOption verwenden, git mergeum die Erstellung von Zusammenführungs-Commits auch im Schnellvorlauf zu erzwingen. ( Seien Sie jedoch nicht zu eifrig. Das könnte bei Überbeanspruchung verschleiern.) VonCs Antwort auf eine verwandte Frage geht hilfreich auf dieses Thema ein.

Cascabel
quelle
5
+1. Das bloße Fehlen von Informationen für dieses spezielle Problem lässt Sie sich fragen, ob es sich tatsächlich um ein Problem handelt. Zweige können jederzeit geändert, umbenannt oder gelöscht werden. Möglicherweise reicht dies git describeaus, da (mit Anmerkungen versehene) Tags als wichtiger als Zweige angesehen werden können.
VonC
9
Ich stimme definitiv zu - Filialen sollen leicht und flexibel sein. Wenn Sie einen Workflow verwenden, in dem diese Informationen wichtig werden, können Sie mithilfe der --no-ffOption sicherstellen, dass immer ein Zusammenführungs-Commit vorhanden ist, sodass Sie den Pfad eines bestimmten Commits immer verfolgen können, wenn es zum Master zusammengeführt wird.
Cascabel
32
Zu Ihrer Information, wenn Sie Commits finden möchten, die nur auf der Fernbedienung vorhanden sind, fügen Sie das -aFlag zum ersten Befehl hinzu, z. B.git branch -a --contains <commit>
Jonathan Day,
8
@ JonathanDay: Nein, das findet Commits in jedem Zweig. Wenn Sie nur diejenigen auf der Fernbedienung möchten , verwenden Sie -r.
Cascabel
7
@SimonTewsi Wie ich bereits sagte, wenn es wirklich ein Problem ist, verwenden Sie merge --no-ff, um den Filialnamen beim Zusammenführen zuverlässig aufzuzeichnen. Stellen Sie sich ansonsten Filialnamen als temporäre Kurzbezeichnungen vor und schreiben Sie Beschreibungen als permanente fest. "Mit welchem ​​Kurznamen haben wir uns während der Entwicklung darauf bezogen?" sollte wirklich keine so wichtige Frage sein wie "Was macht dieses Commit?"
Cascabel
152

Dieser einfache Befehl funktioniert wie ein Zauber:

git name-rev <SHA>

Zum Beispiel (wobei test-branch der Name des Zweigs ist):

git name-rev 651ad3a
251ad3a remotes/origin/test-branch

Auch dies funktioniert für komplexe Szenarien wie:

origin/branchA/
              /branchB
                      /commit<SHA1>
                                   /commit<SHA2>

Hier git name-rev commit<SHA2>gibt branchB .

khichar.anil
quelle
3
Du hast meinen Tag gerettet. Dies ist die perfekte Lösung.
Taco
7
Und diese perfekte Lösung dauerte nur 8 Jahre. Vielen Dank!
S1J0
6
Ich fand git name-rev --name-only <SHA>es nützlicher, nur den Filialnamen zu erhalten. Meine Frage ... Kann es unter keinen Umständen mehr als einen Zweig zurückgeben?
Dean Kayton
1
Dies sollte die beste Antwort sein.
JCollier
1
Vielen Dank an @JCollier für Ihre freundlichen Worte.
khichar.anil
47

Update Dezember 2013:

sschuberth Kommentare

git-what-branch(Perl-Skript, siehe unten) scheint nicht mehr gepflegt zu sein. git-when-mergedist eine in Python geschriebene Alternative, die für mich sehr gut funktioniert.

Es basiert auf " Find Merge Commit, das ein bestimmtes Commit enthält ".

git when-merged [OPTIONS] COMMIT [BRANCH...]

Finden Sie heraus, wann ein Commit in einem oder mehreren Zweigen zusammengeführt wurde.
Suchen Sie das Zusammenführungs-Commit, das COMMITin die angegebenen BRANCHEN eingeführt wurde.

Suchen Sie insbesondere nach dem ältesten Commit im Verlauf des ersten übergeordneten Elements BRANCH, das das COMMITals Vorfahr enthält.


Ursprüngliche Antwort September 2010:

Sebastien Douche hat gerade gezwitschert (16 Minuten vor dieser SO-Antwort):

git-what-branch : Ermitteln Sie, in welchem ​​Zweig sich ein Commit befindet oder wie er zu einem benannten Zweig gelangt ist

Dies ist ein Perl-Skript von Seth Robertson , das sehr interessant erscheint:

ZUSAMMENFASSUNG

git-what-branch [--allref] [--all] [--topo-order | --date-order ]
[--quiet] [--reference-branch=branchname] [--reference=reference]
<commit-hash/tag>...

ÜBERBLICK

Teilen Sie uns (standardmäßig) den frühesten kausalen Pfad von Commits und Zusammenführungen mit, damit das angeforderte Commit in einen benannten Zweig gelangt. Wenn ein Commit direkt für einen benannten Zweig durchgeführt wurde, ist dies offensichtlich der früheste Pfad.

Mit dem frühesten kausalen Pfad meinen wir den Pfad, der am frühesten zu einem benannten Zweig verschmolzen ist, mit Festschreibungszeit (sofern --topo-ordernicht anders angegeben).

PERFORMANCE

Wenn viele Zweige (z. B. Hunderte) das Commit enthalten, kann es lange dauern, bis das System den Pfad gefunden hat (für ein bestimmtes Commit im Linux-Baum dauerte es 8 Sekunden, um einen Zweig zu untersuchen, aber es gab über 200 Kandidatenzweige) zu jedem Commit.
Die Auswahl einer --reference-branch --reference tagzu untersuchenden Person ist hunderte Male schneller (wenn Sie Hunderte von Kandidatenzweigen haben).

BEISPIELE

 # git-what-branch --all 1f9c381fa3e0b9b9042e310c69df87eaf9b46ea4
 1f9c381fa3e0b9b9042e310c69df87eaf9b46ea4 first merged onto master using the following minimal temporal path:
   v2.6.12-rc3-450-g1f9c381 merged up at v2.6.12-rc3-590-gbfd4bda (Thu May  5 08:59:37 2005)
   v2.6.12-rc3-590-gbfd4bda merged up at v2.6.12-rc3-461-g84e48b6 (Tue May  3 18:27:24 2005)
   v2.6.12-rc3-461-g84e48b6 is on master
   v2.6.12-rc3-461-g84e48b6 is on v2.6.12-n
   [...]

Dieses Programm berücksichtigt nicht die Auswirkungen der Auswahl des interessierenden Commits, sondern nur Zusammenführungsvorgänge.

VonC
quelle
4
git-what-branchscheint nicht mehr gepflegt zu sein. Git-When-Merged ist eine in Python geschriebene Alternative, die für mich sehr gut funktioniert.
Sschuberth
@sschuberth danke für dieses update. Ich habe Ihren Kommentar zur besseren Sichtbarkeit in die Antwort aufgenommen.
VonC
37

Um dieses c0118faCommit zu finden, kam es beispielsweise von redesign_interactions:

* ccfd449 (HEAD -> develop) Require to return undef if no digits found
*   93dd5ff Merge pull request #4 from KES777/clean_api
|\
| * 39d82d1 Fix tc0118faests for debugging debugger internals
| * ed67179 Move &push_frame out of core
| * 2fd84b5 Do not lose info about call point
| * 3ab09a2 Improve debugger output: Show info about emitted events
| *   a435005 Merge branch 'redesign_interactions' into clean_api
| |\
| | * a06cc29 Code comments
| | * d5d6266 Remove copy/paste code
| | * c0118fa Allow command to choose how continue interaction
| | * 19cb534 Emit &interact event

Du solltest rennen:

git log c0118fa..HEAD --ancestry-path --merges

Scrollen Sie nach unten, um das Commit für die letzte Zusammenführung zu finden . Welches ist:

commit a435005445a6752dfe788b8d994e155b3cd9778f
Merge: 0953cac a06cc29
Author: Eugen Konkov
Date:   Sat Oct 1 00:54:18 2016 +0300

    Merge branch 'redesign_interactions' into clean_api

Aktualisieren

Oder nur ein Befehl:

git log c0118fa..HEAD --ancestry-path --merges --oneline --color | tail -n 1
Eugen Konkov
quelle
4
Dies war die einzige Lösung, die mir das gewünschte Ergebnis brachte. Ich habe den Rest versucht.
Crafter
Beachten Sie, dass dies nur funktioniert, wenn die Zusammenführungs-Commit-Zusammenfassungen die in den Zusammenführungen verwendeten Zweignamen enthalten, was normalerweise standardmäßig der Fall ist. Allerdings git merge -m"Any String Here"wird die Quell- und Zielbranchen Informationen verschleiern.
Qneill
@qneill: Nein, die Zusammenführung enthält immer Informationen zu zusammengeführten Zweigen : Merge: f6b70fa d58bdcb. Sie können Ihr Merge-Commit so benennen, wie Sie es möchten. Ich werde keine Materie haben
Eugen Konkov
1
@EugenKonkov Sobald die Zweige die Zusammenführung durchlaufen haben, wird der Verlauf des Pfads im Diagramm, von dem sie stammen, im Reflog belassen, jedoch nicht in der Git-Objektdatenbank. Ich habe eine Antwort gepostet, um zu erklären, was ich sage
qneill
UPD-Version funktioniert für mich, einfacher Befehl, der das ursprüngliche Commit findet
vpalmu
9

git branch --contains <ref>ist der offensichtlichste "Porzellan" -Befehl, um dies zu tun. Wenn Sie etwas Ähnliches nur mit "Sanitär" -Befehlen tun möchten:

COMMIT=$(git rev-parse <ref>) # expands hash if needed
for BRANCH in $(git for-each-ref --format "%(refname)" refs/heads); do
  if $(git rev-list $BRANCH | fgrep -q $COMMIT); then
    echo $BRANCH
  fi
done

(Crosspost von dieser SO Antwort )

Jeff Bowman
quelle
6

khichar.anil behandelte das meiste davon in seiner Antwort.

Ich füge nur das Flag hinzu, mit dem die Tags aus der Liste der Revisionsnamen entfernt werden. Dies gibt uns:

git name-rev --name-only --exclude=tags/* $SHA
Phyatt
quelle
Im zweiten Satz scheint etwas zu fehlen.
Peter Mortensen
Ich habe die Beschreibung aktualisiert. Danke für das Feedback.
Phyatt
4

Die Option eines armen Mannes besteht darin, das Tool tig1 zu verwenden HEAD, nach dem Commit zu suchen und dann der Linie von diesem Commit visuell zu folgen, bis ein Zusammenführungs-Commit angezeigt wird. Die Standard-Zusammenführungsnachricht sollte angeben, welcher Zweig wo zusammengeführt wird :)

1 Tig ist eine ncurses-basierte Textmodus-Oberfläche für Git. Es fungiert hauptsächlich als Git-Repository-Browser, kann aber auch beim Staging von Änderungen für das Festschreiben auf Chunk-Ebene helfen und als Pager für die Ausgabe verschiedener Git-Befehle fungieren.

user1338062
quelle
3

Als Experiment habe ich einen Post-Commit-Hook erstellt, der Informationen über den aktuell ausgecheckten Zweig in den Commit-Metadaten speichert. Ich habe auch gitk leicht modifiziert, um diese Informationen anzuzeigen.

Sie können es hier überprüfen: https://github.com/pajp/branch-info-commits

pajp
quelle
1
Aber wenn Sie Metadaten hinzufügen möchten, warum nicht Git-Notizen verwenden, anstatt mit den Überschriften von Commits herumzuspielen? Siehe stackoverflow.com/questions/5212957/… , stackoverflow.com/questions/7298749/… oder stackoverflow.com/questions/7101158/…
VonC
Um ehrlich zu sein, wusste ich nicht, dass es Git-Notizen gibt, als ich das schrieb. Ja, es ist wahrscheinlich eine bessere Idee, Git Notes zu verwenden, um dasselbe zu erreichen.
pajp
3

Ich beschäftige mich mit demselben Problem ( Jenkins Multibranch-Pipeline) - ich habe nur Commit-Informationen und versuche, einen Zweigstellennamen zu finden, von dem dieses Commit ursprünglich stammt. Es muss für Remote-Zweige funktionieren, lokale Kopien sind nicht verfügbar.

Damit arbeite ich:

git rev-parse HEAD | xargs git name-rev

Optional können Sie die Ausgabe entfernen:

git rev-parse HEAD | xargs git name-rev | cut -d' ' -f2 | sed 's/remotes\/origin\///g'
miro
quelle
0

Ich denke, jemand sollte mit dem gleichen Problem konfrontiert sein, das den Zweig nicht herausfinden kann, obwohl er tatsächlich in einem Zweig existiert.

Du solltest besser zuerst alles ziehen:

git pull --all

Führen Sie dann die Zweigsuche durch:

git name-rev <SHA>

oder:

git branch --contains <SHA>
Yates Zhou
quelle
0

Wenn das OP versucht, den Verlauf zu ermitteln, der von einem Zweig beim Erstellen eines bestimmten Commits durchlaufen wurde ("Finden Sie heraus, von welchem ​​Zweig ein Commit aufgrund seines SHA-1-Hashwerts stammt"), gibt es ohne das Reflog keine Datensätze in der Git-Objektdatenbank , die zeigen, welcher benannte Zweig an welchen Commit-Verlauf gebunden war.

(Ich habe dies als Antwort auf einen Kommentar gepostet.)

Hoffentlich veranschaulicht dieses Skript meinen Standpunkt:

rm -rf /tmp/r1 /tmp/r2; mkdir /tmp/r1; cd /tmp/r1
git init; git config user.name n; git config user.email [email protected]
git commit -m"empty" --allow-empty; git branch -m b1; git branch b2
git checkout b1; touch f1; git add f1; git commit -m"Add f1"
git checkout b2; touch f2; git add f2; git commit -m"Add f2"
git merge -m"merge branches" b1; git checkout b1; git merge b2
git clone /tmp/r1 /tmp/r2; cd /tmp/r2; git fetch origin b2:b2
set -x;
cd /tmp/r1; git log --oneline --graph --decorate; git reflog b1; git reflog b2;
cd /tmp/r2; git log --oneline --graph --decorate; git reflog b1; git reflog b2;

Die Ausgabe zeigt, dass es keine Möglichkeit gibt zu wissen, ob das Commit mit 'Add f1' vom Zweig b1 oder b2 vom Remote-Klon / tmp / r2 stammt.

(Letzte Zeilen der Ausgabe hier)

+ cd /tmp/r1
+ git log --oneline --graph --decorate
*   f0c707d (HEAD, b2, b1) merge branches
|\
| * 086c9ce Add f1
* | 80c10e5 Add f2
|/
* 18feb84 empty
+ git reflog b1
f0c707d b1@{0}: merge b2: Fast-forward
086c9ce b1@{1}: commit: Add f1
18feb84 b1@{2}: Branch: renamed refs/heads/master to refs/heads/b1
18feb84 b1@{3}: commit (initial): empty
+ git reflog b2
f0c707d b2@{0}: merge b1: Merge made by the 'recursive' strategy.
80c10e5 b2@{1}: commit: Add f2
18feb84 b2@{2}: branch: Created from b1
+ cd /tmp/r2
+ git log --oneline --graph --decorate
*   f0c707d (HEAD, origin/b2, origin/b1, origin/HEAD, b2, b1) merge branches
|\
| * 086c9ce Add f1
* | 80c10e5 Add f2
|/
* 18feb84 empty
+ git reflog b1
f0c707d b1@{0}: clone: from /tmp/r1
+ git reflog b2
f0c707d b2@{0}: fetch origin b2:b2: storing head
qneill
quelle
Und was ist die Ausgabe für git log 80c10e5..HEAD --ancestry-path --merges --oneline --color | tail -n 1und git log 086c9ce..HEAD --ancestry-path --merges --oneline --color | tail -n 1Befehle für beide Fälle?
Eugen
Die SHAs ändern sich natürlich jedes Mal, wenn die Befehle ausgeführt werden. Um Ihre Befehle zu wiederholen, habe ich HEAD ^ 1 und HEAD ^ 2 bei einem neuen Lauf verwendet. Die Befehle und Ausgaben sind: $ git log HEAD^1..HEAD --ancestry-path --merges --oneline --color | tail -n 1welche Ausbeuten 376142d merge branchesund $ git log HEAD^2..HEAD --ancestry-path --merges --oneline --color | tail -n 1welche Ausbeuten 376142d merge branches - welche die Zusammenfassung des Zusammenführungs-Commits zeigen, die (wie ich behauptet habe) beim Erstellen der Zusammenführung überschrieben werden kann, wodurch möglicherweise der Verzweigungsverlauf der Zusammenführung verschleiert wird.
Qneill
0

TL; DR:

Verwenden Sie Folgendes, wenn Sie sich für den Status des Shell-Exits interessieren:

  • branch-current - Name der aktuellen Filiale
  • branch-names - saubere Filialnamen (einer pro Zeile)
  • branch-name - Stellen Sie sicher, dass nur ein Zweig zurückgegeben wird branch-names

Beide branch-nameund branch-namesakzeptieren ein Commit als Argument und standardmäßig, HEADwenn keines angegeben ist.


Aliase nützlich in der Skripterstellung

branch-current = "symbolic-ref --short HEAD"  # https://stackoverflow.com/a/19585361/5353461
branch-names = !"[ -z \"$1\" ] && git branch-current 2>/dev/null || git branch --format='%(refname:short)' --contains \"${1:-HEAD}\" #"  # https://stackoverflow.com/a/19585361/5353461
branch-name = !"br=$(git branch-names \"$1\") && case \"$br\" in *$'\\n'*) printf \"Multiple branches:\\n%s\" \"$br\">&2; exit 1;; esac; echo \"$br\" #"

Commit nur von nur einem Zweig aus erreichbar

% git branch-name eae13ea
master
% echo $?
0
  • Die Ausgabe erfolgt an STDOUT
  • Ausgangswert ist 0.

Commit von mehreren Zweigen aus erreichbar

% git branch-name 4bc6188
Multiple branches:
attempt-extract
master%
% echo $?
1
  • Die Ausgabe erfolgt nach STDERR
  • Der Exit-Wert ist 1.

Aufgrund des Exit-Status können diese sicher aufgebaut werden. So verwenden Sie beispielsweise die zum Abrufen verwendete Fernbedienung:

remote-fetch = !"branch=$(git branch-name \"$1\") && git config branch.\"$branch\".remote || echo origin #"
Tom Hale
quelle
-1

So finden Sie die lokale Niederlassung:

grep -lR YOUR_COMMIT .git/refs/heads | sed 's/.git\/refs\/heads\///g'

So finden Sie den Remote-Zweig:

grep -lR $commit .git/refs/remotes | sed 's/.git\/refs\/remotes\///g'
Gary Lyn
quelle
1
Einige Erklärungen Ihrer Beispiele werden großartig sein
Eugen Konkov
-4

Abgesehen davon, dass Sie den gesamten Baum durchsuchen, bis Sie einen passenden Hash gefunden haben, nein.

Bernstein
quelle
7
Ich möchte das nicht wirklich ablehnen, denn genau genommen ist es wahr, dass Git diese Informationen nicht dauerhaft speichert, aber es ist sehr oft möglich, es trotzdem herauszufinden. Siehe meine Antwort.
Cascabel