git gc --aggressive vs git repack

91

Estou procurando maneiras de reduzir o tamanho de um gitrepositório. Pesquisando me leva agit gc --aggressive maioria das vezes. Também li que essa não é a abordagem preferida.

Por quê? o que devo saber se estou correndogc --aggressive ?

git repack -a -d --depth=250 --window=250é recomendado gc --aggressive. Por quê? Como repackreduz o tamanho de um repositório? Além disso, não estou muito certo sobre as bandeiras --depthe --window.

O que devo escolher entre gce repack? Quando devo usar gce repack?

Ajith R Nayak
fonte

Respostas:

78

Hoje em dia não tem diferença: git gc --aggressiveopera de acordo com a sugestão que Linus fez em 2007; ver abaixo. A partir da versão 2.11 (quarto trimestre de 2016), o padrão do git é uma profundidade de 50. Uma janela de tamanho 250 é boa porque verifica uma seção maior de cada objeto, mas a profundidade de 250 é ruim porque faz com que cada corrente se refira a um antigo muito profundo objetos, o que retarda todas as operações git futuras para uso de disco marginalmente menor.


Contexto histórico

Linus sugeriu (veja abaixo a postagem da lista de e-mails completa) usando git gc --aggressiveapenas quando você tiver, em suas palavras, "um pacote realmente ruim" ou "deltas realmente horrivelmente ruins", porém "quase sempre, em outros casos, é realmente um muito ruim coisa para fazer." O resultado pode até deixar seu repositório em piores condições do que quando você começou!

O comando que ele sugere para fazer isso corretamente após ter importado "uma longa e complicada história" é

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

Mas isso assume que você já removeu o lixo indesejado do histórico do seu repositório e que seguiu a lista de verificação para reduzir um repositório encontrado na git filter-branchdocumentação .

git-filter-branch pode ser usado para se livrar de um subconjunto de arquivos, geralmente com alguma combinação de --index-filtere --subdirectory-filter. As pessoas esperam que o repositório resultante seja menor do que o original, mas você precisa de mais alguns passos para torná-lo realmente menor, porque o Git se esforça para não perder seus objetos até que você mande. Primeiro, certifique-se de que:

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

  • Você realmente filtrou todos os refs: use --tag-name-filter cat -- --allao chamar git filter-branch.

Então, há duas maneiras de obter um repositório menor. Uma maneira mais segura é clonar, o que mantém seu original intacto.

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

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

  • Remova os refs originais apoiados por git-filter-branch: diga

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

  • Coleta de lixo todos os objetos não referenciados com git gc --prune=now(ou se seu git gcnão for novo o suficiente para suportar argumentos --prune, use em seu git repack -ad; git prunelugar).


Date: Wed, 5 Dec 2007 22:09:12 -0800 (PST)
From: Linus Torvalds <torvalds at linux-foundation dot org>
To: Daniel Berlin <dberlin at dberlin dot org>
cc: David Miller <davem at davemloft dot net>,
    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: <[email protected]>
Message-ID: <[email protected]>
References: <[email protected]>
            <[email protected]>
            <[email protected]>
            <[email protected]>
            <[email protected]>

Na quinta-feira, 6 de dezembro de 2007, Daniel Berlin escreveu:

Na verdade, acontece que git-gc --aggressiveisso faz essa coisa idiota para empacotar arquivos às vezes, independentemente de você ter convertido de um repositório SVN ou não.

Absolutamente. git --aggressiveé principalmente burro. É realmente útil apenas para o caso de "Eu sei que tenho uma embalagem muito ruim e quero jogar fora todas as decisões ruins de embalagem que tomei."

Para explicar isso, vale a pena explicar (você provavelmente está ciente disso, mas deixe-me passar pelo básico de qualquer maneira) como as cadeias delta git funcionam e como elas 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 conforme você trabalha com o repositório, mas geralmente é uma cadeia de alterações em um único arquivo representado como algum tipo de entidade SCM única. No CVS, é obviamente o *,varquivo, e muitos outros sistemas fazem coisas bastante semelhantes.

