(Isso começou como uma resposta a uma pergunta duplicada. Fiz um pouco de edição leve para limpá-la.)
Todas as setas internas do Git são unidirecionais, apontando para trás. Portanto, não existe uma sintaxe curta e conveniente para avançar: simplesmente não é possível.
Ele é possível "movimento contra as flechas", mas a maneira de fazer isso é surpreendente, se você não tê-lo visto antes, e depois óbvio depois. Digamos que temos:
A <-B <-C <-D <-E <-- last
^
|
\--------- middle
O uso middle~2segue as setas duas vezes de Cvolta para A. Então, como passamos Cpara D? A resposta é: começamos Eusando o nome laste trabalhamos para trás até chegarmos middle, registrando os pontos que visitamos ao longo do caminho . Depois, apenas avançamos o quanto queremos na direção de last: mova um passo para D, ou dois paraE .
Isso é particularmente importante quando temos filiais:
D--E <-- feature1
/
...--B--C <-- master
\
F--G <-- feature2
Qual commit é um passo depois C? Não há resposta correta até você adicionar à pergunta: na direção do recurso___ (preencha o espaço em branco).
Para enumerar os commits entre C(excluindo C) em si e, digamos, Gusamos:
git rev-list --topo-order --ancestry-path master..feature2
Os --topo-ordergarante que, mesmo na presença do complexo ramificação-and-fusão, os commits sair, a fim topologicamente-ordenada. Isso é necessário apenas se a corrente não for linear. A --ancestry-pathrestrição significa que, quando trabalhamos de trás para frente feature2, listamos apenas confirmações que foram confirmadas Ccomo um de seus ancestrais. Ou seja, se o gráfico - ou a parte relevante dele de qualquer maneira - se parecer com isso:
A--B--C <-- master
\ \
\ F--G--J <-- feature2
\ /
H-------I <-- feature3
um simples pedido do formulário feature2..masterenumera commits J, Ge I, e Fe Hem alguma ordem. Com --ancestry-pathnós nocauteamos He I: eles não são descendentes de C, apenas de A. Com --topo-order, garantimos que a ordem de enumeração real seja J, então G, então F.
O git rev-listcomando espalha esses IDs de hash em sua saída padrão, um por linha. Para avançar um passo na direção de feature2, então, queremos apenas a última linha.
É possível (e tentador e pode ser útil) adicionar --reversepara que git rev-listimprima os commits em ordem inversa após gerá-los. Isso funciona, mas se você o usar em um pipeline como este:
git rev-list --topo-order --ancestry-path --reverse <id1>...<id2> | head -1
para obter o "próximo commit na direção do id2", e há uma lista muito longa de commits, o git rev-listcomando pode obter um pipe quebrado ao tentar gravar no headqual parou de ler sua entrada e saiu. Como erros de tubos quebrados são normalmente ignorados pelo shell, isso geralmente funciona. Apenas verifique se eles são ignorados no seu uso.
Também é tentador adicionar -n 1ao git rev-listcomando, juntamente com --reverse. Não faça isso! Isso faz com que você git rev-listpare depois de dar um passo para trás e inverta a lista de confirmações visitadas (uma entrada). Então, isso só produz <id2>sempre.
Nota lateral importante
Observe que com os fragmentos do gráfico "diamante" ou "anel benzeno":
I--J
/ \
...--H M--... <-- last
\ /
K--L
movendo um commit "forward" de Hdireção lastvai te quer I ou K . Não há nada que você possa fazer sobre isso: ambos os commits estão um passo à frente! Se você iniciar a partir do commit resultante e seguir outra etapa, agora estará comprometido com o caminho que iniciou.
A cura para isso é evitar dar um passo de cada vez e ficar preso a cadeias dependentes do caminho. Em vez disso, se você planeja visitar uma cadeia inteira de caminho de ancestralidade, antes de fazer qualquer outra coisa , faça uma lista completa de todos os commits na cadeia:
git rev-list --topo-order --reverse --ancestry-path A..B > /tmp/list-of-commits
Em seguida, visite cada confirmação nesta lista, uma de cada vez, e você obterá toda a cadeia. O --topo-orderfará com que você bateu I-e- Jnessa ordem, e Ke- Lnessa ordem (embora não há nenhuma maneira fácil de prever se você vai fazer o par IJ antes ou depois de o par KL).