Comment trouver le prochain commit dans git? (enfant / enfants de référence)

236

ref^fait référence à la validation avant ref, qu'en est-il pour obtenir la validation après ref ?

Par exemple, si je git checkout 12345comment vérifier le prochain commit?

Merci.

PS Oui, git est un arbre de structure de pointeur de nœud DAG. Comment trouver le commit après celui-ci?

Schwern
la source
2
Voir aussi " git children-of"!
VonC

Réponses:

185

Pour répertorier tous les commits, en commençant par celui en cours, puis son enfant, et ainsi de suite - essentiellement un journal git standard, mais dans l'autre sens dans le temps, utilisez quelque chose comme

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

où 894e8b4e93d8f3 est le premier commit que vous souhaitez afficher.

Tim Hunt
la source
3
Pour le cas spécifique de la question d'origine, remplacez simplement HEAD^par 894e8b4e93d8f3^.
Søren Løvborg
2
Peut-être que l'ajout de --oneline est un meilleur résultat de sortie bref.
firo
1
Je dois utiliser ..., ^..échoue silencieusement
TankorSmash
1
Accès fatal: unrecognized argument: --ancestry-pathà
Git
6
Cela ne fonctionnera que s'il se mastertrouve sur le chemin d'ascendance du commit actuel. Voir le deuxième extrait de code de ma réponse pour une solution qui fonctionnera dans tous les cas.
Tom Hale
37

Le créateur de Hudson (maintenant Jenkins) Kohsuke Kawaguchi vient de publier (novembre 2013):
kohsuke / git-children-of :

Étant donné un commit, trouvez les enfants immédiats de ce commit.

#!/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

Comme illustré par ce fil , dans un VCS basé sur l'historique représenté par un DAG (Directed Acyclic Graph) , il n'y a pas "un parent" ou "un enfant".

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

La commande des commits se fait par "topo-order" ou "date-order" (voir livre GitPro )

Mais depuis Git1.6.0 , vous pouvez lister les enfants d'un commit.

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

Remarque: pour les validations parentes , vous rencontrez le même problème, le suffixe ^d'un paramètre de révision signifiant le premier parent de cet objet de validation. ^<n>signifie le <n>parent e (c. rev^ -à- d. est équivalent à rev^1).

Si vous êtes sur une branche fooet que vous émettez " git merge bar", vous fooserez le premier parent.
C'est-à-dire: le premier parent est la branche sur laquelle vous étiez lorsque vous avez fusionné, et le second est la validation sur la branche dans laquelle vous avez fusionné.

VonC
la source
6
git rev-list --childrenressemble vraiment à ce que je veux, mais ce n'est pas DWIM. Il semble répertorier tous les parents et leurs enfants. Je suppose que je peux tous les énumérer et les analyser ... bh, mais c'est quelque chose.
Schwern
@Schwern: vrai, ce git rev-list --childrenn'est pas pour lister seulement les enfants, mais pour lister les parents avec leurs enfants ... vous devez toujours analyser.
VonC
J'ai essayé ce code sur un commit avec deux enfants: $ git children0of 9dd5932 fatal: No annotated tags can describe '71d7b5dd89d241072a0a078ff2c7dfec05d52e1f'. However, there were unannotated tags: try --tags. quelle sortie obtenez-vous?
Tom Hale
@TomHale Je ne peux pas le tester pour le moment, mais posez une nouvelle question (avec la version OS et Git), de cette façon, tout le monde peut tester.
VonC
1
Le seul problème avec git-children-ofest qu'il utilise git decrire qui tente de formater le SHA comme lisible par l'homme, ce qui peut échouer avec l'erreur de @ TomHale, et donne des résultats comme v1.0.4-14-g2414721celui-ci sont déroutants si vous vous attendiez à un SHA. Le remplacer par un simple en echofait un excellent outil, merci!
Nickolay
18

ce que j'ai trouvé c'est

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

où je mets commit1comme commit actuel et commit2à la tête actuelle. Cela me renvoie une liste de tous les commits qui créent un chemin entre commit1et commit2.

La dernière ligne de la sortie est l'enfant de commit1 (sur le chemin de commit2).

white_gecko
la source
3
Il suffit donc d'ajouter | tail -1pour obtenir l'enfant.
Jesse Glick
8

