Wie finde ich das nächste Commit in Git? (Kind / Kinder von ref)

236

ref^bezieht sich auf das Commit vor ref, was ist mit dem Commit nach ref ?

Wenn ich zum Beispiel, git checkout 12345wie überprüfe ich das nächste Commit?

Vielen Dank.

PS Ja, git ist ein DAG-Knotenzeiger-Strukturbaum. Wie finde ich das Commit nach diesem?

Schwern
quelle
2
Siehe auch " git children-of"!
VonC

Antworten:

185

Um alle Commits aufzulisten, beginnend mit dem aktuellen und dann dem untergeordneten Commit usw. - im Grunde genommen ein Standard-Git-Log, aber mit der Zeit in die andere Richtung, verwenden Sie so etwas wie

git log --reverse --ancestry-path 894e8b4e93d8f3^..master

Dabei ist 894e8b4e93d8f3 das erste Commit, das Sie anzeigen möchten.

Tim Hunt
quelle
3
Für den speziellen Fall in der ursprünglichen Frage, nur Ersatz HEAD^für 894e8b4e93d8f3^.
Søren Løvborg
2
Vielleicht ist add --oneline ein besseres kurzes Ausgabeergebnis.
Firo
1
Ich muss verwenden ..., ^..scheitert lautlos
TankorSmash
1
Erste fatal: unrecognized argument: --ancestry-pathin git Version 1.7.1
user151841
6
Dies funktioniert nur, wenn mastersich der Stammpfad des aktuellen Commits befindet. Im zweiten Code-Snippet meiner Antwort finden Sie eine Lösung, die in allen Fällen funktioniert.
Tom Hale
37

Der Schöpfer von Hudson (jetzt Jenkins) Kohsuke Kawaguchi, der gerade veröffentlicht wurde (November 2013):
kohsuke / git-children-of :

Finden Sie bei einem Commit unmittelbare Kinder dieses Commits.

#!/bin/bash -e
# given a commit, find immediate children of that commit.
for arg in "$@"; do
  for commit in $(git rev-parse $arg^0); do
    for child in $(git log --format='%H %P' --all | grep -F " $commit" | cut -f1 -d' '); do
      git describe $child
    done
  done
done

Wie dieser Thread zeigt , gibt es in einem VCS, das auf dem Verlauf basiert, der durch eine DAG (Directed Acyclic Graph) dargestellt wird , nicht "ein Elternteil" oder "ein Kind".

        C1 -> C2 -> C3
      /               \
A -> B                  E -> F
      \               /
        D1 -> D2 ----/

Die Bestellung der Commits erfolgt nach "Topo-Order" oder "Date-Order" (siehe GitPro-Buch ).

Seit Git1.6.0 können Sie jedoch die untergeordneten Elemente eines Commits auflisten .

git rev-list --children
git log --children

Hinweis: Bei übergeordneten Commits tritt das gleiche Problem auf. Das Suffix ^eines Revisionsparameters bedeutet das erste übergeordnete Commit dieses Commit-Objekts. ^<n>Bedeutet die<n> th Elternteil (dh rev^ ist äquivalent zu rev^1).

Wenn Sie sich in einem Zweig befinden foound " git merge bar" ausgeben, fooist dies der erste Elternteil.
Dh: Das erste übergeordnete Element ist der Zweig, in dem Sie sich beim Zusammenführen befanden, und das zweite ist das Festschreiben für den Zweig, in dem Sie zusammengeführt wurden.

VonC
quelle
6
git rev-list --childrensieht sicher so aus wie ich will, aber es DWIM nicht. Es scheint alle Eltern und ihre Kinder aufzulisten. Ich nehme an, ich kann sie alle auflisten und sie analysieren ... bleh, aber es ist etwas.
Schwern
@Schwern: true, git rev-list --childrenist nicht nur zum Auflisten von Kindern gedacht , sondern zum Auflisten von Eltern mit ihren Kindern ... Sie müssen immer analysieren.
VonC
Ich habe diesen Code bei einem Commit mit zwei Kindern ausprobiert: $ git children0of 9dd5932 fatal: No annotated tags can describe '71d7b5dd89d241072a0a078ff2c7dfec05d52e1f'. However, there were unannotated tags: try --tags. Welche Ausgabe erhalten Sie?
Tom Hale
@TomHale Ich kann es momentan nicht testen, stelle aber eine neue Frage (mit Betriebssystem und Git-Version), so dass jeder testen kann.
VonC
1
Das einzige Problem dabei git-children-ofist, dass es git description verwendet , das versucht, den SHA als lesbar zu formatieren, was mit dem Fehler von @ TomHale fehlschlagen kann und solche Ergebnisse liefert v1.0.4-14-g2414721, die verwirrend sind, wenn Sie einen SHA erwartet haben. Das Ersetzen durch ein einfaches echomacht dies zu einem hervorragenden Werkzeug, danke!
Nickolay
18

