git gc – agressivo vs git repack

Eu estou procurando maneiras de reduzir o tamanho de um repository git . Pesquisando me leva a git gc --aggressive maioria das vezes. Eu também li que esta não é a abordagem preferida.

Por quê? O que devo saber se estou executando gc --aggressive ?

git repack -a -d --depth=250 --window=250 é recomendado em gc --aggressive . Por quê? Como o repack reduz o tamanho de um repository? Além disso, não estou bem claro sobre as bandeiras – --depth e – --depth .

O que devo escolher entre gc e repack ? Quando devo usar gc e repack ?

Hoje em dia não há diferença: git gc --aggressive opera de acordo com a sugestão feita por Linus em 2007; ver abaixo. A partir da versão 2.11 (Q4 2016), o git padroniza para uma profundidade de 50. Uma janela de tamanho 250 é boa porque varre uma seção maior de cada object, mas a profundidade em 250 é ruim porque faz com que todas as correntes se refiram a antigas objects, o que retarda todas as futuras operações do git para uso de disco marginalmente menor.


Contexto histórico

Linus sugeriu (veja abaixo o post completo da lista de discussão) usando git gc --aggressive somente quando você tem, em suas palavras, “um pacote realmente ruim” ou “deltas terrivelmente ruins”, no entanto “quase sempre, em outros casos, é realmente uma coisa muito ruim de se fazer. ”O resultado pode até deixar seu repository em condições piores do que quando você começou!

O comando que ele sugere para fazer isso corretamente depois de ter importado “uma história longa e envolvida” é

 git repack -a -d -f --depth=250 --window=250 

Mas isso pressupõe que você já tenha removido a gosma indesejada do histórico do seu repository e que você tenha seguido a lista de verificação para encolher um repository encontrado na documentação da git filter-branch .

git-filter-branch pode ser usado para se livrar de um subconjunto de arquivos, geralmente com alguma combinação de --index-filter e --subdirectory-filter . As pessoas esperam que o repository resultante seja menor que o original, mas você precisa de mais algumas etapas para diminuí-lo, porque o Git tenta não perder seus objects até que você os informe. Primeiro, certifique-se de que:

  • Você realmente removeu todas as variantes de um nome de arquivo, se um blob foi movido ao longo de sua vida útil. git log --name-only --follow --all -- filename pode ajudá-lo a encontrar renomeações.

  • Você realmente filtrou todas as referências: use --tag-name-filter cat -- --all ao chamar git filter-branch .

Então, existem duas maneiras de obter um repository menor. Uma maneira mais segura é clonar, que mantém seu original intacto.

  • Clone-o com git clone file:///path/to/repo . O clone não terá os objects removidos. Veja git-clone. (Note que a clonagem com um caminho simples apenas hardlinks tudo!)

Se você realmente não quiser cloná-lo, por qualquer motivo, verifique os seguintes pontos (nesta ordem). Esta é uma abordagem muito destrutiva, então faça um backup ou volte a cloná-lo. Você foi avisado.

  • Remova os refs originais copiados por git-filter-branch: digamos

     git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d 
  • Expirar todos os reflogs com git reflog expire --expire=now --all .

  • Garbage colete todos os objects não referenciados com git gc --prune=now (ou se o seu git gc não for novo o suficiente para suportar argumentos para --prune , use git repack -ad; git prune ).


 Date: Wed, 5 Dec 2007 22:09:12 -0800 (PST) From: Linus Torvalds  To: Daniel Berlin  cc: David Miller , ismail at pardus dot org dot tr, gcc at gcc dot gnu dot org, git at vger dot kernel dot org Subject: Re: Git and GCC In-Reply-To: <4aca3dc20712052111o730f6fb6h7a329ee811a70f28@mail.gmail.com> Message-ID:  References: <4aca3dc20712051947t5fbbb383ua1727c652eb25d7e@mail.gmail.com> <20071205.202047.58135920.davem@davemloft.net> <4aca3dc20712052032n521c344cla07a5df1f2c26cb8@mail.gmail.com> <20071205.204848.227521641.davem@davemloft.net> <4aca3dc20712052111o730f6fb6h7a329ee811a70f28@mail.gmail.com> 

No dia 6 de dezembro de 2007, Daniel Berlin escreveu:

Na verdade, git-gc --aggressive faz essa coisa estúpida para embalar arquivos às vezes, independentemente de você ter convertido de um repository SVN ou não.

Absolutamente. git --aggressive é principalmente idiota. É realmente útil apenas para o caso de “Eu sei que tenho uma matilha muito ruim e quero jogar fora todas as decisões ruins de empacotamento que fiz”.

Para explicar isso, vale a pena explicar (você provavelmente está ciente disso, mas deixe-me ir através do básico de qualquer maneira) como funciona o git delta-chains, e como eles são tão diferentes da maioria dos outros sistemas.

