Como encontro o próximo commit no git? (filho / a de ref)

236

ref^refere-se ao commit antes ref, que tal obter o commit depois ref ?

Por exemplo, se eu, git checkout 12345como verifico o próximo commit?

Obrigado.

PS Sim, o git é uma árvore de estrutura de ponteiro de nó do DAG. Como encontro o commit após este?

Schwern
fonte
2
Veja também " git children-of"!
VonC 22/11/2013

Respostas:

185

Para listar todos os commits, começando pelo atual e depois seu filho, e assim por diante - basicamente o log git padrão, mas seguindo o caminho inverso no tempo, use algo como

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

onde 894e8b4e93d8f3 é o primeiro commit que você deseja mostrar.

Tim Hunt
fonte
3
Para o caso específico na pergunta original, basta substituir HEAD^por 894e8b4e93d8f3^.
Søren Løvborg
2
Talvez, add --oneline seja um melhor resultado breve.
firo
1
Eu preciso usar ..., ^..falha silenciosamente
TankorSmash
1
Ficando fatal: unrecognized argument: --ancestry-pathem git versão 1.7.1
user151841
6
Isso funcionará apenas se masterestiver no caminho de ascendência do commit atual. Veja o segundo trecho de código da minha resposta para obter uma solução que funcione em todos os casos.
Tom Hale
37

O criador do Hudson (agora Jenkins) Kohsuke Kawaguchi acaba de publicar (novembro de 2013):
kohsuke / git-children-of :

Dado um commit, encontre os filhos imediatos desse 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

Como ilustrado por esta discussão , em um VCS baseado na história representada por um DAG (Directed Acyclic Graph) , não há "um pai" ou "um filho".

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

A ordenação das confirmações é feita por "ordem superior" ou "ordem da data" (consulte o livro GitPro )

Mas desde o Git1.6.0 , você pode listar os filhos de um commit.

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

Nota: para confirmações pai , você tem o mesmo problema, com o sufixo ^de um parâmetro de revisão que significa o primeiro pai desse objeto de confirmação. ^<n>significa o <n>th pai (ou seja, rev^ é equivalente a rev^1).

Se você estiver no ramo fooe emitir " git merge bar", fooserá o primeiro pai.
Ou seja: o primeiro pai é o ramo em que você estava quando você mesclou e o segundo é o commit no ramo em que você mesclou.

VonC
fonte
6
git rev-list --childrencerteza parece com o que eu quero, mas não DWIM. Parece listar todos os pais e filhos. Suponho que posso listá-los todos e analisá-los ... bleh, mas é alguma coisa.
Schwern
@ Schwern: verdade, git rev-list --childrennão é para listar apenas filhos, mas para listar pais e filhos ... você sempre precisa analisar.
VonC
Eu tentei esse código em um commit com dois filhos: $ git children0of 9dd5932 fatal: No annotated tags can describe '71d7b5dd89d241072a0a078ff2c7dfec05d52e1f'. However, there were unannotated tags: try --tags. Qual saída você obtém?
Tom Hale
@ TomHale Não posso testá-lo agora, mas faça uma nova pergunta (com a versão do SO e do Git), para que todos possam testar.
VonC 19/09/16
1
O único problema git-children-ofé que ele usa o git descrevem que tenta formatar o SHA como legível por humanos, o que pode falhar com o erro do @ TomHale e fornece resultados assim v1.0.4-14-g2414721confusos se você esperava um SHA. Substituí-lo por um simples echofaz desta uma excelente ferramenta, obrigado!
Nickolay
18

o que eu achei é

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

onde eu defini commit1como o commit atual e commit2para o head atual. Isso me retorna uma lista de todos os commits que constroem um caminho entre commit1e commit2.

A última linha da saída é filha de commit1 (no caminho para commit2).

white_gecko
fonte
3
Então basta adicionar | tail -1para pegar a criança.
Jesse Glick
8

Eu sei o que você quer dizer. É frustrante ter uma sintaxe abundante para ir para confirmações anteriores, mas nenhuma para ir para as próximas. Em uma história complexa, o problema de "qual é o próximo commit" torna-se bastante difícil, mas depois, na mesclagem complexa, a mesma dureza surge com os commits 'anteriores'. No caso simples, dentro de um único ramo com um histórico linear (mesmo localmente para um número limitado de confirmações), seria bom e faria sentido avançar e retroceder.