was ich gefunden habe ist

git rev-list --ancestry-path commit1..commit2

wo ich commit1als aktuelles Commit und commit2auf den aktuellen Kopf setze . Dies gibt mir eine Liste aller Commits zurück, die einen Pfad zwischen commit1und bildencommit2 .

Die letzte Zeile der Ausgabe ist das untergeordnete Element von commit1 (auf dem Pfad zu commit2).

white_gecko
quelle
3
Also einfach hinzufügen | tail -1, um das Kind zu bekommen.
Jesse Glick
8

Ich weiß, was du meinst. Es ist frustrierend, reichlich Syntax zu haben, um zu früheren Commits zu gelangen, aber keine, um zu den nächsten zu gelangen. In einer komplexen Geschichte wird das Problem "Was ist das nächste Commit?" Ziemlich schwierig, aber beim komplexen Zusammenführen tritt dieselbe Härte auch bei "vorherigen" Commits auf. Im einfachen Fall wäre es in einem einzelnen Zweig mit einer linearen Historie (auch nur lokal für eine begrenzte Anzahl von Commits) schön und sinnvoll, vorwärts und rückwärts zu gehen.

Das eigentliche Problem dabei ist jedoch, dass die untergeordneten Commits nicht referenziert werden, sondern nur eine rückwärts verknüpfte Liste. Um das untergeordnete Commit zu finden, ist eine Suche erforderlich, die nicht allzu schlecht ist, aber wahrscheinlich nicht etwas, das Git in die Refspec-Logik aufnehmen möchte.

Auf jeden Fall bin ich auf diese Frage gestoßen, weil ich einfach einen Schritt nach dem anderen in der Geschichte vorwärts gehen möchte, Tests mache, und manchmal muss man vorwärts und nicht rückwärts gehen. Nun, mit ein paar weiteren Gedanken kam ich auf diese Lösung:

Wählen Sie ein Commit aus, bevor Sie sich gerade befinden. Dies könnte wahrscheinlich ein Zweigkopf sein. Wenn Sie sich in Zweig ~ 10 befinden, dann "Git Checkout-Zweig ~ 9", dann "Git Checkout-Zweig ~ 8", um den nächsten zu erhalten, dann "Git Checkout-Zweig ~ 7" und so weiter.

Das Dekrementieren der Nummer sollte in einem Skript wirklich einfach sein, wenn Sie es brauchen. Viel einfacher als das Parsen der Git-Rev-Liste.

vontrapp
quelle
Es ist zwar nützlich, findet aber nicht das nächste Commit. Es geht in Richtung des aktuellen Commits.
Schwern
1
Na dann könnten Sie wohl tun: " BRANCH=master; git co $BRANCH~$[ $(git rev-list HEAD..$BRANCH | wc -l) - 1 ]" Sie müssen auf einen Ast zugehen, daran führt kein Weg vorbei.
vontrapp
8

Zwei praktische Antworten:

Ein Kind

Basierend auf @ Michaels Antwort habe ich den childAlias ​​in meinem gehackt .gitconfig.

Es funktioniert im Standardfall wie erwartet und ist außerdem vielseitig einsetzbar.

# Get the child commit of the current commit.
# Use $1 instead of 'HEAD' if given. Use $2 instead of curent branch if given.
child = "!bash -c 'git log --format=%H --reverse --ancestry-path ${1:-HEAD}..${2:\"$(git rev-parse --abbrev-ref HEAD)\"} | head -1' -"

Standardmäßig wird das untergeordnete Element von HEAD angegeben (sofern kein anderes Commit-ish-Argument angegeben wird), indem der Abstammung ein Schritt in Richtung der Spitze des aktuellen Zweigs gefolgt wird (es sei denn, ein anderes Commit-ish wird als zweites Argument angegeben).

Verwenden Sie %hanstelle von%H wenn Sie das kurze Hash-Formular möchten.

Mehrere Kinder

Mit einem abgetrennten KOPF (es gibt keinen Zweig) oder um alle Kinder unabhängig von Zweigen zu bekommen:

# For the current (or specified) commit-ish, get the all children, print the first child 
children = "!bash -c 'c=${1:-HEAD}; set -- $(git rev-list --all --not \"$c\"^@ --children | grep $(git rev-parse \"$c\") ); shift; echo $1' -"

Ändern Sie das $1in$* , um alle zu drucken.

