Novidades do Git 2.37

Tim Petterson
Tim Petterson
Voltar para a lista

Após o lançamento do Git 2.0.0 há dois meses e meio, a gente recebeu uma nova versão secundária do Git, 2.1.0, com uma série de novos recursos interessantes!

As notas de versão completas estão disponíveis aqui, mas podem ser um pouco concisas se você não estiver envolvido a fundo com a comunidade do Git. Este blog é meu próprio comentário sobre alguns aspectos da versão que nos deixaram entusiasmados na Atlassian.

Melhores padrões de paginador

As citações neste artigo foram retiradas diretamente das notas de versão, com meu próprio comentário abaixo.

Desde o início do Git, demos ao ambiente LESS um valor padrão "FRSX" quando geramos "less" como o paginador. "S" (cortar linhas longas em vez de embrulhar) foi removido deste conjunto padrão de opções, porque é mais ou menos uma questão de gosto pessoal, ao contrário dos outros, que têm boas justificativas (ou seja, "R" é muito justificado porque muitos tipos de saída que produzimos são coloridos e "FX" é justificado porque a saída que produzimos é no geral mais curta do que uma página). Se você ainda não substituiu os padrões do paginador do git, essa alteração significa que a saída paginada dos comandos git vai ser encapsulada em vez de ser truncada na largura do terminal. Aqui está um exemplo com o git 2.1.0 (embrulhado) à esquerda e 2.0.3 (truncado) à direita:

Estilos de paginador no git 2.1.0 vs. git 2.0.3

É provável que a saída de log só vá ser afetada se você usar um terminal estreito ou tiver longas linhas ininterruptas nas mensagens de commit. A sabedoria geral do Git é manter as mensagens de commit com no máximo 72 caracteres de largura, mas se o empacotamento incomodar, você pode desabilitá-lo restaurando o comportamento original com:

$ git config core.pager "less -S"

É óbvio que, o paginador também é usado para exibir outras saídas, como git blame, que pode ter linhas muito longas dependendo do tamanho do nome do autor e do estilo de codificação. O 2.1.0 notas de versão também mostram que você pode habilitar a marcação -S apenas para o paginador de blame com:

$ git config pager.blame "less -S"

Se você está curioso sobre as opções de less padrão que o git ainda usa:

  • -F faz o less sair se houver menos de uma página de saída,
  • -R garante que apenas as sequências de escape de cores ANSI sejam emitidas em formato bruto (para que as cores do console git funcionem) e
  • -X impede que a tela seja limpa quando o less inicializa (de novo útil para registros com menos de uma página).

Melhor conclusão do bash

O script de conclusão do bash (em contrib/) foi atualizado para lidar melhor com aliases que definem uma sequência complexa de comandos, o que é superlegal! Sou um grande fã de aliases Git personalizados. A capacidade de conectar a conclusão bash do git em meus aliases complexos os torna muito mais poderosos e convenientes de usar na linha de comando. Por exemplo, eu tenho um alias definido que libera chaves do item no estilo Jira (por exemplo. STASH-123) do log:

issues = !sh -c 'git log --oneline $@ | egrep -o [A-Z]+-[0-9]+ | sort | uniq' -

Todos os argumentos são passados para o comando git log, para que você possa restringir o intervalo de commits para recuperar chaves do item. Por exemplo, git issues -n 1 vai mostrar a chave do item associada ao commit na ponta do meu branch. A partir de 2.1.0, A conclusão do bash do git foi aprimorada para permitir que eu conclua o alias os itens do git como se fosse o comando git log.

No git 2.0.3, digitar git issues m retornaria ao comportamento padrão de conclusão do bash de listar arquivos começando com m no diretório atual. No git 2.1.0, ele é concluído da forma certa para main, pois é assim que o comando git log se comportaria. Você também pode propagar a conclusão do bash prefixando o alias com um comando nulo começando com um ':'. É um recurso útil se o comando git que você quer concluir não for o primeiro comando no alias. Por exemplo, o alias:

issues = "!f() { echo 'Printing issue keys'; git log --oneline $@ | egrep -o [A-Z]+-[0-9]+ | sort | uniq; }; f"