Git também faz cadeias delta, mas as faz muito mais "vagamente". Não há 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á absolutamente nenhuma regra rígida de agrupamento.

Em geral, isso é muito bom. É bom por várias razões conceituais ( ou seja , git internamente nunca realmente precisa se preocupar com toda a cadeia de revisão - ele realmente não pensa em termos de deltas), mas também é ótimo porque se livrar das regras inflexíveis de delta significa que o git não tem nenhum problema com a fusão de dois arquivos, por exemplo - simplesmente não existem *,v“arquivos de revisão” arbitrários 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 a apenas um arquivo, não terá muitas opções de escolha sobre o que fazer com os deltas, mas no git pode ser um problema totalmente diferente.

E é aí que --aggressiveentra o nome realmente ruim . Enquanto o git geralmente tenta reutilizar as informações delta (porque é uma boa ideia e não desperdiça tempo de CPU reencontrando todos os deltas bons que encontramos anteriormente), às vezes você quero dizer “vamos começar tudo de novo, com uma tela em branco, e ignorar todas as informações delta anteriores e tentar gerar um novo conjunto de deltas”.

Portanto, --aggressivenão se trata realmente de ser agressivo, mas de desperdiçar tempo de CPU refazendo uma decisão que já tomamos antes!

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

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

Vou enviar um patch para o Junio ​​apenas para remover a git gc --aggressive documentação. 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, fazer incrementais git gcé a abordagem certa e melhor do que fazer git gc --aggressive. Ele vai reutilizar deltas antigos e, quando esses deltas antigos não puderem ser encontrados (a razão para fazer GC incremental em primeiro lugar!), Ele criará novos.

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

Portanto, o equivalente a git gc --aggressive- mas feito corretamente - é fazer (durante a noite) algo como

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

onde aquela coisa de profundidade é apenas sobre quão profundas as cadeias delta podem ser (torná-las mais longas para a história antiga - vale a pena o espaço acima), e a coisa de janela é sobre o tamanho da janela de um objeto que queremos que cada candidato delta examine.

