Principais novidades do Git 2.49

O projeto open source Git acaba de lançar o Git 2.49, trazendo consigo diversas novidades e correções de bugs provenientes de mais de 89 colaboradores, sendo 24 deles estreantes. Esta atualização promete otimizar o desempenho e a experiência do usuário, consolidando o Git como uma ferramenta essencial para desenvolvedores. Vamos explorar os destaques desta versão e entender como ela pode facilitar o seu dia a dia.

## Empacotamento Aprimorado com Name Hash v2

Já falamos sobre o modelo de armazenamento de objetos do Git, onde os objetos podem ser escritos individualmente (objetos “soltos”) ou agrupados em packfiles. O Git utiliza packfiles em diversas funções, incluindo armazenamento local (ao repactar ou executar o GC do seu repositório), bem como ao enviar dados para ou de outro repositório Git (como fetching, clonagem ou push).

Armazenar objetos juntos em packfiles oferece algumas vantagens em relação ao armazenamento individual como objetos soltos. Uma delas é que as buscas de objetos podem ser realizadas muito mais rapidamente no armazenamento em pack. Ao procurar um objeto solto, o Git precisa fazer múltiplas chamadas de sistema para encontrar o objeto, abri-lo, lê-lo e fechá-lo.

Essas chamadas de sistema podem ser agilizadas usando o cache de bloco do sistema operacional, mas como os objetos são procurados por um SHA-1 (ou SHA-256) de seus conteúdos, esse acesso pseudo-aleatório não é muito eficiente para o cache.

O mais interessante é que, como os objetos soltos são armazenados individualmente, só podemos compactar seus conteúdos de forma isolada e não podemos armazenar objetos como deltas de outros objetos semelhantes que já existem no seu repositório. Por exemplo, imagine que você está fazendo uma série de pequenas alterações em um blob grande no seu repositório.

Quando esses objetos são escritos inicialmente, cada um é armazenado individualmente e compactado com zlib. No entanto, se a maior parte do conteúdo do arquivo permanecer inalterada entre os pares de edições, o Git pode compactar ainda mais esses objetos, armazenando as versões sucessivas como deltas das anteriores. Isso permite que o Git armazene as alterações feitas em um objeto (em relação a outro objeto) em vez de várias cópias de blobs quase idênticos.

### Otimização do Empacotamento no Git 2.49

Mas como o Git descobre quais pares de objetos são bons candidatos para armazenar como pares de base delta? Uma forma útil é comparar objetos que aparecem em caminhos semelhantes. O Git faz isso computando o que chama de “name hash“, que é efetivamente um hash numérico classificável que pondera mais fortemente os 16 caracteres finais não-espaço em branco em um caminho de arquivo.

Essa função, criada por Linus em 2006, é excelente para agrupar funções com extensões semelhantes (todas terminando em .c, .h, etc.) ou arquivos que foram movidos de um diretório para outro (a/foo.txt para b/foo.txt).

No entanto, a implementação atual do name-hash pode levar a uma compactação ruim quando há muitos arquivos com o mesmo nome base, mas conteúdos muito diferentes, como ter muitos arquivos CHANGELOG.md para diferentes subsistemas armazenados juntos no seu repositório. O Git 2.49 introduz uma nova variante da função hash que leva mais em conta a estrutura do diretório ao computar seu hash.

Entre outras alterações, cada camada da hierarquia de diretórios recebe seu próprio hash, que é deslocado para baixo e então XORed no hash geral. Isso cria uma função hash que é mais sensível a todo o caminho, não apenas aos 16 caracteres finais.

Isso pode levar a melhorias significativas tanto no desempenho do empacotamento quanto no tamanho geral do pack resultante. Por exemplo, usar a nova função hash conseguiu melhorar o tempo necessário para repactar microsoft/fluentui de ~96 segundos para ~34 segundos, e reduzir o tamanho do pack resultante de 439 MiB para apenas 160 MiB.

Embora esse recurso ainda não seja compatível com o recurso de bitmaps de acessibilidade do Git, você pode experimentá-lo usando a nova flag –name-hash-version do git repack ou do git pack-objects através da versão mais recente.

## Preenchimento de Blobs Históricos em Clones Parciais

Já se deparou com a seguinte saída ao trabalhar em um clone parcial?

“`
$ git blame README.md
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (1/1), 1.64 KiB | 8.10 MiB/s, done.
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (1/1), 1.64 KiB | 7.30 MiB/s, done.
[…]
“`