O problema real com isso, no entanto, é que os filhos confirmados não são referenciados, é apenas uma lista vinculada ao contrário. Encontrar o commit filho requer uma pesquisa, o que não é muito ruim, mas provavelmente não é algo que o git deseja colocar na lógica refspec.

De qualquer forma, me deparei com essa pergunta porque simplesmente quero avançar na história que um commit de cada vez, fazendo testes, e às vezes você precisa dar um passo à frente e não para trás. Bem, pensando um pouco mais, eu vim com esta solução:

Escolha um commit antes de onde você está. Provavelmente poderia ser uma cabeça de ramo. Se você estiver no ramo ~ 10, "git checkout branch ~ 9" e "git checkout branch ~ 8" para obter o próximo depois disso, "git checkout branch ~ 7" e assim por diante.

Diminuir o número deve ser realmente fácil em um script, se você precisar. Muito mais fácil do que analisar git rev-list.

vontrapp
fonte
Embora útil, não encontra o próximo commit. Ele caminha em direção ao commit atual.
Schwern 28/02/12
1
Bem, então suponho que você poderia fazer: " BRANCH=master; git co $BRANCH~$[ $(git rev-list HEAD..$BRANCH | wc -l) - 1 ]" Você tem que ir em direção a um galho, de jeito nenhum.
Vontrapp
8

Duas respostas práticas:

Uma criança

Com base na resposta de @ Michael , cortei o childalias no meu .gitconfig.

Funciona como esperado no caso padrão e também é 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' -"

O padrão é fornecer o filho de HEAD (a menos que outro argumento de confirmação seja fornecido) seguindo a ancestralidade um passo em direção à ponta do ramo atual (a menos que outro commit-ish seja fornecido como segundo argumento).

Use em %hvez de %Hse desejar o formato de hash curto.

Vários filhos

Com um HEAD desanexado (não há ramo) ou para obter todos os filhos, independentemente dos ramos:

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

Mude $1para $*para imprimir todos os filhos.

Você também pode mudar --allpara um commit-ish para exibir apenas os filhos que são ancestrais desse commit - em outras palavras, para exibir apenas os filhos “na direção” do commit fornecido. Isso pode ajudá-lo a reduzir a produção de muitos filhos para apenas um.

Tom Hale
fonte
7

Não existe um "próximo commit" exclusivo. Como o histórico no Git é um DAG, e não uma linha, muitos commits podem ter um pai comum (ramificações) e os commits podem ter mais de um pai (mesclagens).

Se você tem uma ramificação específica em mente, pode ver o log e ver qual confirmação lista a atual como pai.

Phil Miller
fonte
37
Por essa lógica, também não há "confirmação prévia", mas há muita sintaxe para obter o (s) pai (s).
Schwern 15/02/10
7
@ Schwern: Também não há "commit anterior"; <rev>^é "pai confirmado" ('primeiro pai' para confirmações de mesclagem).
Jakub Narębski
6

Eu tentei muitas soluções diferentes e nenhuma funcionou para mim. Tinha que inventar o meu.

encontre o próximo commit

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

encontre confirmação anterior

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

No caso em que você não tem uma confirmação de "destino" específica em mente, mas deseja ver confirmações filho que possam estar em qualquer ramificação, você pode usar este comando:

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

Se você quiser ver todos os filhos e netos , precisará usar rev-list --childrenrecursivamente, assim:

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

(A versão que fornece apenas netos usaria um conjunto mais complexo sede / ou cut.)

Finalmente, você pode alimentar isso em um log --graphcomando para ver a estrutura da árvore, assim:

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

Nota : todos os comandos acima assumem que você configurou a variável shell ${COMMIT}para alguma referência (branch, tag, sha1) do commit em cujos filhos você está interessado.

Matt McHenry
fonte
2
isso responde à pergunta que eu não sabia como formular para google e stackoverflow, mas estava tentando perguntar. obrigado por reconhecer de forma proativa a necessidade
Tommy Knowlton
para mim ${COMMIT}does not ESIST, mas você poderia usar $(git rev-parse HEAD) em vez
Radon8472
desculpe, adicionou uma observação sobre ${COMMIT}.
Matt McHenry
5