Em outros SCMs, uma cadeia delta é geralmente fixa. Pode ser “para frente” ou “para trás”, e pode evoluir um pouco enquanto você trabalha com o repository, mas geralmente é uma cadeia de mudanças em um único arquivo representado como algum tipo de entidade SCM única. No CVS, é obviamente o arquivo *,v , e muitos outros sistemas fazem coisas parecidas.

O Git também faz cadeias delta, mas o faz muito mais “livremente”. Não existe uma entidade fixa. Deltas são gerados contra qualquer outra versão aleatória que git considere ser um bom candidato delta (com várias heurísticas razoavelmente bem-sucedidas), e não há regras rígidas de agrupamento.

Isso geralmente é uma coisa muito boa. É bom por várias razões conceituais ( isto é , git internamente nunca realmente precisa se preocupar com toda a cadeia de revisão – realmente não pensa em termos de deltas), mas também é ótimo porque se livrar das regras delta inflexíveis significa Esse git não tem nenhum problema em mesclar dois arquivos, por exemplo – simplesmente não há arquivos de revisão arbitrários *,v que tenham algum significado oculto.

Isso também significa que a escolha de deltas é uma questão muito mais aberta. Se você limitar a cadeia delta para apenas um arquivo, você realmente não tem muitas opções sobre o que fazer com os deltas, mas no git, pode ser um problema totalmente diferente.

E é aí que entra o --aggressive mal chamado --aggressive enquanto o git geralmente tenta reutilizar a informação delta (porque é uma boa idéia, e não desperdiça tempo da CPU re-encontrando todos os bons deltas que encontramos anteriormente) Às vezes, você quer dizer “vamos começar tudo de novo, com uma lousa em branco, e ignorar todas as informações delta anteriores e tentar gerar um novo conjunto de deltas”.

Então – --aggressive não é realmente ser agressivo, mas desperdiçar tempo de CPU refazendo uma decisão que já fizemos antes!

Às vezes isso é uma coisa boa. Algumas ferramentas de importação, em particular, podem gerar deltas terrivelmente ruins. Qualquer coisa que use o git fast-import , por exemplo, provavelmente não tem um ótimo layout delta, então vale a pena dizer “eu quero começar de uma forma limpa”.

Mas quase sempre, em outros casos, é realmente uma coisa muito ruim de se fazer. Vai desperdiçar o tempo de CPU, e especialmente se você realmente fez um bom trabalho no deltaing anterior, o resultado final não vai reutilizar todos os bons deltas que você já encontrou, então você vai acabar com um pior resultado final também!

Vou enviar um patch para o Junio ​​para remover apenas a documentação git gc --aggressive do git gc --aggressive . Pode ser útil, mas geralmente só é útil quando você realmente entende em um nível muito profundo o que está fazendo, e essa documentação não ajuda você a fazer isso.

Geralmente, fazendo incremental git gc é a abordagem correta, e melhor do que fazer git gc --aggressive . Vai reutilizar deltas antigos e, quando esses deltas antigos não puderem ser encontrados (a razão para fazer o GC incremental em primeiro lugar!), Ele criará novos deltas.

Por outro lado, é definitivamente verdade que uma “importação inicial de uma história longa e envolvida” é um ponto em que pode valer a pena gastar muito tempo encontrando os deltas realmente bons . Então, todo usuário que git gc --aggressive (contanto que não use o git gc --aggressive para desfazê-lo!) Terá a vantagem daquele evento único. Então, especialmente para grandes projetos com uma longa história, provavelmente vale a pena fazer algum trabalho extra, dizendo ao código de localização do delta para enlouquecer.

Então, o equivalente a git gc --aggressivegit gc --aggressive – mas feito corretamente – é fazer (durante a noite) algo como

 git repack -a -d --depth=250 --window=250 

onde essa profundidade é apenas sobre o quão profundas podem ser as cadeias delta (torná-las mais longas para a história antiga – vale a pena a sobrecarga de espaço), e a janela é sobre o tamanho de uma janela de object que queremos que cada candidato delta escaneie.