E aqui, você pode querer adicionar o -fsinalizador (que é “largar todos os deltas antigos”, já que agora você está realmente tentando ter certeza de que este realmente encontre bons candidatos.

E então vai levar uma eternidade e um dia ( ou seja , algo do tipo “faça durante a noite”). Mas o resultado final é que todos os downstream desse repositório receberão pacotes muito melhores, sem ter que gastar nenhum esforço com eles.

          Linus
Greg Bacon
fonte
2
Seu comentário sobre profundidade é um pouco confuso. No começo eu ia reclamar que você está totalmente errado, que agressivo pode acelerar muito um repositório git. Depois de fazer uma coleta de lixo agressiva, um ENORME repo levou cinco minutos para fazer um git status reduzido para segundos. Mas então percebi que você não quis dizer que o gc agressivo desacelerou o repo, mas apenas um tamanho de profundidade extremamente grande.
user6856
58

Quando devo usar o gc & repack?

Como mencionei em " A coleta de lixo do Git não parece funcionar totalmente ", a git gc --aggressivenão é suficiente ou mesmo suficiente por si só.
E, como explico abaixo , muitas vezes não é necessário.

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

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

Nota: Git 2.11 (Q4 2016) irá definir a gc aggressiveprofundidade padrão para 50

Veja o commit 07e7dbf (11 de agosto de 2016) de Jeff King ( peff) .
(Fundido por Junio ​​C Hamano - gitster- no commit 0952ca8 , 21 de setembro de 2016)

gc: profundidade agressiva padrão para 50

" git gc --aggressive" usado para limitar o comprimento da cadeia delta a 250, que é muito profundo para obter economia de espaço adicional e é prejudicial para o desempenho do 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 --aggressivesinalizador " " git-gcfaz três coisas:

  1. use " -f" para descartar deltas existentes e recomputar do zero
  2. use "--window = 250" para procurar deltas com mais atenção
  3. use "--depth = 250" para fazer cadeias delta mais longas

Os itens (1) e (2) são boas combinações para uma reembalagem "agressiva".
Eles pedem ao repack para fazer mais trabalho de computação na esperança de obter um pacote melhor. Você paga os custos durante a reembalagem, e outras operações vêem apenas o benefício.

O item (3) não é tão claro.
Permitir cadeias mais longas significa menos restrições aos deltas, o que significa potencialmente encontrar cadeias melhores e economizar espaço.
Mas também significa que as operações que acessam os deltas devem seguir cadeias mais longas, o que afeta seu desempenho.
Portanto, é uma troca e não está claro se a troca é mesmo boa.

(Veja commit 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 à medida que a profundidade aumenta. Economizar de 5 a 10% entre 10 e 50 provavelmente compensa a compensação de CPU. Economizar 1% para ir de 50 para 100 ou outro 0,5% para ir de 100 para 250 provavelmente não é.


Falando em economia de CPU, " git repack" aprendi a aceitar a --threads=<n>opção e passá-la para objetos de pacote.

Consulte o commit 40bcf31 (26 de abril de 2017) de Junio ​​C Hamano ( gitster) .
(Incorporado por Junio ​​C Hamano - gitster- no commit 31fb6f4 , 29 de maio de 2017)

reembalar: aceitar --threads=<n>e passá-lo parapack-objects

Já o fazemos para --window=<n>e --depth=<n>; isso ajudará quando o usuário quiser forçar --threads=1um teste reproduzível sem ser afetado pela corrida de vários threads.

VonC
fonte
3
Mencionei
1
Obrigado por esta atualização moderna! Todas as outras respostas aqui são antigas. Agora podemos ver que git gc --aggressivefoi consertado duas vezes: primeiro, para fazer o que Linus sugeriu em 2007 como um "método de embalagem melhor". E então no Git 2.11 para evitar a profundidade excessiva do objeto que Linus havia sugerido, mas que acabou sendo prejudicial (retarda todas as operações futuras do Git e não economiza nenhum espaço que valha a pena falar).
gw0
git gc, seguido por git repack -Ad e git prune aumenta o tamanho do meu repositório ... por quê?
devops
@devops Não tenho certeza: qual versão do Git você está usando? Você pode fazer uma nova pergunta para isso (com mais detalhes como o sistema operacional, o tamanho geral do seu repo, ...)
VonC
man git-repackdiz para -d: `Também execute git prune -pack para remover arquivos redundantes de objetos soltos`. Ou git prunetambém faz isso? man git-prunediz In most cases, users should run git gc, which calls git prune., então qual é a utilidade depois git gc? Não seria melhor ou suficiente usar apenas git repack -Ad && git gc?
Jakob de
14

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

Como o próprio Linus explica neste e-mail , o que git gc --aggressivebasicamente faz é isso:

Embora o git geralmente tente reutilizar as informações delta (porque é uma boa ideia e não desperdiça tempo de CPU reencontrando todos os bons deltas que encontramos anteriormente), às vezes você quer dizer "vamos começar tudo de novo, com um quadro em branco e ignorar todas as informações anteriores do delta e tentar gerar um novo conjunto de deltas ".

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

Na maioria das vezes, o git faz um bom trabalho na determinação de deltas úteis e o uso git gc --aggressivedeixará você com deltas que são potencialmente ainda piores, ao mesmo tempo que desperdiça muito tempo de CPU.


Linus termina sua correspondência com a conclusão de que git repackcom uma grande --depthe --windowé a melhor escolha na maioria das vezes; especialmente depois que você importou um grande projeto e deseja ter certeza de que o git encontra bons deltas.

Portanto, o equivalente a git gc --aggressive- mas feito corretamente - é fazer (durante a noite) algo como

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

onde aquela coisa de profundidade é apenas sobre quão profundas as cadeias delta podem ser (torná-las mais longas para a história antiga - vale a pena o espaço em cima), e a coisa de janela é sobre o tamanho da janela de um objeto que queremos que cada candidato delta varra.

E aqui, você pode querer adicionar o -fsinalizador (que é o "descarte todos os deltas antigos", já que agora você está tentando ter certeza de que este realmente encontre bons candidatos.

Sascha Wolf
fonte
9

Cuidado. Não execute git gc --agressivecom o repositório que não está sincronizado com o remoto se você não tiver backups.

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

Para o meu computador de 8 GB agressivo, o gc ficou sem memória no repositório de 1 GB com 10k pequenos commits. Quando o OOM killer encerrou o processo git - ele me deixou com o repositório quase vazio, apenas a árvore de trabalho e alguns deltas sobreviveram.

Claro, não era a única cópia do repositório, então eu apenas o recriei e extraí do remoto (fetch não funcionou em repo quebrado e travou na etapa de 'resolução de deltas' algumas vezes eu tentei fazer isso), mas se o seu repo é repositório local de desenvolvedor único sem nenhum controle remoto - faça backup primeiro.

Sage Pointer
fonte
5

Observação: cuidado ao usar git gc --aggressive, conforme esclarece o Git 2.22 (segundo trimestre de 2019).

Consulte o commit 0044f77 , o commit daecbf2 , o commit 7384504 , o commit 22d4e3b , o commit 080a448 , o commit 54d56f5 , o commit d257e0f , o commit b6a8d09 (07 abr 2019) e o commit fc559fb , o commit cf9cd77 , o commit b11e856 (22 Marvar Arnmason avar) de .
(Fundido por Junio ​​C Hamano - gitster- no commit ac70c53 , 25 de abril de 2019)

gc docs: subestime a utilidade de --aggressive

Os gc --aggressivedocumentos " " existentes quase não recomendam aos usuários que os executem regularmente.
Falei pessoalmente com muitos usuários que tomaram esses documentos como um conselho para usar essa opção e, geralmente , é (na maioria) uma perda de tempo .

Portanto, vamos esclarecer o que ele realmente faz e deixar que o usuário tire suas próprias conclusões.

Vamos também esclarecer o "Os efeitos são [...] persistentes" para parafrasear uma breve versão da explicação de Jeff King .

Isso significa que a documentação do git-gc agora inclui :

AGRESSIVO

Quando a --aggressiveopção for fornecida, git-repackserá invocado com o -fsinalizador, que por sua vez passará --no-reuse-deltapara git-pack-objects .
Isso descartará quaisquer deltas existentes e os recalculará, ao custo de gastar muito mais tempo com o reembalagem.

Os efeitos disso são principalmente persistentes, por exemplo, quando pacotes e objetos soltos são aglutinados em um outro pacote, os deltas existentes nesse pacote podem ser reutilizados, mas também há vários casos em que podemos escolher um delta abaixo do ideal de um mais recente em vez disso.

Além disso, o fornecimento --aggressiveajustará as opções --depthe --windowpassadas para git-repack.
Veja as configurações gc.aggressiveDepthe gc.aggressiveWindowabaixo.
Ao usar um tamanho de janela maior, é mais provável que encontremos deltas mais ideais.

Provavelmente não vale a pena usar esta opção em um determinado repositório sem executar benchmarks de desempenho personalizados nele .
Leva muito mais tempo e a otimização de espaço / delta resultante pode ou não valer a pena. Não usar isso é a compensação certa para a maioria dos usuários e seus repositórios.

E ( commit 080a448 ):

gcdocs: observe como os --aggressiveimpactos --windowe--depth

Desde 07e7dbf ( gc: profundidade agressiva padrão de 50, 11/08/2016, Git v2.10.1), usamos de forma confusa a mesma profundidade em--aggressive que usamos por padrão.

Conforme observado naquele commit que faz sentido, era errado tornar mais profundo o padrão para "agressivo" e, assim, economizar espaço em disco às custas do desempenho do tempo de execução, que geralmente é o oposto de alguém que gostaria de "gc agressivo" quer.

VonC
fonte