Je vois ce que tu veux dire. Il est frustrant d'avoir une syntaxe abondante pour passer aux validations précédentes, mais aucune pour passer aux suivantes. Dans une histoire complexe, le problème de «quelle est la prochaine validation» devient plutôt difficile, mais dans la fusion complexe, la même dureté émerge également avec les validations «précédentes». Dans le cas simple, à l'intérieur d'une seule branche avec un historique linéaire (même juste localement pour un nombre limité de commits), il serait intéressant et logique d'avancer et de reculer.

Le vrai problème avec cela, cependant, est que les validations des enfants ne sont pas référencées, c'est seulement une liste liée en arrière. Trouver le commit enfant prend une recherche, ce qui n'est pas trop mal, mais probablement pas quelque chose que git veut mettre dans la logique refspec.

Quoi qu'il en soit, je suis tombé sur cette question parce que je veux simplement avancer dans l'histoire, un engagement à la fois, faire des tests, et parfois vous devez avancer et non reculer. Eh bien, avec un peu plus de réflexion, j'ai trouvé cette solution:

Choisissez un commit avant votre arrivée. Cela pourrait probablement être un chef de branche. Si vous êtes à la branche ~ 10, puis "git checkout branch ~ 9" puis "git checkout branch ~ 8" pour obtenir le suivant après cela, puis "git checkout branch ~ 7" et ainsi de suite.

La décrémentation du nombre devrait être très facile dans un script si vous en avez besoin. Beaucoup plus facile que d'analyser git rev-list.

vontrapp
la source
Bien qu'utile, il ne trouve pas le prochain commit. Il marche vers le commit actuel.
Schwern
1
Eh bien, je suppose que vous pourriez faire: " BRANCH=master; git co $BRANCH~$[ $(git rev-list HEAD..$BRANCH | wc -l) - 1 ]" Vous devez aller vers une branche, pas question.
vontrapp
8

Deux réponses pratiques:

Un enfant

Sur la base de la réponse de @ Michael , j'ai piraté l' childalias dans mon .gitconfig.

Il fonctionne comme prévu dans le cas par défaut et est également polyvalent.

# 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' -"

Par défaut, il donne à l'enfant de HEAD (sauf si un autre argument commit-ish est donné) en suivant l'ascendance un pas vers la pointe de la branche en cours (sauf si un autre commit-ish est donné comme deuxième argument).

Utilisez %hau lieu de %Hsi vous voulez la forme de hachage courte.

Plusieurs enfants

