¿Cómo encuentro el próximo commit en git? (niño / hijos de referencia)

236

ref^se refiere a la confirmación antes ref, ¿qué pasa con la confirmación después ref ?

Por ejemplo, si yo, git checkout 12345¿cómo verifico el próximo commit?

Gracias.

PD Sí, git es un árbol de estructura de puntero de nodo DAG. ¿Cómo encuentro el commit después de este?

Schwern
fuente
2
Ver también " git children-of"!
VonC

Respuestas:

185

Para enumerar todas las confirmaciones, comenzando por la actual, y luego su hijo, y así sucesivamente, básicamente el registro de git estándar, pero yendo hacia el otro lado en el tiempo, use algo como

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

donde 894e8b4e93d8f3 es la primera confirmación que desea mostrar.

Tim Hunt
fuente
3
Para el caso concreto de la pregunta original, simplemente sustituir HEAD^a 894e8b4e93d8f3^.
Søren Løvborg
2
Tal vez, agregue --oneline es un mejor resultado de salida breve.
firo
1
Necesito usar ..., ^..falla en silencio
TankorSmash
1
Entrar fatal: unrecognized argument: --ancestry-pathen git versión 1.7.1
user151841
66
Esto solo funcionará si masterestá en la ruta de ascendencia de la confirmación actual. Vea el segundo fragmento de código de mi respuesta para obtener una solución que funcione en todos los casos.
Tom Hale
37

El creador de Hudson (ahora Jenkins) Kohsuke Kawaguchi acaba de publicar (noviembre de 2013):
kohsuke / git-children-of :

Dado un compromiso, encuentre hijos inmediatos de ese compromiso.

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

Como lo ilustra este hilo , en un VCS basado en el historial representado por un DAG (Gráfico Acíclico Dirigido) , no hay "un padre" o "un hijo".

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

El pedido de commits se realiza por "topo-order" o "date-order" (ver el libro de GitPro )

Pero desde Git1.6.0 , puede enumerar los elementos secundarios de una confirmación.

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

Nota: para las confirmaciones principales , tiene el mismo problema, con el sufijo ^de un parámetro de revisión que significa el primer elemento primario de ese objeto de confirmación. ^<n>significa el <n>padre th ( rev^ es decir, es equivalente a rev^1).

Si está en la sucursal fooy emite " git merge bar", fooserá el primer padre.
Es decir: el primer padre es la rama en la que estaba cuando se fusionó, y el segundo es el compromiso en la rama en la que se fusionó.

VonC
fuente
66
git rev-list --childrenseguro se parece a lo que quiero, pero no DWIM. Parece enumerar a todos los padres y sus hijos. Supongo que puedo enumerarlos a todos y analizarlos ... bleh, pero es algo.
Schwern
@Schwern: es cierto, git rev-list --childrenno es solo para enumerar a los niños, sino para enumerar a los padres con sus hijos ... siempre debe analizar.
VonC
Intenté ese código en un commit con dos hijos: $ git children0of 9dd5932 fatal: No annotated tags can describe '71d7b5dd89d241072a0a078ff2c7dfec05d52e1f'. However, there were unannotated tags: try --tags. ¿Qué resultado obtienes?
Tom Hale
@TomHale No puedo probarlo en este momento, pero haga una nueva pregunta (con el sistema operativo y la versión Git), de esa manera todos pueden probar.
VonC
1
El único problema con git-children-ofes que usa git describe que los intentos de formatear el SHA son legibles para los humanos, lo que puede fallar con el error de @ TomHale, y da resultados como v1.0.4-14-g2414721si fueran confusos si esperaba un SHA. Reemplazarlo con un simple lo echoconvierte en una herramienta excelente, ¡gracias!
Nickolay
18

lo que encontré es

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

donde configuré commit1como el commit actual y commit2para el jefe actual. Esto me devuelve una lista de todos los commits que crean una ruta entre commit1y commit2.

La última línea de la salida es el hijo de commit1 (en la ruta de acceso a commit2).

white_gecko
fuente
3
Así que solo agregue | tail -1para obtener el niño.
Jesse Glick
8