não funciona bem com a conclusão porque o git não reconhece o comando echo como um destino de conclusão. Mas se você prefixar com ': git log ;', a conclusão vai funcionar corretamente de novo:

issues = "!f() { : git log ; echo 'Printing issue keys'; git log --oneline $@ | egrep -o [A-Z]+-[0-9]+ | sort | uniq; }; f"

Essa é uma grande vantagem de usabilidade se você gosta de criar scripts de aliases git complexos! Lembre-se de que essas informações estão em contrib/ e não fazem parte do core git, então não se esqueça de atualizar o perfil bash para apontar para a nova versão do contrib/completion/git-completion.bash se você precisar.

approxidate vem para o commit do git

A opção "git commit ‐‐date=" aprendeu mais formatos datação, incluindo "‐‐date=now". A marcação --date do commit do Git agora retorna para o incrível (e um pouco excêntrico) analisador approxidate do Git quando o primo mais rígido parse_date() não puder analisar uma cadeia de dados de data particular. approxidate pode lidar com coisas óbvias como --date = now e também permite alguns formatos um pouco mais esotéricos como --date="midnight the 12th of october, anno domini 1979" ou --date=teatime. Alex Peattie tem uma excelente publicação no blog sobre o incrível manuseio de datas do git, se você quiser saber mais.

Caminhos melhores com grep.fullname

O "git grep" aprendeu a variável de configuração grep.fullname para forçar "‐‐full-name" a ser o padrão, o que pode causar regressões para usuários com script que não esperam esse novo comportamento. Mas quero poupar você da invocação de man git-grep: --full-name.

Quando executado a partir de um subdiretório, o comando costuma gerar caminhos relativos ao diretório atual. Essa opção força a saída dos caminhos em relação ao diretório superior do projeto. Beleza! Esse é um bom padrão para o meu fluxo de trabalho, que em geral envolve a execução do git grep para encontrar um caminho de arquivo para copiar e colar em um arquivo XML em algum lugar (essa informação pode trair o fato de eu ser desenvolvedor Java). Se for útil para você também, basta executar:

$ git config --global grep.fullname true

para habilitá-lo na configuração.

A marcação --global aplica a configuração ao meu $HOME/.gitconfig então ele se torna o comportamento padrão para todos os repositórios git no meu sistema. Ele sempre pode ser substituído no nível do repositório, se necessário.

git replace ficou mais inteligente

Espere! Volte um pouco. O que essa coisa do git replace fez em primeiro lugar?

Em poucas palavras, git replace permite que você reescreva certos objetos no repositório do Git sem alterar a árvore correspondente ou fazer o commit de SHAs. Se esta é a primeira vez que você ouve falar do git replace e está familiarizado com o modelo de dados Git, pode soar como um sacrilégio! Com certeza foi assim para mim. Eu tenho outro blog em andamento para quando e por que você pode usar esse recurso. Se você quiser saber mais nesse meio tempo, este artigo é muito melhor do que a página de manual, que é um pouco escassa em casos de uso.

De qualquer forma, como o git replace ficou mais inteligente?

"O git replace" aprendeu o subcomando "‐‐edit" para criar uma substituição editando um objeto existente. A opção --edit permite que você copie e substitua com conveniência um objeto específico, despejando o conteúdo em um arquivo temporário e iniciando seu editor favorito. Para substituir o commit na ponta do main, você pode apenas executar:

 $ git replace --edit main

Ou para editar o blob que o commit de ponta do main considera como jira-components/pom.xml, você pode executar:

 $ git replace --edit main:jira-components/pom.xml

Você deveria fazer esse procedimento? É provável que não :). Na maioria dos casos, você deve usar git rebase para reescrever objetos, pois ele reescreve adequadamente os SHAs de commit e mantém um histórico íntegro.

O "git replace" aprendeu uma opção "‐‐graft" para reescrever os pais de um commit. A opção --graft é um atalho para substituir um commit por um idêntico, exceto com pais diferentes. Essa é uma maneira conveniente de obter um dos casos de uso um pouco mais íntegros para o git replace, encurtando o histórico do git. Para substituir o pai da ponta do meu branch principal, eu poderia apenas executar:

 $ git replace main --graft [new parent]..

