Entendendo git rev-list
Enquanto procurava exemplos de git hook, me deparei com a seguinte postagem: https://github.com/Movidone/git-hooks/blob/master/pre-receive e eu queria entender o seguinte comando:
git rev-list $new_list --not --all
onde new_list é obtida de:
NULL_SHA1="0000000000000000000000000000000000000000" # 40 0's
new_list=
any_deleted=false
while read oldsha newsha refname; do
case $oldsha,$newsha in *,$NULL_SHA1) # it's a delete
any_deleted=true;;
$NULL_SHA1,*) # it's a create new_list="$new_list $newsha";; *,*) # it's an update new_list="$new_list $newsha";;
esac
done
Achei que rev-list mostra commits em ordem cronológica reversa.
Mas, alguém pode compartilhar uma ideia mais clara sobre o que -note -allopções são destinados para?
Conforme a documentação:
--not
Reverses the meaning of the ^ prefix (or lack thereof) for all following revision specifiers, up to the next --not.
--all
Pretend as if all the refs in refs/ are listed on the command line as <commit>.
Não consigo entender completamente essas opções.
[Atualizar] Depois de fazer alguns commits de teste, percebi que se eu não usar as opções --note --allentão, git rev-listlista todos os commits no branch e não o que pretendo fazer push.
Porém, queria entender por que não imprime os valores de sha no terminal quando a --allopção é passada?
Respostas
O git rev-listcomando é um comando muito complicado e muito central no Git, pois o que ele faz é percorrer o gráfico . A palavra gráfico aqui se refere ao próprio gráfico de commits e, em alguns casos, ao próximo nível abaixo (objetos Git acessíveis a partir de commits).
Achei que rev-list mostra commits em ordem cronológica reversa.
Não exatamente, mas perto:
- O pedido pode ser alterado. O padrão é cronológico reverso.
- O padrão é percorrer alguns commits, mas você pode
rev-listir mais fundo para incluir objetos de árvore e blob e até objetos de tag. Isso é para programas comogit fetchegit push(que invocamgit pack-objects) egit pack-objects. Pretendo ignorar totalmente essa possibilidade aqui, mas sinto que deveria pelo menos mencioná-la. 😀
Portanto, o padrão é listar alguns commits em ordem cronológica reversa. É importante, e um pouco complicado, especificar exatamente quais partes do gráfico iremos git rev-listpercorrer: algumas em alguns commits .
Mas, alguém pode compartilhar uma ideia mais clara sobre o que
--note--allopções são destinados para?
Como observa VonC , o efeito aqui é listar commits que são novos para o repositório receptor. Isso depende do fato de que este git rev-listcomando está sendo executado em um gancho de pré-recepção . Geralmente, ele não faz nada de útil fora desse gancho específico. Portanto, como você pode ver, o ambiente de tempo de execução de um gancho, no Git, costuma ser um pouco especial. (Isso é verdade para mais do que apenas o gancho de pré-recebimento: é preciso pensar sobre o contexto de ativação de cada gancho.)
Mais sobre --not --all
A --allopção faz exatamente o que você citou na documentação:
Finja que todos os refs
refs/estão listados na linha de comando ...
Portanto, isso faz o equivalente a a git for-each-ref refs: ele faz um loop sobre cada referência. Isso inclui nomes do ramo ( masterou main, develop, feature/talle assim por diante, todos os quais estão realmente em refs/heads/), nomes de marca ( v1.2que é realmente refs/tags/v1.2), nomes remota de rastreamento ( origin/developque é realmente refs/remotes/origin/develop), refs substituição (em refs/replace/), o estoque ( refs/stash), bisection refs, Gerrit refs se você estiver usando Gerrit e assim por diante. Observe que ele não faz um loop sobre as entradas de reflog.
O --notprefixo é uma operação booleana simples. Na sintaxe do gitrevisions - veja a documentação do gitrevisions - podemos escrever coisas como develop, o quedevelop significa que eu digo a você para começar e trabalhar para trás e incluir esses commits , mas também coisas como ^develop, o quedevelop significa que eu digo a você para começar e trabalhar para trás e excluir esses commits . Então, se eu escrever:
git rev-list feature1 feature2 ^main
Estou pedindo ao Git para percorrer os commits acessíveis a partir dos commits identificados pelos nomes feature1e feature2, mas para excluir os commits alcançáveis dos commits identificados por main. Para (muito) mais sobre a ideia geral de alcançabilidade e gráfico de caminhada, consulte Think Like (a) Git .
O --notoperador efetivamente vira o ^em cada ref:
git rev-list --not feature1 feature2 ^main
é uma abreviatura, por assim dizer, para:
git rev-list ^feature1 ^feature2 main
Isso percorre a lista de commits acessíveis de main, mas exclui aqueles alcançáveis de feature1ou feature2.
Normalmente, todos os commits podem ser encontrados com--all
Se você estiver usando o Git da maneira normal do dia a dia, e não tiver um "HEAD separado" no momento - o modo HEAD separado não é exatamente anormal, mas não é a maneira normal de trabalhar - a --allopção de git rev-listdiz a ele para incluir todos os commits , porque todos os commits são acessíveis a partir de todas as referências. 1 Portanto, --not --allefetivamente exclui todos os commits. Portanto, adicionar --not --alla qualquer um git rev-listque de outra forma listaria alguns commits tem o efeito de inibir a lista. A saída está vazia: por que nos incomodamos?
Se você está no modo HEAD desanexado e fez vários novos commits - isso pode acontecer quando você está no meio de um rebase interativo ou conflitante, por exemplo - então git rev-list HEAD --not --alllistaria aqueles commits que são acessíveis, HEADmas não de qualquer nome de branch. Nesse rebase, por exemplo, seriam apenas aqueles commits que você copiou até agora.
Portanto, o modo "desanexado HEAD" seria um lugar onde git rev-list --not --allpoderia ser útil na linha de comando. Mas para a situação que você está examinando - um gancho de pré-recebimento - não estamos realmente na linha de comando.
Ganchos de pré-recebimento
Quando alguém costuma git pushenviar commits para seu próprio Git, seu Git:
- configura uma área de quarentena para conter quaisquer novos objetos (novos commits e blobs e assim por diante); 1
- negocia com o remetente para decidir o que o remetente deve enviar;
- recebe esses objetos; e
- obtém uma lista de solicitações de atualização de referência . Essas solicitações de atualização basicamente dizem que esse nome contém este ID de hash . 2
Antes de realmente fazer qualquer uma das atualizações solicitadas, seu Git:
- Alimenta a lista inteira para o gancho de pré-recebimento. Esse gancho pode dizer "não"; em caso afirmativo, todo o push, como um todo, é rejeitado.
- Se disser "ok", alimenta a lista, uma solicitação de cada vez, para o gancho de atualização. Quando aquele gancho diz "ok", faz a atualização. Se o gancho disser "não", seu Git rejeita a atualização, mas passa a examinar outras.
- Depois que todas as atualizações são aceitas ou rejeitadas na etapa 2, alimenta a lista de aceitos para o gancho pós-recebimento.
Os objetos necessários, que foram adicionados a alguma referência na etapa 2, são movidos da quarentena para o banco de dados de objetos do Git. Aqueles que foram rejeitados, não.
Agora, pense em um típico git push. Recebemos alguns novos commit (s) e uma solicitação: cria um novo nome de branchfeature/short , ou obtemos alguns novos commit (s) e uma solicitação: atualiza o nome do branch existente developpara incluir esses novos commits, junto com os antigos .
Na etapa 1 acima, temos um único novo hash ID. Executamos um loop para ler todos os nomes de ref e seus IDs de hash atuais e novos propostos, e o loop foi executado apenas uma vez, porque apenas um nome estava sendo git pushalterado. Esse hash ID refere-se ao novo commit ou commits, que serão adicionados a este branch existente ou serão a dica e outros commits exclusivos para o novo branch.
Agora gostaríamos de inspecionar esses commits, e não qualquer um dos commits existentes que podem ser acessados de qualquer branch existente. Para simplificar, ao invés $new_listda minha outra resposta, vamos supor que nós apenas um novo hash ID, $newe o antigo hash ID para o nome do branch,: $oldall-zeros se o branch for totalmente novo, ou algum commit existente válido se for um nome de filial existente.
Se os novos commits estiverem em um branch completamente novo, então:
git rev-list $new ^master ^develop ^feature/short ^feature/tall
cobriríamos eles, por exemplo, se soubéssemos que os únicos branches existentes são esses quatro (e que não há tags, etc. com que nos preocupar). Mas e se eles estiverem sendo adicionados, digamos , develop? Então nós gostaríamos de excluir os commits que estão atualmente em develop. Podemos usar o $oldhash ID para fazer isso:
git rev-list $new ^master ^$old ^feature/short ^feature/tall
Isso listaria novamente apenas os novos commits que quem quer que esteja executando git push origin developdeseja adicionar ao nosso develop.
Mas pense $old. Este é um ID de hash. Onde Git conseguiu isso? Git obteve esse hash ID do nome develop . Este é um gancho de pré-recepção ; o nome develop ainda não foi atualizado . Portanto, o nome develop é um nome para o ID de hash antigo $old. Que significa:
git rev-list $new ^master ^develop ^feature/short ^feature/tall
vai também fazer o trabalho.
Se git rev-list $newseguido por "and not all existing" fará o trabalho, então:
git rev-list $new --not --branches
fará o trabalho. Isso é quase o que temos aqui.
O bug de apenas usar --branchesé que ele não recebe nenhuma tag ou outras referências. Poderíamos usar --not --branches --tagsmas --not --allé mais curto e também recebe todos os outros refs.
Então é daí que --not --allvem: depende do caso especial de um gancho de pré-recepção. Listamos os novos hash IDs, conforme proposto por quem está executando um git push, que nosso Git nos passou como uma lista de linhas. Temos git rev-listandar o gráfico proposto-a-ser-atualizados cometer, olhando para as novas submissões na área de quarentena, mas excluindo todos os commits que já estão em nosso repositório. O comando rev-list produz esses IDs de hash, um por linha, que lemos em um loop de shell e fazemos o que quisermos para inspecionar cada confirmação.
1 A área de quarentena era nova no Git 2.11. Antes disso, novos objetos podem permanecer no repositório por um tempo, mesmo se o push for rejeitado. A área de quarentena não é realmente um grande problema para a maioria das pessoas, mas para grandes servidores como o GitHub, pode economizar muito espaço em disco.
2 A solicitação pode ser forçada ou não forçada e, se forçada, pode ser forçada ou não. Essas informações não estão disponíveis no gancho de pré-recebimento (nem no gancho de atualização), que é, hum, vamos apenas dizer que não é tão bom , mas há problemas de compatibilidade ao adicioná-lo. É tudo habitável, principalmente, no entanto. O gancho pode dizer se é criar um novo ref ou excluir a solicitação de ref existente, porque se for assim, um dos dois IDs de hash - antigo ou novo - será o "hash nulo" de todos os zeros (que é reservado; nenhum ID de hash é permitido para serem todos zeros).
Isso significa:
- Listar commits que podem ser acessados seguindo os links pai dos commits dados, aqui
$new_list, os novos, modificados ou deletados - mas exclua commits que são alcançáveis daquele (s) dado (s) com um
^na frente deles, aqui " all ", isto é, todos os commits HEADS, ou commits marcados.
Isso limita a lista de rev apenas aos novos commits recebidos, e não a todos os commits (recebidos e já presentes no repositório de recebimento)