Avec une tête détachée (il n'y a pas de branche) ou pour obtenir tous les enfants indépendamment des branches:

# 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' -"

Modifiez le $1pour $*imprimer tous les enfants.

Vous pouvez également passer --allà un commit-ish pour afficher uniquement les enfants qui sont les ancêtres de ce commit, en d'autres termes, pour afficher uniquement les enfants «dans le sens» du commit donné. Cela peut vous aider à réduire la sortie de nombreux enfants à un seul.

Tom Hale
la source
7

Il n'y a pas de "prochain commit" unique. Étant donné que l'historique dans Git est un DAG et non une ligne, de nombreuses validations peuvent avoir un parent commun (branches) et les validations peuvent avoir plusieurs parents (fusion).

Si vous avez une branche particulière en tête, vous pouvez consulter son journal et voir quel commit répertorie la branche actuelle comme son parent.

Phil Miller
la source
37
Selon cette logique, il n'y a pas non plus de "commit précédent", mais il y a beaucoup de syntaxe pour obtenir le (s) parent (s).
Schwern
7
@Schwern: Il n'y a pas non plus de "commit précédent"; <rev>^est "parent commit" ("premier parent" pour les validations de fusion).
Jakub Narębski
6

J'ai essayé de nombreuses solutions différentes et aucune n'a fonctionné pour moi. Je devais trouver le mien.

trouver le prochain commit

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

trouver le commit précédent

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

Dans le cas où vous n'avez pas de validation de "destination" particulière à l'esprit, mais que vous souhaitez plutôt voir les validations enfants qui pourraient être sur n'importe quelle branche, vous pouvez utiliser cette commande:

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

Si vous voulez voir tous les enfants et petits -enfants , vous devez utiliser rev-list --childrenrécursivement, comme ceci:

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

(La version qui ne donne que des petits-enfants utiliserait un système plus complexe sedet / ou cut.)

Enfin, vous pouvez alimenter cela dans une log --graphcommande pour voir la structure arborescente, comme ceci:

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

Remarque : les commandes ci-dessus supposent toutes que vous avez défini la variable shell ${COMMIT}sur une référence (branche, balise, sha1) du commit dont les enfants vous intéressent.

Matt McHenry
la source
2
cela répond à la question que je ne savais pas comment formuler pour google et stackoverflow, mais que j'essayais de poser. merci d'avoir reconnu proactivement le besoin
Tommy Knowlton
pour moi ne ${COMMIT}résiste pas, mais vous pouvez utiliser à la $(git rev-parse HEAD) place
Radon8472
désolé, a ajouté une note à propos de ${COMMIT}.
Matt McHenry
5

(Cela a commencé comme une réponse à une question en double. J'ai fait un peu de retouche légère pour le nettoyer.)

Toutes les flèches internes de Git sont à sens unique, pointant vers l'arrière. Il n'y a donc pas de courte syntaxe pratique pour avancer: ce n'est tout simplement pas possible.

Il est possible de "bouger contre les flèches", mais la façon de le faire est surprenante si vous ne l'avez pas vue auparavant, puis évidente après. Disons que nous avons:

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

L'utilisation middle~2suit les flèches deux fois de l' Carrière vers A. Alors, comment pouvons-nous passer de Cà D? La réponse est: nous commençons par E, en utilisant le nom last, et nous remontons jusqu'à ce que nous arrivions middle, en enregistrant les points que nous visitons en cours de route . Ensuite, nous allons aussi loin que nous voulons dans le sens de last: déplacer un pas vers D, ou deux vers E.

Ceci est particulièrement important lorsque nous avons des succursales:

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

Quel engagement est une étape après C? Il n'y a pas de bonne réponse tant que vous n'ajoutez pas à la question: dans le sens de la fonction___ (remplissez le blanc).

Pour énumérer les commits entre C(à l'exclusion C) lui-même et, disons G, nous utilisons:

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

Le --topo-orderfait en sorte que même en présence de branchements et de fusions complexes, les commits sortent par ordre topologique. Ceci n'est requis que si la chaîne n'est pas linéaire. La --ancestry-pathcontrainte signifie que lorsque nous travaillons en arrière à partir de feature2, nous répertorions uniquement les validations qui ont été validées Ccomme l'un de leurs propres ancêtres. Autrement dit, si le graphique - ou la partie pertinente de celui-ci de toute façon - ressemble réellement à ceci:

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

une simple demande du formulaire feature2..masterénumère les commits J, Get I, et Fet Hdans un certain ordre. Avec --ancestry-pathnous assommons Het I: ils ne sont pas des descendants de C, seulement de A. Avec --topo-ordernous nous assurons que l'ordre d'énumération est réelle J, puis G, alors F.

La git rev-listcommande répand ces ID de hachage sur sa sortie standard, une par ligne. Pour avancer d'un pas dans la direction de feature2, alors, nous voulons juste la dernière ligne.

Il est possible (et tentant et peut être utile) d'ajouter --reverseafin d' git rev-listimprimer les validations dans l'ordre inverse après les avoir générées. Cela fonctionne, mais si vous l'utilisez dans un pipeline comme celui-ci:

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

pour obtenir juste le "prochain commit dans le sens de id2", et il y a une très longue liste de commits, la git rev-listcommande peut obtenir un tube cassé quand elle essaie d'écrire, headce qui a arrêté la lecture de son entrée et est sorti. Étant donné que les erreurs de canal cassé sont normalement ignorées par le shell, cela fonctionne principalement. Assurez-vous simplement qu'ils sont ignorés dans votre utilisation.

Il est également tentant d'ajouter -n 1à la git rev-listcommande, avec --reverse. Ne fais pas ça! Cela fait git rev-lists'arrêter après avoir fait un pas en arrière , puis inverser la liste (une entrée) des commits visités. Donc, cela produit juste<id2> chaque fois.

Remarque importante

Notez qu'avec des fragments de graphe "diamant" ou "anneau de benzène":

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

déplacer un commit "en avant" de Hvers lastvous obtiendra soit I ou K . Vous ne pouvez rien y faire: les deux validations sont un pas en avant! Si vous commencez ensuite à partir de la validation résultante et passez à une autre étape, vous êtes maintenant engagé sur le chemin sur lequel vous avez commencé.

Le remède à cela est d'éviter de se déplacer une étape à la fois et de s'enfermer dans des chaînes dépendantes du chemin. Au lieu de cela, si vous prévoyez de visiter toute une chaîne de chemin d'ascendance, avant de faire quoi que ce soit d'autre , faites une liste complète de tous les commits de la chaîne:

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

Ensuite, visitez chaque commit de cette liste, un par un, et vous obtiendrez toute la chaîne. Le --topo-orderfera en sorte que vous Ifrappiez -et- Jdans cet ordre, et K-et- Ldans cet ordre (bien qu'il n'y ait pas de moyen facile de prédire si vous ferez la paire IJ avant ou après la paire KL).

torek
la source
" Il n'y a pas de bonne réponse jusqu'à ce que vous ajoutiez à la question: dans le sens de la fonction___ " C'est un très bon point. Merci pour votre réponse.
Schwern
4

J'ai cet alias dans ~/.gitconfig

first-child = "!f() { git log  --reverse --ancestry-path --pretty=%H $1..${2:-HEAD} | head -1; }; f"
faible
la source
c'est quoi f()? Et ce doit head -1être le premier enfant, sinon cela rapportera simplement la TÊTE.
Xerus
@Xerus Parce qu'il utilise une syntaxe shell "complexe", et git ne le reconnaîtra pas s'il n'est pas encapsulé f(). Oui, head -1c'est une supposition courageuse.
faible
Agréable! A ajusté le mien pour: nextref = "!f() { git log --reverse --ancestry-path --pretty=%H $1..HEAD | head -${2:-1} | tail -1; }; f"afin que vous puissiez choisir dans quelle mesure, en option
Z. Khullah
2

J'ai réussi à trouver le prochain enfant de la manière suivante:

git log --reverse --children -n1 HEAD (where 'n' is the number of children to show)
DevRQ
la source
1
pour moi, cela ne montre pas le premier enfant, cela montre le commit actuel
Radon8472
2

Si les commits enfants sont tous sur une branche, vous pouvez utiliser gitk --all commit^.., où "commit" est quelque chose identifiant le commit. Par exemple, si l'abréviation SHA-1 du commit est c6661c5, tapezgitk --all c6661c5^..

Vous devrez probablement entrer le SHA-1 complet dans la cellule "SHA1 ID:" de gitk. Vous aurez besoin du SHA-1 complet, qui pour cet exemple peut être obtenu viagit rev-parse c6661c5

Alternativement, git rev-list --all --children | grep '^c6661c5883bb53d400ce160a5897610ecedbdc9d'produira une ligne contenant tous les enfants de ce commit, probablement s'il y a ou non une branche impliquée.

user339589
la source
0

Chaque commit stocke un pointeur sur son parent (parents, en cas de commit de fusion (standard)).

Il n'y a donc aucun moyen de pointer vers un commit enfant (s'il y en a un) du parent.

Lakshman Prasad
la source
La validation ne peut pas stocker de pointeurs vers ses enfants, car des validations enfants supplémentaires (points de branchement) peuvent être ajoutées ultérieurement.
Jakub Narębski
@Jakub je ne suis pas vraiment. Ils ne peuvent pas être ajoutés plus tard?
Schwern
1
Jakub: C'est exactement ce que j'ai dit. «Chaque commit stocke des pointeurs uniquement vers son parent.»
Lakshman Prasad
@Schwern: Les commits dans git sont immuables (ce qui a une bonne conséquence de la responsabilité), donc les pointeurs vers les enfants ne peuvent pas "être ajoutés plus tard". L'une des raisons est que l'identifiant de commit (utilisé par exemple dans les liens "parents") dépend du contenu du comit; c'est la seule solution dans un système distribué, sans autorité de numérotation centrale. De plus, les «enfants» des validations dépendent des branches que vous avez, ce qui peut être différent d'un référentiel à l'autre (et les validations sont les mêmes dans chaque référentiel).
Jakub Narębski
4
@becomingGuru J'ai voté contre. C'est peut-être vrai, mais cela ne répond pas à ma question. La question est "comment trouver le prochain commit dans git?" Ce n'est pas "un git commit stocke-t-il un pointeur sur ses enfants?"
Schwern
0

Les réponses existantes supposent que vous avez une branche contenant le commit que vous recherchez.

Dans mon cas, le commit que je cherchais n'était pas activé git rev-list --allcar aucune branche ne le contenait.

J'ai fini par regarder gitk --reflogmanuellement.

Si vous ne trouvez pas votre commit même dans le reflog, essayez soit:

  • git fsck --full pour répertorier les commissions pendantes (c'est-à-dire n'appartenant à aucune branche), ou
  • git fsck --lost-found faire des refs pointant vers les pendants s'engage à appliquer des techniques dans les autres réponses.
snipsnipsnip
la source