Sie können auch --allzu einem Commit wechseln , um nur die untergeordneten Elemente anzuzeigen, die Vorfahren dieses Commits sind. Mit anderen Worten, um nur die untergeordneten Elemente "in Richtung" des angegebenen Commits anzuzeigen. Dies kann Ihnen helfen, die Ausgabe von vielen Kindern auf nur eines zu beschränken.

Tom Hale
quelle
7

Es gibt kein eindeutiges "nächstes Commit". Da der Verlauf in Git eine DAG und keine Zeile ist, können viele Commits ein gemeinsames übergeordnetes Element (Zweige) und Commits mehr als ein übergeordnetes Element (Zusammenführungen) haben.

Wenn Sie an einen bestimmten Zweig denken, können Sie in dessen Protokoll nachsehen, in welchem ​​Commit der aktuelle Zweig als übergeordneter aufgeführt ist.

Phil Miller
quelle
37
Nach dieser Logik gibt es auch kein "vorheriges Festschreiben", aber es gibt genügend Syntax, um die Eltern zu erhalten.
Schwern
7
@Schwern: Es gibt auch kein "vorheriges Commit"; <rev>^ist "übergeordnetes Commit" ("erstes übergeordnetes Element" für Zusammenführungs-Commits).
Jakub Narębski
6

Ich habe viele verschiedene Lösungen ausprobiert und keine hat für mich funktioniert. Musste mir meine einfallen lassen.

Finde das nächste Commit

function n() {
    git log --reverse --pretty=%H master | grep -A 1 $(git rev-parse HEAD) | tail -n1 | xargs git checkout
}

Vorheriges Commit finden

function p() {
    git checkout HEAD^1
}
MK
quelle
6

Wenn Sie kein bestimmtes "Ziel" -Commit im Auge haben, sondern untergeordnete Commits anzeigen möchten, die sich möglicherweise in einem Zweig befinden, können Sie diesen Befehl verwenden:

git rev-list --children --all | grep ^${COMMIT}

Wenn Sie alle Kinder und Enkelkinder sehen möchten , müssen Sie wie folgt rev-list --childrenrekursiv verwenden:

git rev-list --children --all | \
egrep ^\($(git rev-list --children --all | \
           grep ^${COMMIT} | \
           sed 's/ /|/g')\)

(Die Version, die nur Enkelkinder gibt, würde eine komplexere sedund / oder verwenden cut.)

Schließlich können Sie dies in einen log --graphBefehl einspeisen, um die Baumstruktur wie folgt anzuzeigen:

git log --graph --oneline --decorate \
\^${COMMIT}^@ \
$(git rev-list --children --all | \
  egrep ^\($(git rev-list --children --all | \
             grep ^${COMMIT} | \
             sed 's/ /|/g')\))

Hinweis : Bei den obigen Befehlen wird davon ausgegangen, dass Sie die Shell-Variable ${COMMIT}auf eine Referenz (branch, tag, sha1) des Commits festgelegt haben, dessen untergeordnete Elemente Sie interessieren.

Matt McHenry
quelle
2
Dies beantwortet die Frage, die ich nicht für Google und Stackoverflow formulieren konnte, aber zu stellen versuchte. Vielen Dank, dass Sie die Notwendigkeit proaktiv erkannt haben
Tommy Knowlton,
für mich ${COMMIT}besteht es nicht, aber Sie könnten $(git rev-parse HEAD) stattdessen verwenden
Radon8472
Entschuldigung, fügte eine Notiz über ${COMMIT}.
Matt McHenry
5

(Dies begann als Antwort auf eine doppelte Frage. Ich habe ein wenig Licht bearbeitet, um sie zu bereinigen.)

Alle internen Pfeile von Git sind einseitig und zeigen nach hinten. Es gibt daher keine kurze bequeme Syntax für das Vorwärtsbewegen: Es ist einfach nicht möglich.

Es ist möglich, sich "gegen die Pfeile zu bewegen", aber die Vorgehensweise ist überraschend, wenn Sie es noch nicht gesehen haben und danach offensichtlich. Nehmen wir an, wir haben:

A <-B <-C <-D <-E   <-- last
        ^
        |
         \--------- middle

Mit middle~2folgt die Pfeile zweimal von Czurück nach A. Wie bewegen wir uns von Cnach D? Die Antwort ist: Wir beginnen bei E, den Namen mit last, und die Arbeit nach hinten , bis wir bekommen middle, das Aufzeichnen der Punkte , die wir auf dem Weg besuchen . Dann bewegen wir uns einfach so weit wie wir wollen in die Richtung last: einen Schritt nach Doder zwei nach E.