(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).

torek
fonte
" Não há resposta correta até você adicionar à pergunta: na direção do recurso___ " Este é um ponto muito bom. Obrigado pela sua resposta.
Schwern 4/10/19
4

Eu tenho esse apelido em ~/.gitconfig

first-child = "!f() { git log  --reverse --ancestry-path --pretty=%H $1..${2:-HEAD} | head -1; }; f"
fraco
fonte
o que é f()? E precisa head -1ser o primeiro filho, caso contrário, isso simplesmente informará o HEAD.
Xerus
@Xerus Porque usa alguma sintaxe de shell "complexa" e o git não a reconhecerá se não estiver envolvida f(). Sim, head -1é um palpite corajoso.
weakish
Agradável! Meu Tweaked para: nextref = "!f() { git log --reverse --ancestry-path --pretty=%H $1..HEAD | head -${2:-1} | tail -1; }; f"assim que você pode escolher como muito à frente, opcionalmente
Z. Khullah
2

Consegui encontrar o próximo filho da seguinte maneira:

git log --reverse --children -n1 HEAD (where 'n' is the number of children to show)
DevRQ
fonte
1
para mim isso não mostra a primeira criança, mostra o atual comprometer
Radon8472
2

Se as confirmações filho estiverem todas em alguma ramificação, você poderá usar gitk --all commit^.., onde "confirmar" é algo que identifica a confirmação. Por exemplo, se o SHA-1 abreviado da confirmação for c6661c5, digitegitk --all c6661c5^..

Você provavelmente precisará inserir o SHA-1 completo na célula "SHA1 ID:" do gitk. Você precisará do SHA-1 completo, que para este exemplo pode ser obtido viagit rev-parse c6661c5

Como alternativa, git rev-list --all --children | grep '^c6661c5883bb53d400ce160a5897610ecedbdc9d'produzirá uma linha contendo todos os filhos desse commit, presumivelmente se existe ou não um ramo envolvido.

user339589
fonte
0

Cada confirmação armazena um ponteiro para o pai (pais, em caso de consolidação (padrão)).

Portanto, não há como apontar para um commit filho (se houver) do pai.

Lakshman Prasad
fonte
A confirmação não pode armazenar ponteiros para seus filhos, pois confirmações filho adicionais (pontos de ramificação) podem ser adicionadas posteriormente.
Jakub Narębski
@ Jakub Eu realmente não sigo. Eles não podem ser adicionados mais tarde?
Schwern 16/02/10
1
Jakub: Foi exatamente o que eu disse. 'Cada confirmação armazena ponteiros apenas para seu pai.'
Lakshman Prasad
@ Schwern: As confirmações no git são imutáveis ​​(o que tem uma boa consequência da responsabilidade), portanto, os indicadores para as crianças não podem ser "adicionados posteriormente". Um dos motivos é que o identificador do commit (usado, por exemplo, nos links "pai") depende do conteúdo do comitê; essa é a única solução em sistema distribuído, sem autoridade central de numeração. Além disso, "filhos" de confirmações dependem dos ramos que você possui, e isso, por sua vez, pode ser diferente de repositório para repositório (e confirmações são as mesmas em cada repositório).
Jakub Narębski
4
@becomingGuru Eu votei para baixo. Pode ser verdade, mas não responde à minha pergunta. A pergunta é "como encontro o próximo commit no git?" Não é "um git cometer armazena um ponteiro para seus filhos?"
Schwern 16/02/10
0

As respostas existentes assumem que você tem um ramo que contém o commit que você está procurando.

No meu caso, o commit que eu estava procurando não estava, git rev-list --allpois nenhum ramo continha isso.

Acabei olhando gitk --reflogmanualmente.

Se você não conseguir encontrar seu commit nem no reflog, tente:

  • git fsck --full para listar confirmações pendentes (ou seja, que não estejam em nenhum ramo)
  • git fsck --lost-found fazer refs apontando para dangling se compromete a aplicar técnicas nas outras respostas.
snipsnipsnip
fonte