E aqui, você pode querer adicionar o sinalizador -f (que é o “soltar todos os deltas antigos”, já que agora você está realmente tentando se certificar de que este realmente encontre bons candidatos.

E então isso vai levar uma eternidade e um dia ( isto é , uma coisa do tipo “faça durante a noite”). Mas o resultado final é que todo mundo a jusante desse repository terá pacotes muito melhores, sem ter que gastar nenhum esforço por conta própria.

  Linus 

Quando devo usar o gc e o reempacotamento?

Como mencionei em ” A garbage collection do Git não parece funcionar totalmente “, um git gc --aggressive não é suficiente nem suficiente por conta própria.

A combinação mais eficaz seria adicionar o git repack , mas também git prune :

 git gc git repack -Ad # kills in-pack garbage git prune # kills loose garbage 

Nota: O Git 2.11 (Q4 2016) irá definir a profundidade agressiva padrão de gc para 50

Veja commit 07e7dbf (11 ago 2016) por Jeff King ( peff ) .
(Mesclado por Junio ​​C Hamano – gitster – em commit 0952ca8 , 21 set 2016)

gc : profundidade agressiva padrão para 50

git gc --aggressive ” usado para limitar o comprimento da cadeia delta para 250, que é muito profundo para ganhar economia de espaço adicional e é prejudicial para o desempenho em tempo de execução.
O limite foi reduzido para 50.

O resumo é: o padrão atual de 250 não economiza muito espaço e custa CPU. Não é uma boa troca.

O --aggressive--aggressive ” para git-gc faz três coisas:

  1. use ” -f ” para descartar deltas existentes e recompilar a partir do zero
  2. use “–window = 250” para procurar mais pelos deltas
  3. use “–depth = 250” para fazer cadeias delta mais longas

Itens (1) e (2) são bons jogos para um empacotamento “agressivo”.
Eles pedem ao repack para fazer mais trabalho de computação na esperança de obter um pacote melhor. Você paga os custos durante o reempacotamento e outras operações veem apenas o benefício.

Item (3) não é tão claro.
Permitir correntes mais longas significa menos restrições nos deltas, o que significa encontrar potencialmente melhores e poupar algum espaço.
Mas também significa que as operações que acessam os deltas precisam seguir cadeias mais longas, o que afeta seu desempenho.
Então, é uma troca, e não está claro que a troca seja boa.

(Ver compromisso para estudo )

Você pode ver que a economia de CPU para operações regulares melhora à medida que diminuímos a profundidade.
Mas também podemos ver que a economia de espaço não é tão grande quanto a profundidade aumenta. Economizar 5-10% entre 10 e 50 provavelmente vale a troca da CPU. Economizar 1% para ir de 50 a 100, ou outro 0,5% para ir de 100 a 250, provavelmente não é.


Falando em economia de CPU, o ” git repack ” aprendeu a aceitar a opção --threads= e a passou para os objects pack.

Veja commit 40bcf31 (26 abr 2017) de Junio ​​C Hamano ( gitster ) .
(Mesclado por Junio ​​C Hamano – gitster – em commit 31fb6f4 , 29 de maio de 2017)

repack: aceita --threads= e passa para pack-objects

Nós já fazemos isso para --window= e --depth= ; isso ajudará quando o usuário quiser forçar --threads=1 para testes reproduzíveis sem ser afetado pela corrida de vários threads.

O problema com o git gc --aggressive é que o nome e a documentação da opção são enganosos.

Como o próprio Linus explica nesta correspondência , o que o git gc --aggressive basicamente faz é isto:

Enquanto o git geralmente tenta reutilizar informações delta (porque é uma boa idéia, e não desperdiça tempo de CPU re-encontrando todos os bons deltas que encontramos anteriormente), às vezes você quer dizer “vamos começar tudo de novo, com um em branco, e ignore todas as informações delta anteriores, e tente gerar um novo conjunto de deltas “.

Normalmente não há necessidade de recalcular os deltas no git, já que o git determina esses deltas muito flexíveis. Só faz sentido se você sabe que tem deltas muito, muito ruins. Como Linus explica, principalmente as ferramentas que fazem uso do git fast-import enquadram nessa categoria.

Na maioria das vezes o git faz um trabalho muito bom em determinar deltas úteis e usar o git gc --aggressive vai deixar você com deltas que são potencialmente piores enquanto desperdiçam muito tempo de CPU.


Linus termina sua correspondência com a conclusão de que o git repack com uma grande --depth e --depth é a melhor escolha na maior parte do tempo; especialmente depois que você importou um projeto grande e quer ter certeza de que o git encontra bons deltas.

Então, o equivalente a git gc --aggressivegit gc --aggressive – mas feito corretamente – é fazer (durante a noite) algo como

git repack -a -d --depth=250 --window=250

onde essa profundidade é apenas sobre o quão profundas podem ser as cadeias delta (torná-las mais longas para a história antiga – vale a pena a sobrecarga de espaço), e a janela é sobre o tamanho de uma janela de object que queremos que cada candidato delta escaneie.

E aqui, você pode querer adicionar o sinalizador -f (que é o “drop all old deltas”, já que você está realmente tentando se certificar de que este realmente encontra bons candidatos.

Cuidado. Não execute o git gc --agressive com o repository que não está sincronizado com o remote se você não tiver backups.

Essa operação recria deltas do zero e pode levar à perda de dados se for interrompida normalmente.

Para o meu computador 8GB, o agressivo gc ficou sem memory no repository de 1Gb com 10k pequenos commits. Quando o assassino de OOM terminou o processo git – ele me deixou com repository quase vazio, apenas a tree de trabalho e alguns deltas sobreviveram.

Claro, não foi a única cópia do repository, então eu apenas recriou e puxei de remoto (busca não funcionou em repository quebrado e deadlocked em ‘resolvendo deltas’ passo algumas vezes eu tentei fazê-lo), mas se o seu repo é repo local de desenvolvedor único sem remotas – faça o backup primeiro.