Ou para cortar meu histórico em um ponto específico, posso deixar um commit órfão omitindo os novos pais por completo:

 $ git replace main --graft

De novo, você não deveria fazer esse processo sem um bom motivo. O uso criterioso do git rebase no geral é a maneira preferida de reescrever o histórico.

Ordenação sensata de tags com tag.sort

"git tag" aprendeu a prestar atenção em "tag.sort" configuração, para ser usada como a ordem de classificação padrão quando nenhuma opção ‐-sort= for oferecida. Essa é uma ótima notícia se você estiver usando números de versão nos nomes de tags, o que imagino que 99,9% de vocês estejam fazendo. Depois de lançar uma versão que contém um segmento com mais de um dígito de comprimento (por exemplo, uma v10 ou uma v1.10), a ordem lexicográfica padrão do git não é mais suficiente. Veja a ordem padrão de tags do repositório do Git do Atlassian Stash (agora chamado de Bitbucket Server), por exemplo:

src/stash $ git tag -l *.*.0
..
stash-parent-2.0.0
stash-parent-2.1.0
stash-parent-2.10.0
stash-parent-2.11.0
stash-parent-2.12.0
stash-parent-2.2.0
stash-parent-2.3.0
stash-parent-2.4.0
stash-parent-2.5.0
stash-parent-2.6.0
stash-parent-2.7.0
stash-parent-2.8.0
stash-parent-2.9.0
stash-parent-3.0.0
..

Não é bom! 2.10.0 vem cronologicamente após 2.3.0, portanto, essa ordem de classificação de tags padrão está incorreta. Desde o git 2.0.0, conseguimos usar a marcação --sort para fazer a classificação correta das versões numéricas:

src/stash $ git tag --sort="version:refname" -l *.*.0
..
stash-parent-2.0.0
stash-parent-2.1.0
stash-parent-2.2.0
stash-parent-2.3.0
stash-parent-2.4.0
stash-parent-2.5.0
stash-parent-2.6.0
stash-parent-2.7.0
stash-parent-2.8.0
stash-parent-2.9.0
stash-parent-2.10.0
stash-parent-2.11.0
stash-parent-2.12.0
stash-parent-3.0.0
..

Muito melhor. Com o git 2.1.0, você pode tornar esse pedido o padrão executando:

$ git config --global tag.sort version:refname

A propósito, a marcação -l usada nos exemplos de git tag acima restringe os nomes das tags exibidos a um padrão específico. -l *.*.0 é usado para mostrar apenas versões principais e secundárias do Stash (agora chamado de Bitbucket Server).

Verificação mais simples de commits assinados

Foi adicionado um novo comando "git verify-commit" para verificar assinaturas GPG em commits assinados de um jeito semelhante ao jeito com que o "git verify-tag" é usado para verificar tags assinadas. Se você estiver usando a assinatura da commit para autenticar a autoria dos commits, o comando verify-commit torna a verificação de assinaturas muito mais fácil. Em vez de escrever seu próprio script para analisar a saída do git log --show-signature, você pode apenas passar git verify-commit a um conjunto de commits para verificar as assinaturas. Mas é provável que você não esteja usando commits assinados no momento (o que não usamos na Atlassian), pois eles introduzem algumas despesas gerais de gerenciamento de chaves e aborrecimento do desenvolvedor. As tags assinadas são em geral aceitas como um melhor equilíbrio entre conveniência e segurança para a maioria dos projetos. Se você está curioso sobre por que um projeto pode optar por usar commits assinados, Mike Gerwitz conta uma ótima história de terror do git em um cenário hipotético em que elas seriam muito úteis. Portanto, se você trabalha em um setor que trabalha muito com informações confidenciais, pode considerar a implementação deles como parte do fluxo de trabalho.

Melhorias de desempenho

O git 2.1.0 também veio com algumas boas melhorias de desempenho.