Dies ist besonders wichtig, wenn wir Niederlassungen haben:

          D--E   <-- feature1
         /
...--B--C   <-- master
         \
          F--G   <-- feature2

Welches Commit ist einen Schritt später C? Es gibt keine richtige Antwort, bis Sie der Frage hinzufügen: in Richtung feature___ (füllen Sie die Lücke aus ).

Um die Commits zwischen C(ohne C) sich selbst und beispielsweise aufzuzählen G, verwenden wir:

git rev-list --topo-order --ancestry-path master..feature2

Dies --topo-orderstellt sicher, dass die Commits auch bei komplexen Verzweigungen und Zusammenführungen in topologisch sortierter Reihenfolge ausgeführt werden. Dies ist nur erforderlich, wenn die Kette nicht linear ist. Die --ancestry-pathEinschränkung bedeutet, dass wir, wenn wir rückwärts von arbeiten feature2, nur Commits auflisten, die Commit Cals einen ihrer eigenen Vorfahren haben. Das heißt, wenn das Diagramm - oder der relevante Teil davon - tatsächlich so aussieht:

A--B--C   <-- master
 \     \
  \     F--G--J   <-- feature2
   \         /
    H-------I   <-- feature3

eine einfache Anfrage des Formulars feature2..masteraufzählt Commits J, Gund I, und Fund Hin einer bestimmten Reihenfolge. Mit --ancestry-pathschlagen wir aus Hund I: Sie sind keine Nachkommen von C, nur von A. Mit stellen --topo-orderwir sicher, dass die tatsächliche Aufzählungsreihenfolge Jdann G, dann ist F.

Der git rev-listBefehl verschüttet diese Hash-IDs in seiner Standardausgabe, eine pro Zeile. Um einen Schritt vorwärts in Richtung zu gehen feature2, wollen wir nur die letzte Zeile.

Das Hinzufügen ist möglich (und verlockend und kann nützlich sein), --reversesodass git rev-listdie Commits nach dem Generieren in umgekehrter Reihenfolge gedruckt werden. Dies funktioniert, aber wenn Sie es in einer Pipeline wie dieser verwenden:

git rev-list --topo-order --ancestry-path --reverse <id1>...<id2> | head -1

Um nur das "nächste Commit in Richtung von id2" zu erhalten, und es gibt eine sehr lange Liste von Commits, kann der git rev-listBefehl eine fehlerhafte Pipe bekommen, wenn er versucht zu schreiben, headdie seine Eingabe nicht mehr gelesen und beendet hat. Da Rohrbruchfehler normalerweise von der Shell ignoriert werden, funktioniert dies meistens. Stellen Sie einfach sicher, dass sie bei Ihrer Verwendung ignoriert werden .

Es ist auch verlockend, -n 1den git rev-listBefehl zusammen mit hinzuzufügen --reverse. Tu es nicht! Dies führt dazu, git rev-listdass Sie nach einem Schritt zurück anhalten und dann die Liste der besuchten Commits (mit einem Eintrag) umkehren. Das produziert also <id2>jedes Mal.

Wichtige Randnotiz

Beachten Sie, dass bei "Diamant" - oder "Benzolring" -Graphenfragmente:

       I--J
      /    \
...--H      M--...  <-- last
      \    /
       K--L

Wenn Sie ein Commit "vorwärts" von in HRichtung bewegen, lasterhalten Sie entweder I oder K . Daran können Sie nichts ändern: Beide Commits sind einen Schritt vorwärts! Wenn Sie dann mit dem resultierenden Commit beginnen und einen weiteren Schritt ausführen, werden Sie jetzt auf den Pfad festgelegt, auf dem Sie begonnen haben.

Die Heilung dafür besteht darin, zu vermeiden, dass Sie sich Schritt für Schritt bewegen und in pfadabhängige Ketten geraten. Wenn Sie stattdessen vorhaben, eine gesamte Ahnenpfadkette zu besuchen, bevor Sie etwas anderes tun, erstellen Sie eine vollständige Liste aller Commits in der Kette:

git rev-list --topo-order --reverse --ancestry-path A..B > /tmp/list-of-commits

Besuchen Sie dann jedes Commit in dieser Liste nacheinander, und Sie erhalten die gesamte Kette. Das --topo-orderwird sicherstellen , dass Sie treffen I-und- Jin dieser Reihenfolge, und K-und- Lin dieser Reihenfolge (obwohl es keine einfache Möglichkeit , vorherzusagen , ob Sie vor oder nach dem KL Paar das IJ Paar werde tun).