Yo sé lo que quieres decir. Es frustrante tener una sintaxis abundante para ir a confirmaciones anteriores, pero ninguna para pasar a las siguientes. En una historia compleja, el problema de "cuál es el próximo compromiso" se vuelve bastante difícil, pero luego, en la fusión compleja, surge la misma dureza con los compromisos 'anteriores' también. En el caso simple, dentro de una sola rama con un historial lineal (incluso solo localmente para un número limitado de confirmaciones) sería bueno y tendría sentido avanzar y retroceder.

Sin embargo, el verdadero problema con esto es que los commits de los niños no están referenciados, es solo una lista vinculada hacia atrás. Encontrar el child commit requiere una búsqueda, lo cual no es tan malo, pero probablemente no es algo que git quiera poner en la lógica de refspec.

En cualquier caso, me encontré con esta pregunta porque simplemente quiero avanzar en la historia, un compromiso a la vez, haciendo pruebas, y a veces hay que avanzar y no retroceder. Bueno, pensando un poco más, se me ocurrió esta solución:

Elige un compromiso antes de donde estás. Esto probablemente podría ser una cabeza de rama. Si está en la sucursal ~ 10, luego "git checkout branch ~ 9" luego "git checkout branch ~ 8" para obtener el siguiente después de eso, luego "git checkout branch ~ 7" y así sucesivamente.

Disminuir el número debería ser realmente fácil en un script si lo necesita. Mucho más fácil que analizar git rev-list.

vontrapp
fuente
Si bien es útil, no encuentra la próxima confirmación. Camina hacia el compromiso actual.
Schwern
1
Bueno, entonces supongo que podrías hacer: " BRANCH=master; git co $BRANCH~$[ $(git rev-list HEAD..$BRANCH | wc -l) - 1 ]" Tienes que ir hacia una rama, no hay forma de evitar eso.
vontrapp
8

Dos respuestas prácticas:

Un niño

Basado en la respuesta de @ Michael , pirateé el childalias en mi .gitconfig.

Funciona como se esperaba en el caso predeterminado, y también es versátil.

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

El valor predeterminado es dar al hijo de HEAD (a menos que se proporcione otro argumento commit-ish) siguiendo la ascendencia un paso hacia la punta de la rama actual (a menos que se proporcione otro commit-ish como segundo argumento).

Use en %hlugar de %Hsi desea la forma hash corta.

Niños múltiples

Con una CABEZA separada (no hay rama) o para obtener todos los niños independientemente de las ramas:

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

Cambie $1a $*para imprimir todos los elementos secundarios.

También puede cambiar --alla un commit-ish para mostrar solo los hijos que son ancestros de ese commit; en otras palabras, para mostrar solo los hijos "en la dirección de" el commit dado. Esto puede ayudarlo a reducir la producción de muchos hijos a uno solo.

Tom Hale
fuente
7

No hay un "próximo compromiso" único. Debido a que el historial en Git es un DAG, y no una línea, muchos commits pueden tener un padre común (ramas), y los commits pueden tener más de un padre (fusiones).

Si tiene en mente una rama en particular, puede mirar su registro y ver qué confirmación enumera la presente como su padre.

Phil Miller
fuente
37
Según esa lógica, tampoco hay una "confirmación previa", pero hay mucha sintaxis para obtener los padres.
Schwern
77
@Schwern: Tampoco hay "confirmación previa"; <rev>^es "commit principal" ('primer padre' para los commits de fusión).
Jakub Narębski
6

He probado muchas soluciones diferentes y ninguna funcionó para mí. Tuve que inventar el mío.

encontrar el próximo compromiso

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

encontrar compromiso anterior

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

En el caso de que no tenga en mente una confirmación de "destino" particular, sino que desee ver las confirmaciones secundarias que podrían estar en cualquier rama, puede usar este comando:

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

Si desea ver a todos los hijos y nietos , debe usarlos de forma rev-list --childrenrecursiva, así:

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

(La versión que da solo nietos usaría una más compleja sedy / o cut).

Finalmente, puede introducir eso en un log --graphcomando para ver la estructura de árbol, así:

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

Nota : todos los comandos anteriores suponen que ha establecido la variable de shell ${COMMIT}en alguna referencia (branch, tag, sha1) del commit cuyos hijos le interesan.