Um formato experimental para usar dois arquivos (o arquivo de base e as alterações incrementais em relação a ele) para representar o índice foi introduzido; ele pode reduzir o custo de E/S de reescrever um índice grande quando apenas uma pequena parte da árvore de trabalho é alterada. Tradução: se você tende a ter grandes commits que alteram muitos arquivos, executar o git add pode ter ficado mais rápido. O git add já é muito rápido para qualquer caso incremental que eu tentei no local, então não encontrei uma grande diferença no desempenho entre as versões ao testar. É interessante que, a adição inicial parece ter acelerado um pouco para grandes conjuntos de arquivos. Durante um dos meus testes de desempenho rápidos e sujos, tentei preparar todas as alterações na base de código do Jira entre o Jira 5 e o Jira 6.

$ git checkout jira-v6.0 $ git reset jira-v5.0 $ time git add --all

No git 2.0.3, eu tive uma média de 2,44 segundos. No git 2.1.0, por outro lado, a média foi de 2,18 segundos — uma economia de tempo de mais de 10%! Observe que esses testes não foram exatamente feitos em condições de laboratório, e estamos economizando cerca de um quarto de segundo ao adicionar mais de 14.500 arquivos ao índice de uma só vez, então você pode não ver um grande impacto durante o uso normal do Git. Você pode ler mais sobre o novo formato de índice dividido na documentação do formato de índice.

A variável de configuração "core.preloadindex" é ativada por padrão, permitindo que as plataformas modernas aproveitem os vários núcleos. Legal! Eu não tinha habilitado esse recurso antes, mas depois de atualizar para 2.1.0 a diferença de desempenho é perceptível. Como outro teste rápido e sujo, tentei cronometrar execuções do status do git em relação às mudanças encenadas entre o Jira 5 e o Jira 6 que usei acima. No git 2.0.3, eu tive uma média de 4,94 segundos com mais de 14.500 arquivos preparados, e, no git 2.1.0, uma média de 3,99 — uma economia impressionante de cerca de 19%. Esse resultado é especialmente bom se você usar um prompt de shell personalizado que verifica se a cópia de trabalho tem alterações sem commit toda vez que ela retorna. O Bash com certeza pareceu um pouco mais ágil para mim para índices muito grandes. O "git blame" foi muito otimizado reorganizando a estrutura de dados que é usada para acompanhar o trabalho a ser feito. git blame agora é mais rápido em descobrir quem quebrou algo contribuiu com uma determinada linha de código :). Estou muito feliz com essa melhoria em particular, pois significa que o git-guilt (uma pequena ferramenta que escrevi para investigar como a guilt muda entre os commits) também pode esperar um bom aumento de desempenho, pois depende muito da saída do guilt para funcionar.

Como outro teste rápido e sujo, verifiquei o tempo que levou para calcular a transferência de culpa no repositório de origem git entre 2.0.0 e 2.1.0. Assim o git-guilt bifurca 886 comandos git blame em arquivos de tamanhos variados que mudaram entre git 2.0.0 e git 2.1.0.

$ git guilt v2.0.0 v2.1.0

O git 2.0.3 teve média de 72,1 segundos, e o git 2.1.0 teve uma média de 66,7 segundos, uma melhoria de 7,5%! Você pode ver a transferência real do git-guilt se estiver curioso (Karsten Blees acabou de superar Junio C Hamano por 66 LOC).

Esses testes de desempenho são todos um pouco ad-hoc, mas estamos no processo de atualizar o Bitbucket Cloud para o git 2.1.0. Vamos monitorar os recursos antes e depois do upgrade para ver como a nova versão afeta o desempenho de certas operações do Git, em particular blame e diff. Vou dar um retorno em algumas semanas para que você saiba como as coisas vão.

E ainda tem mais!

Há algumas outras coisas excelentes no git 2.1.0 que eu não abordei neste blog, então confira as notas de versão completas se você estiver interessado. Muitos elogios são devidos à equipe do Git por outra versão de qualidade e repleta de recursos. Se você estiver interessado em ler mais dicas e petiscos do mundo do git, fique à vontade para me seguir (@kannonboy) e o Atlassian Dev Tools (@atldevtools) no Twitter.

Pronto(a) para aprender Git?

Tente este tutorial interativo.

Comece agora mesmo