torek
quelle
" Es gibt keine richtige Antwort, bis Sie der Frage hinzufügen: in Richtung feature___ " Dies ist ein sehr guter Punkt. Danke für deine Antwort.
Schwern
4

Ich habe diesen Alias ​​in ~/.gitconfig

first-child = "!f() { git log  --reverse --ancestry-path --pretty=%H $1..${2:-HEAD} | head -1; }; f"
schwach
quelle
was ist f()? Und es muss sein head -1, das erste Kind zu sein, sonst meldet dies einfach den KOPF.
Xerus
@Xerus Da es eine "komplexe" Shell-Syntax verwendet und Git es nicht erkennt, wenn es nicht eingewickelt ist f(). Ja, head -1ist eine mutige Vermutung.
schwächer
Nett! Meine wurde angepasst an: nextref = "!f() { git log --reverse --ancestry-path --pretty=%H $1..HEAD | head -${2:-1} | tail -1; }; f"So können Sie optional auswählen, wie weit Sie voraus sind
Z. Khullah,
2

Ich habe es geschafft, das nächste Kind folgendermaßen zu finden:

git log --reverse --children -n1 HEAD (where 'n' is the number of children to show)
DevRQ
quelle
1
Für mich zeigt dies nicht das erste Kind, sondern das aktuelle Commit
Radon8472
2

Wenn sich die untergeordneten Commits alle in einem Zweig befinden, können Sie verwenden gitk --all commit^.., wobei "Commit" das Commit identifiziert. Wenn der abgekürzte SHA-1 des Commits beispielsweise c6661c5 lautet, geben Sie Folgendes eingitk --all c6661c5^..

Sie müssen wahrscheinlich den vollständigen SHA-1 in die Zelle "SHA1 ID:" von gitk eingeben. Sie benötigen den vollständigen SHA-1, der für dieses Beispiel über erhältlich istgit rev-parse c6661c5

Alternativ git rev-list --all --children | grep '^c6661c5883bb53d400ce160a5897610ecedbdc9d'wird eine Zeile erstellt, die alle untergeordneten Elemente dieses Commits enthält, vermutlich unabhängig davon, ob ein Zweig beteiligt ist oder nicht.

user339589
quelle
0

Jedes Commit speichert einen Zeiger auf sein übergeordnetes Element (Eltern, im Falle eines Zusammenführungs-Commits (Standard)).

Es gibt also keine Möglichkeit, auf ein untergeordnetes Commit (falls vorhanden) des Elternteils hinzuweisen.

Lakshman Prasad
quelle
Commit kann keine Zeiger auf seine untergeordneten Commits speichern, da zu einem späteren Zeitpunkt zusätzliche untergeordnete Commits (Verzweigungspunkte) hinzugefügt werden können.
Jakub Narębski
@ Jakub Ich folge nicht wirklich. Sie können später nicht hinzugefügt werden?
Schwern
1
Jakub: Genau das habe ich gesagt. "Jedes Commit speichert Zeiger nur auf sein übergeordnetes Element."
Lakshman Prasad
@Schwern: Commits in Git sind unveränderlich (was eine nette Konsequenz der Verantwortlichkeit hat), so dass Zeiger auf Kinder nicht "später hinzugefügt" werden können. Einer der Gründe ist, dass die Kennung des Commits (z. B. in "übergeordneten" Links) vom Inhalt des Commits abhängt. Dies ist die einzige Lösung in einem verteilten System ohne zentrale Nummerierungsbehörde. Auch "Kinder" von Commits hängen von den Zweigen ab, die Sie haben, und dies kann wiederum von Repository zu Repository unterschiedlich sein (und Commits sind in jedem Repository gleich).
Jakub Narębski
4
@becomingGuru Ich habe es abgelehnt. Es mag wahr sein, aber es beantwortet meine Frage nicht. Die Frage ist: "Wie finde ich das nächste Commit in Git?" Es ist nicht "Speichert ein Git-Commit einen Zeiger auf seine untergeordneten Elemente?"
Schwern
0

Bei vorhandenen Antworten wird davon ausgegangen, dass Sie einen Zweig haben, der das gesuchte Commit enthält.

In meinem Fall war das von mir gesuchte Commit nicht aktiviert git rev-list --all da dies in keinem Zweig enthalten war.

Am Ende habe ich durchgesehen gitk --reflog manuell .

Wenn Sie Ihr Commit nicht einmal im Reflog finden können, versuchen Sie Folgendes:

  • git fsck --full baumelnde Commits (dh nicht in einem Zweig) aufzulisten, oder
  • git fsck --lost-found Refs zu machen, die auf baumelnde Commits hinweisen, Techniken in den anderen Antworten anzuwenden.
snipsnipsnip
quelle