Por que meu repositório git é tão grande?

141

145M = .git / objects / pack /

Eu escrevi um script para adicionar os tamanhos das diferenças de cada commit e do commit antes de voltar da ponta de cada branch. Recebo 129 MB, o que é sem compactação e sem contabilizar os mesmos arquivos entre filiais e histórico comum entre filiais.

O Git leva todas essas coisas em consideração, então eu esperaria um repositório muito menor. Então, por que o .git é tão grande?

Eu fiz:

git fsck --full
git gc --prune=today --aggressive
git repack

Para responder sobre quantos arquivos / confirmações, tenho 19 ramificações, aproximadamente 40 arquivos em cada. 287 confirmados, encontrados usando:

git log --oneline --all|wc -l

Não deve demorar 10 megabytes para armazenar informações sobre isso.

Ian Kelling
fonte
5
A Linus recomenda o seguinte sobre o GC agressivo. Isso faz uma diferença significativa? git repack -a -d --depth = 250 --window = 250
Greg Bacon
obrigado gbacon, mas não há diferença.
23610 Ian Kelling
Isso ocorre porque você está perdendo o -f. metalinguist.wordpress.com/2007/12/06/…
spuder
git repack -a -dreduzi meu repo de 956 MB para 250 MB . Grande sucesso! Obrigado!
Xanderiel

Respostas:

68

Recentemente, puxei o repositório remoto errado para o local ( git remote add ...e git remote update). Depois de excluir a referência remota indesejada, ramificações e tags, eu ainda tinha 1,4 GB (!) De espaço desperdiçado no meu repositório. Eu só consegui me livrar disso clonando-o git clone file:///path/to/repository. Observe que isso file://faz muita diferença ao clonar um repositório local - apenas os objetos referenciados são copiados, não toda a estrutura de diretórios.

Edit: Aqui está uma linha de Ian para recriar todas as filiais no novo repositório:

d1=#original repo
d2=#new repo (must already exist)
cd $d1
for b in $(git branch | cut -c 3-)
do
    git checkout $b
    x=$(git rev-parse HEAD)
    cd $d2
    git checkout -b $b $x
    cd $d1
done
pgs
fonte
1
Uau. OBRIGADO. .git = 15 milhões agora !! Após a clonagem, aqui está um pequeno liner para preservar suas ramificações anteriores. d1 = # repositório original; d2 = # novo repositório; cd $ d1; para b em $ (git branch | cut -c 3-); faça git checkout $ b; x = $ (git rev-parse HEAD); cd $ d2; git checkout -b $ b $ x; cd $ d1; feito
Ian Kelling
se você marcar isso, poderá adicionar o liner 1 à sua resposta para que seja formatado como código.
26230 Ian Kelling
1
Eu adicionei tolamente um monte de arquivos de vídeo ao meu repositório e tive que redefinir --soft HEAD ^ e confirmar novamente. O diretório .git / objects ficou enorme depois disso, e essa foi a única maneira de recuperá-lo. No entanto, eu não gostei da maneira como o liner mudou meus nomes de filiais (ele mostrou origem / nome da filial em vez de apenas nome da filial). Então, fui um pouco além e executei uma cirurgia superficial - excluí o diretório .git / objects do original e o coloquei no clone. Isso fez o truque, deixando todos os ramos originais, árbitros, etc. intactos, e tudo parece funcionar (cruzando os dedos).
Jack Senechal
1
obrigado pela dica sobre o file: // clone, que fez o truque para mim
adam.wulf
3
@vonbrand se você criar um link físico para um arquivo e excluir o arquivo original, nada acontece, exceto que um contador de referência é diminuído de 2 para 1. Somente se esse contador for diminuído para 0, o espaço será liberado para outros arquivos no fs. Portanto, não, mesmo que os arquivos tenham links físicos, nada aconteceria se o original fosse excluído.
31513 stefreak
157

Alguns scripts que eu uso:

git-fatfiles

git rev-list --all --objects | \
    sed -n $(git rev-list --objects --all | \
    cut -f1 -d' ' | \
    git cat-file --batch-check | \
    grep blob | \
    sort -n -k 3 | \
    tail -n40 | \
    while read hash type size; do 
         echo -n "-e s/$hash/$size/p ";
    done) | \
    sort -n -k1