Matt McHenry
fuente
2
Esto responde a la pregunta que no sabía cómo formular para google y stackoverflow, pero estaba tratando de preguntar. gracias por reconocer de manera proactiva la necesidad
Tommy Knowlton
para mí ${COMMIT}no es, pero podría usar $(git rev-parse HEAD) en su lugar
Radon8472
lo siento, agregué una nota sobre ${COMMIT}.
Matt McHenry
5

(Esto comenzó como una respuesta a una pregunta duplicada. He hecho un poco de edición ligera para limpiarlo).

Todas las flechas internas de Git son unidireccionales, apuntando hacia atrás. Por lo tanto, no hay una sintaxis corta y conveniente para avanzar: simplemente no es posible.

Que es posible "movimiento en contra de las flechas", pero la manera de hacerlo es sorprendente si no se ha visto antes, y luego obvia después. Digamos que tenemos:

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

Usando middle~2sigue las flechas dos veces de Catrás a A. Entonces, ¿cómo nos movemos de Ca D? La respuesta es: comenzamos en E, usando el nombre last, y trabajamos hacia atrás hasta llegar middle, registrando los puntos que visitamos en el camino . Luego nos movemos tan lejos como queremos en la dirección de last: mover un paso a D, o dos a E.

Esto es particularmente importante cuando tenemos sucursales:

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

¿Qué commit es un paso después C? No hay una respuesta correcta hasta que agregue a la pregunta: en la dirección de la función___ (complete el espacio en blanco).

Para enumerar las confirmaciones entre C(excluyendo C) en sí y, por ejemplo, Gusamos:

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

Esto --topo-orderse asegura de que incluso en presencia de complejas ramificaciones y fusiones, los commits salgan en orden topológico. Esto solo es necesario si la cadena no es lineal. La --ancestry-pathrestricción significa que cuando trabajamos desde atrás feature2, solo enumeramos los commits que tienen commit Ccomo uno de sus propios antepasados. Es decir, si el gráfico, o el fragmento relevante de todos modos, en realidad se ve así:

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

una simple solicitud del formulario feature2..masterenumera commits J, Gy I, y Fy Hen algún orden. Con --ancestry-pathnoqueamos Hy I: no son descendientes de C, solo de A. Con --topo-ordernos aseguramos de que el orden de enumeración real sea J, entonces G, entonces F.

El git rev-listcomando derrama estas identificaciones hash en su salida estándar, una por línea. Para avanzar un paso en la dirección de feature2, entonces, solo queremos la última línea.

Es posible (y tentador y puede ser útil) agregar --reversepara que git rev-listimprima las confirmaciones en orden inverso después de generarlas. Esto funciona, pero si lo usa en una tubería como esta:

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

solo para obtener el "próximo commit en la dirección de id2", y hay una lista muy larga de commits, el git rev-listcomando puede obtener una tubería rota cuando intenta escribir en el headque dejó de leer su entrada y salió. Dado que el shell normalmente ignora los errores de tubería rota, esto funciona principalmente. Solo asegúrate de que se ignoren en tu uso.

También es tentador agregar -n 1al git rev-listcomando, junto con --reverse. ¡No lo hagas! Eso hace que git rev-listpare después de caminar un paso atrás y luego revierta la lista de confirmaciones visitadas (una entrada). Entonces esto solo produce <id2>cada vez.

Nota al margen importante

Tenga en cuenta que con los fragmentos de gráfico "diamante" o "anillo de benceno":

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

moviendo una cometen "hacia adelante" de Hhacia lastobtendrá ya sea I o K . No hay nada que pueda hacer al respecto: ¡ambas confirmaciones son un paso adelante! Si luego comienza desde la confirmación resultante y da otro paso, ahora está comprometido con cualquier ruta en la que comenzó.

La cura para esto es evitar moverse un paso a la vez y quedar encerrado en cadenas dependientes de la ruta. En cambio, si planea visitar una cadena completa de ruta de ascendencia, antes de hacer cualquier otra cosa , haga una lista completa de todas las confirmaciones en la cadena:

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