Para entender o que aconteceu, vamos analisar um cenário de exemplo: imagine que você está trabalhando em um clone parcial que você clonou com –filter=blob:none. Nesse caso, seu repositório terá todas as suas árvores, commit e objetos de tag anotada, mas apenas o conjunto de blobs que são imediatamente acessíveis a partir de HEAD. Em outras palavras, seu clone local tem apenas o conjunto de blobs necessários para preencher um checkout completo na revisão mais recente, e carregar quaisquer blobs históricos irá falhar em quaisquer objetos ausentes de onde você clonou seu repositório.

No exemplo acima, solicitamos um blame do arquivo no caminho README.md. No entanto, para construir esse blame, precisamos ver todas as versões históricas do arquivo para computar o diff em cada camada para descobrir se uma revisão modificou ou não uma determinada linha. Mas aqui vemos o Git carregando cada versão histórica do objeto uma por uma, levando a armazenamento inchado e desempenho ruim.

### A Nova Ferramenta: Git Backfill

O Git 2.49 introduz uma nova ferramenta, git backfill, que pode preencher quaisquer blobs históricos ausentes de um clone –filter=blob:none em um pequeno número de lotes. Essas solicitações usam a nova API de caminhada de caminho (também introduzida no Git 2.49) para agrupar objetos que aparecem no mesmo caminho, resultando em uma compactação delta muito melhor nos packfiles enviados de volta do servidor.

Como essas solicitações são enviadas em lotes em vez de uma por uma, podemos facilmente preencher todos os blobs ausentes em apenas alguns packs em vez de um pack por blob.

Depois de executar git backfill no exemplo acima, nossa experiência se parece mais com:

“`
$ git clone –sparse –filter=blob:none git@github.com:git/git.git[…] # downloads historical commits/trees/tags
$ cd git
$ git sparse-checkout add builtin
[…] # downloads current contents of builtin/
$ git backfill –sparse
[…] # backfills historical contents of builtin/
$ git blame — builtin/backfill.c
85127bcdeab (Derrick Stolee 2025-02-03 17:11:07 +0000 1) /* We need this macro to access core_apply_sparse_checkout */
85127bcdeab (Derrick Stolee 2025-02-03 17:11:07 +0000 2) #define USE_THE_REPOSITORY_VARIABLE
85127bcdeab (Derrick Stolee 2025-02-03 17:11:07 +0000 3)
[…]
“`

No entanto, executar git backfill imediatamente após clonar um repositório com –filter=blob:none não traz muito benefício, já que teria sido mais conveniente simplesmente clonar o repositório sem um filtro de objeto habilitado em primeiro lugar. Ao usar a opção –sparse do comando backfill (o padrão sempre que o recurso de sparse checkout está habilitado no seu repositório), o Git só baixará os blobs que aparecem dentro do seu sparse checkout, evitando objetos que você não faria checkout de qualquer maneira.

Para experimentar, execute git backfill em qualquer clone –filter=blob:none de um repositório usando o Git 2.49 hoje mesmo!


Além das melhorias mencionadas, o Git 2.49 traz outras novidades importantes:

* O Git utiliza compactação zlib ao escrever objetos soltos ou objetos individuais dentro de packs. O zlib-ng, um fork que mescla muitas otimizações e remove códigos obsoletos, agora pode ser usado para construir o Git, oferecendo um aumento de velocidade de cerca de 25%.
* Esta versão marca um marco importante com a inclusão de código Rust no projeto Git. As crates libgit-sys e libgit são wrappers de baixo e alto nível em torno de uma pequena parte do código da biblioteca do Git.
* O esforço contínuo para afastar-se de variáveis globais como the_repository continua, e muitos mais comandos nesta versão usam o repository fornecido em vez do global.
* A opção –expire-to em git repack controla o comportamento de objetos inacessíveis que foram removidos do repositório. No Git 2.49, você pode experimentar esse comportamento via git gc –expire-to.
* A funcionalidade de correção automática do Git, help.autocorrect, foi atualizada para interpretar “1” como outros comandos booleanos, e números positivos maiores que 1 como antes.
* O Git 2.49 introduz uma opção –revision que busca o histórico até a revisão especificada, independentemente de haver ou não um branch ou tag apontando para ela.
* O Git está removendo o suporte para convenções antigas de configuração de remotos baseadas em arquivos em $GIT_DIR/branches e $GIT_DIR/remote, que serão removidas no Git 3.0.
* O projeto Git teve dois estagiários do Outreachy que concluíram seus projetos: Usman Akinyemi adicionou suporte para incluir informações uname no user agent do Git ao fazer solicitações HTTP, e Seyi Kuforiji converteu mais testes de unidade para usar o Clar testing framework.

Para mais detalhes, confira as notas de lançamento do Git 2.49 no repositório Git.

Primeira: Este conteúdo foi auxiliado por Inteligência Artificiado, mas escrito e revisado por um humano.

Segunda: Via The GitHub Blog

Leave a Comment

Exit mobile version