...
89076 images/screenshots/properties.png
103472 images/screenshots/signals.png
9434202 video/parasite-intro.avi

Se você quiser mais linhas, consulte também a versão Perl em uma resposta vizinha: https://stackoverflow.com/a/45366030/266720

erradicar git (para video/parasite.avi):

git filter-branch -f  --index-filter \
    'git rm --force --cached --ignore-unmatch video/parasite-intro.avi' \
     -- --all
rm -Rf .git/refs/original && \
    git reflog expire --expire=now --all && \
    git gc --aggressive && \
    git prune

Nota: o segundo script foi projetado para remover completamente as informações do Git (incluindo todas as informações dos reflogs). Use com cuidado.

Vi.
fonte
2
Finalmente ... Ironicamente, eu vi essa resposta no início da minha pesquisa, mas parecia muito complicada ... depois de tentar outras coisas, essa começou a fazer sentido e pronto!
msanteler
@ msanteler, O git-fatfilesscript antigo ( ) surgiu quando eu fiz a pergunta no IRC (Freenode / # git). Salvei a melhor versão em um arquivo e a postei como resposta aqui. (Embora não seja possível o autor original nos logs do IRC).
Vi.
Isso funciona muito bem inicialmente. Mas quando eu pego ou puxo do controle remoto novamente, ele apenas copia todos os arquivos grandes novamente no arquivo morto. Como evito isso?
pir
1
@ Felbo, Então o problema provavelmente não está apenas no seu repositório local, mas em outros repositórios também. Talvez você precise executar o procedimento em qualquer lugar ou forçar todo mundo a abandonar ramificações originais e mudar para ramificações reescritas. Não é fácil em uma grande equipe e precisa de cooperação entre desenvolvedores e / ou intervenção do gerente. Às vezes, apenas deixar a pedra de carga dentro pode ser uma opção melhor.
Vi.
1
Essa função é ótima, mas é inimaginavelmente lenta. Ele nem pode terminar no meu computador se eu remover o limite de 40 linhas. Para sua informação, acabei de adicionar uma resposta com uma versão mais eficiente dessa função. Verifique se você deseja usar essa lógica em um grande repositório ou se deseja ver os tamanhos somados por arquivo ou por pasta.
Piojo 28/07/19
66

git gcjá faz git repackisso, então não faz sentido reembalar manualmente, a menos que você esteja passando algumas opções especiais para ele.

A primeira etapa é verificar se a maioria do espaço é (como normalmente seria o caso) o seu banco de dados de objetos.

git count-objects -v

Isso deve fornecer um relatório de quantos objetos descompactados existem em seu repositório, quanto espaço eles ocupam, quantos arquivos de pacote você tem e quanto espaço eles ocupam.

Idealmente, após uma reembalagem, você não teria objetos descompactados e um arquivo de pacote, mas é perfeitamente normal ter alguns objetos que não são diretamente referenciados pelas ramificações atuais ainda presentes e descompactados.

Se você tiver um único pacote grande e quiser saber o que está ocupando o espaço, poderá listar os objetos que compõem o pacote, além de como eles são armazenados.

git verify-pack -v .git/objects/pack/pack-*.idx

Observe que verify-packleva um arquivo de índice e não o próprio arquivo de pacote. Isso fornece um relatório de cada objeto no pacote, seu tamanho real e seu tamanho, bem como informações sobre se ele foi 'deltificado' e, em caso afirmativo, a origem da cadeia delta.

Para verificar se existem objetos invulgarmente grandes no seu repositório, você pode classificar a saída numericamente na terceira da quarta coluna (por exemplo | sort -k3n).

Nesta saída, você poderá ver o conteúdo de qualquer objeto usando o git showcomando, embora não seja possível ver exatamente onde no histórico de consolidação do repositório o objeto é referenciado. Se você precisar fazer isso, tente algo com esta pergunta .

CB Bailey
fonte
1
Isso achou ótimos objetos grandes. A resposta aceita se livrou deles.
26230 Ian Kelling
2
A diferença entre git gc e git repack de acordo com linus torvalds. metalinguist.wordpress.com/2007/12/06/...
spuder
31

Apenas para sua informação, a maior razão pela qual você pode acabar mantendo objetos indesejados é que o git mantém um reflog.

O reflog está lá para salvar sua bunda quando você acidentalmente excluir sua ramificação principal ou de alguma forma danificar catastroficamente seu repositório.

A maneira mais fácil de corrigir isso é truncar seus reflogs antes de compactá-los (apenas certifique-se de que você nunca deseja voltar a nenhum dos commits no reflog).

git gc --prune=now --aggressive
git repack

Isso é diferente, git gc --prune=todaypois expira todo o reflog imediatamente.

John Gietzen
fonte
1
Este fez isso por mim! Eu fui de cerca de 5gb para 32mb.
Hawkee 28/09/16
Essa resposta parecia mais fácil, mas infelizmente não funcionou para mim. No meu caso, eu estava trabalhando em um repositório apenas clonado. Essa é a razão?
Mert
13

Se você deseja descobrir quais arquivos estão ocupando espaço em seu repositório git, execute

git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5

Em seguida, extraia a referência de blob que ocupa mais espaço (a última linha) e verifique o nome do arquivo que ocupa muito espaço

git rev-list --objects --all | grep <reference>

Pode até ser um arquivo que você removeu git rm, mas o git se lembra porque ainda existem referências a ele, como tags, controles remotos e reflog.

Depois de saber de qual arquivo você deseja se livrar, eu recomendo usar git forget-blob

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

É fácil de usar, basta fazer

git forget-blob file-to-forget

Isso removerá todas as referências do git, removerá o blob de todos os commit no histórico e executará a coleta de lixo para liberar espaço.

nachoparker
fonte
7

O script git-fatfiles da resposta de Vi é adorável se você quiser ver o tamanho de todos os seus blobs, mas é tão lento que pode ser inutilizado. Eu removi o limite de saída de 40 linhas e ele tentou usar toda a RAM do meu computador em vez de terminar. Então, eu a reescrevi: isso é milhares de vezes mais rápido, adicionou recursos (opcional) e algum bug estranho foi removido - a versão antiga daria contagens imprecisas se você soma a saída para ver o espaço total usado por um arquivo.

#!/usr/bin/perl
use warnings;
use strict;
use IPC::Open2;
use v5.14;

# Try to get the "format_bytes" function:
my $canFormat = eval {
    require Number::Bytes::Human;
    Number::Bytes::Human->import('format_bytes');
    1;
};
my $format_bytes;
if ($canFormat) {
    $format_bytes = \&format_bytes;
}
else {
    $format_bytes = sub { return shift; };
}

# parse arguments:
my ($directories, $sum);
{
    my $arg = $ARGV[0] // "";
    if ($arg eq "--sum" || $arg eq "-s") {
        $sum = 1;
    }
    elsif ($arg eq "--directories" || $arg eq "-d") {
        $directories = 1;
        $sum = 1;
    }
    elsif ($arg) {
        print "Usage: $0 [ --sum, -s | --directories, -d ]\n";
        exit 1;
    } 
}

# the format is [hash, file]
my %revList = map { (split(' ', $_))[0 => 1]; } qx(git rev-list --all --objects);
my $pid = open2(my $childOut, my $childIn, "git cat-file --batch-check");

# The format is (hash => size)
my %hashSizes = map {
    print $childIn $_ . "\n";
    my @blobData = split(' ', <$childOut>);
    if ($blobData[1] eq 'blob') {
        # [hash, size]
        $blobData[0] => $blobData[2];
    }
    else {
        ();
    }
} keys %revList;
close($childIn);
waitpid($pid, 0);

# Need to filter because some aren't files--there are useless directories in this list.
# Format is name => size.
my %fileSizes =
    map { exists($hashSizes{$_}) ? ($revList{$_} => $hashSizes{$_}) : () } keys %revList;


my @sortedSizes;
if ($sum) {
    my %fileSizeSums;
    if ($directories) {
        while (my ($name, $size) = each %fileSizes) {
            # strip off the trailing part of the filename:
            $fileSizeSums{$name =~ s|/[^/]*$||r} += $size;
        }
    }
    else {
        while (my ($name, $size) = each %fileSizes) {
            $fileSizeSums{$name} += $size;
        }
    }

    @sortedSizes = map { [$_, $fileSizeSums{$_}] }
        sort { $fileSizeSums{$a} <=> $fileSizeSums{$b} } keys %fileSizeSums;
}
else {
    # Print the space taken by each file/blob, sorted by size
    @sortedSizes = map { [$_, $fileSizes{$_}] }
        sort { $fileSizes{$a} <=> $fileSizes{$b} } keys %fileSizes;

}

for my $fileSize (@sortedSizes) {
    printf "%s\t%s\n", $format_bytes->($fileSize->[1]), $fileSize->[0];
}

Nomeie git-fatfiles.pl e execute-o. Para ver o espaço em disco usado por todas as revisões de um arquivo, use a --sumopção Para ver a mesma coisa, mas para arquivos em cada diretório, use a --directoriesopção Se você instalar o número :: Bytes :: Humano módulo CPAN (run "cpan Número :: Bytes :: Humanos"), os tamanhos será formatado: "/path/to/file.mp4 21M".

piojo
fonte
4

Tem certeza de que está contando apenas os arquivos .pack e não os arquivos .idx? Eles estão no mesmo diretório que os arquivos .pack, mas não possuem nenhum dado do repositório (como a extensão indica, eles nada mais são do que índices para o pacote correspondente - na verdade, se você souber o comando correto, poderá recrie-os facilmente a partir do arquivo do pacote, e o próprio git faz isso ao clonar, pois apenas um arquivo de pacote é transferido usando o protocolo git nativo).

Como uma amostra representativa, dei uma olhada no meu clone local do repositório linux-2.6:

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

O que indica uma expansão de cerca de 7% deve ser comum.

Existem também os arquivos externos objects/; na minha experiência pessoal, eles indexe gitk.cachetendem a ser os maiores (totalizando 11 milhões no meu clone do repositório linux-2.6).

CesarB
fonte
3

Outros objetos git armazenados em .gitincluem árvores, confirmações e tags. As confirmações e tags são pequenas, mas as árvores podem ficar grandes, principalmente se você tiver um número muito grande de arquivos pequenos no seu repositório. Quantos arquivos e quantas confirmações você possui?

Greg Hewgill
fonte
Boa pergunta. 19 ramificações com cerca de 40 arquivos em cada. git count-objects -v diz "in-pack: 1570". Não sei exatamente o que isso significa ou como contar quantos commits tenho. Algumas centenas, eu acho.
Ian Kelling
Ok, não parece que essa é a resposta então. Algumas centenas serão insignificantes em comparação com 145 MB.
Greg Hewgill
2

Você tentou usar o git repack ?

baudtack
fonte
Boa pergunta. Eu também tive a impressão de que o git gc também faz isso?
23610 Ian Kelling
Isso acontece com o git gc --auto Não tenho certeza sobre o que você usou.
baudtack
2

antes de executar o git filter-branch e o git gc, revise as tags presentes no seu repositório. Qualquer sistema real que possua etiquetagem automática para coisas como integração e implantações contínuas fará com que objetos não datados ainda sejam atualizados por essas tags, portanto, o gc não poderá removê-las e você continuará se perguntando por que o tamanho do repo ainda é tão grande.

A melhor maneira de se livrar de todas as coisas indesejadas é rodar o git-filter & git gc e depois empurrar o master para um novo repositório. O novo repositório vazio terá a árvore limpa.

v_abhi_v
fonte
1

Isso pode acontecer se você adicionou um grande pedaço de arquivos acidentalmente e os preparou, não necessariamente os compromete. Isso pode acontecer em um railsaplicativo quando você executar bundle install --deploymente, em seguida, acidentalmente git add ., então você ver todos os arquivos adicionados sob vendor/bundlevocê unstage eles, mas eles já tem na história git, então você tem que aplicar a resposta de Vi e mudança video/parasite-intro.avide vendor/bundleseguida, executar o segundo comando que ele proporciona.

Você pode ver a diferença com a git count-objects -vqual, no meu caso, antes de aplicar o script tinha um pacote de tamanho: de 52K e depois de aplicá-lo era de 3,8K.

juliangonzalez
fonte
1

Vale a pena conferir o stacktrace.log. É basicamente um log de erros para rastrear confirmações que falharam. Descobri recentemente que meu stacktrace.log é de 65,5 GB e meu aplicativo é de 66,7 GB.

Nes
fonte