Luego, visite cada confirmación en esta lista, una a la vez, y obtendrá toda la cadena. Se --topo-orderasegurará de que golpee I-y- Jen ese orden, y K-y- Len ese orden (aunque no hay una manera fácil de predecir si hará el par IJ antes o después del par KL).

torek
fuente
" No hay una respuesta correcta hasta que agregue a la pregunta: en la dirección de la función___ " Este es un muy buen punto. Gracias por tu respuesta.
Schwern
4

Tengo este alias en ~/.gitconfig

first-child = "!f() { git log  --reverse --ancestry-path --pretty=%H $1..${2:-HEAD} | head -1; }; f"
debilitar
fuente
lo que es f()? Y debe head -1ser el primer hijo, de lo contrario, esto simplemente informará a HEAD.
Xerus
@Xerus Debido a que usa una sintaxis de shell "compleja", y git no la reconocerá si no está envuelta f(). Sí, head -1es una suposición valiente.
débil
¡Agradable! Ajusté el mío para: nextref = "!f() { git log --reverse --ancestry-path --pretty=%H $1..HEAD | head -${2:-1} | tail -1; }; f"para que pueda elegir qué tan adelante, opcionalmente
Z. Khullah
2

Logré encontrar al próximo hijo de la siguiente manera:

git log --reverse --children -n1 HEAD (where 'n' is the number of children to show)
DevRQ
fuente
1
para mí esto no muestra el primer hijo, muestra la confirmación actual
Radon8472
2

Si los commits secundarios están todos en alguna rama, puede usar gitk --all commit^.., donde "commit" es algo que identifica el commit. Por ejemplo, si el SHA-1 abreviado de la confirmación es c6661c5, escribagitk --all c6661c5^..

Probablemente necesitará ingresar el SHA-1 completo en la celda "SHA1 ID:" de gitk. Necesitará el SHA-1 completo, que para este ejemplo se puede obtener a través degit rev-parse c6661c5

Alternativamente, git rev-list --all --children | grep '^c6661c5883bb53d400ce160a5897610ecedbdc9d'producirá una línea que contiene todos los elementos secundarios de este commit, presumiblemente si hay una rama involucrada o no.

user339589
fuente
0

Cada confirmación almacena un puntero a su padre (padres, en caso de confirmación de fusión (estándar)).

Por lo tanto, no hay forma de señalar un commit secundario (si hay uno) del padre.

Lakshman Prasad
fuente
Commit no puede almacenar punteros a sus hijos, ya que se pueden agregar commits secundarios adicionales (puntos de ramificación) en un punto posterior.
Jakub Narębski
@Jakub Realmente no sigo. ¿No se pueden agregar más tarde?
Schwern
1
Jakub: Eso es exactamente lo que dije. 'Cada commit almacena punteros solo para su padre'.
Lakshman Prasad
@Schwern: los compromisos en git son inmutables (lo que tiene una buena consecuencia de la responsabilidad), por lo que los consejos para los niños no pueden "agregarse más tarde". Una de las razones es que el identificador de commit (usado, por ejemplo, en enlaces "primarios") depende del contenido del comit; Esta es la única solución en un sistema distribuido, sin autoridad central de numeración. Además, los "hijos" de los commits dependen de las ramas que tenga, y esto a su vez puede ser diferente de un repositorio a otro (y los commits son los mismos en cada repositorio).
Jakub Narębski
44
@becomingGuru lo rechacé. Puede ser cierto, pero no responde mi pregunta. La pregunta es "¿cómo encuentro el próximo commit en git?" No es "¿un git commit almacena un puntero a sus hijos?"
Schwern
0

Las respuestas existentes suponen que tiene una rama que contiene la confirmación que está buscando.

En mi caso, la confirmación que estaba buscando no estaba activada git rev-list --allya que ninguna rama contenía eso.

Terminé mirando gitk --reflogmanualmente.

Si no puede encontrar su confirmación incluso en el registro, intente:

  • git fsck --full para enumerar confirmaciones colgantes (es decir, no en ninguna rama), o
  • git fsck --lost-found hacer referencias apuntando a compromisos pendientes para aplicar técnicas en las otras respuestas.
snipsnipsnip
fuente