Perché il mio repository git è così grande?


142

145M = .git / objects / pack /

Ho scritto una sceneggiatura per sommare le dimensioni delle differenze di ciascun commit e il commit prima di tornare indietro dalla punta di ogni ramo. Ottengo 129 MB, che è senza compressione e senza tenere conto degli stessi file tra i rami e la storia comune tra i rami.

Git tiene conto di tutte queste cose, quindi mi aspetto un repository molto più piccolo. Allora, perché .git è così grande?

Ho fatto:

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

Per rispondere a quanti file / commit, ho 19 rami di circa 40 file in ciascuno. 287 commit, trovati utilizzando:

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

Non dovrebbero essere necessari 10 megabyte per memorizzare informazioni al riguardo.


5
Linus consiglia quanto segue di aggressivo. Fa una differenza significativa? git repack -a -d --depth = 250 --window = 250
Greg Bacon

grazie gbacon, ma nessuna differenza.
Ian Kelling,


git repack -a -dridotto il mio repository da 956 MB a 250 MB . Grande successo! Grazie!
Xanderiel,

Risposte:


68

Recentemente ho inserito il repository remoto sbagliato in quello locale ( git remote add ...e git remote update). Dopo aver eliminato il riferimento remoto indesiderato, i rami e i tag avevo ancora 1,4 GB (!) Di spazio sprecato nel mio repository. Sono stato in grado di sbarazzarmi di questo solo clonandolo con git clone file:///path/to/repository. Si noti che file://quando si clona un repository locale fa la differenza: solo gli oggetti a cui si fa riferimento vengono copiati, non l'intera struttura della directory.

Modifica: Ecco la linea di Ian per ricreare tutti i rami nel nuovo repository:

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

1
Wow. GRAZIE. .git = 15M ora !! dopo la clonazione, ecco un piccolo liner per preservare i rami precedenti. d1 = # repository originale; d2 = # nuovo repository; cd $ d1; per b in $ (ramo git | cut -c 3-); esegui il checkout $ b; x = $ (git rev-parse HEAD); cd $ d2; git checkout -b $ b $ x; cd $ d1; fatto
Ian Kelling il

se lo controlli, potresti aggiungere 1 liner alla tua risposta in modo che sia formattato come codice.
Ian Kelling,

1
Ho aggiunto scioccamente un sacco di file video al mio repository e ho dovuto ripristinare --soft HEAD ^ e ricominciare. Il dir .git / objects era enorme dopo quello, e questo era l'unico modo per farlo tornare indietro. Tuttavia, non mi piaceva il modo in cui una delle linee cambiava i nomi dei miei rami (mostrava origine / nome del ramo invece del solo nome del ramo). Quindi ho fatto un ulteriore passo avanti e ho eseguito un intervento chirurgico impreciso: ho eliminato la directory .git / objects dall'originale e ho inserito quello dal clone. Questo ha funzionato, lasciando intatti tutti i rami, i riferimenti, ecc. Originali e tutto sembra funzionare (incrociando le dita).
Jack Senechal,

1
grazie per il suggerimento sul file: // clone, che ha fatto il trucco per me
adam.wulf

3
@vonbrand se si collega a un file e si elimina il file originale, non accade nulla, tranne che un contatore di riferimento viene ridotto da 2 a 1. Solo se quel contatore viene ridotto a 0, lo spazio viene liberato per altri file sull'fs. Quindi no, anche se i file fossero hard link non accadrebbe nulla se l'originale venisse eliminato.
stefreak,

157

Alcuni script che 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 vuoi più righe, vedi anche la versione Perl in una risposta vicina: https://stackoverflow.com/a/45366030/266720

git-eradicate (per 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: il secondo script è progettato per rimuovere completamente le informazioni da Git (comprese tutte le informazioni dai reflog). Usare con cautela.


2
Finalmente ... Ironicamente ho visto questa risposta prima nella mia ricerca, ma sembrava troppo complicata ... dopo aver provato altre cose, questa ha iniziato a dare un senso e voilà!
msanteler,

@msanteler, Il precedente git-fatfilesscript ( ) è emerso quando ho posto la domanda su IRC (Freenode / # git). Ho salvato la versione migliore in un file, quindi l'ho pubblicata come risposta qui. (Non posso l'autore originale nei registri IRC sebbene).
Vi.

Questo funziona molto bene inizialmente. Ma quando riprendo o estraggo di nuovo dal telecomando, copia semplicemente tutti i file di grandi dimensioni nell'archivio. Come posso impedirlo?
pir

1
@felbo, Quindi il problema probabilmente non è solo nel tuo repository locale, ma anche in altri repository. Forse devi eseguire la procedura ovunque, o forzare tutti ad abbandonare i rami originali e passare ai rami riscritti. Non è facile in una grande squadra e necessita della cooperazione tra gli sviluppatori e / o l'intervento del manager. A volte è sufficiente lasciare la pietra di carico all'interno.
Vi.

1
Questa funzione è eccezionale, ma è inimmaginabilmente lenta. Non riesco nemmeno a finire sul mio computer se rimuovo il limite di 40 linee. Cordiali saluti, ho appena aggiunto una risposta con una versione più efficiente di questa funzione. Dai un'occhiata se vuoi usare questa logica su un grande repository, o se vuoi vedere le dimensioni sommate per file o per cartella.
Piojo,

66

git gclo fa già, git repackquindi non ha senso reimballare manualmente a meno che non gli si passino alcune opzioni speciali.

Il primo passo è vedere se la maggior parte dello spazio è (come sarebbe normalmente il caso) il database degli oggetti.

git count-objects -v

Questo dovrebbe fornire un rapporto su quanti oggetti scompattati ci sono nel tuo repository, quanto spazio occupano, quanti file pack hai e quanto spazio occupano.

Idealmente, dopo un repack, non avresti oggetti decompressi e un file pack ma è perfettamente normale avere alcuni oggetti che non sono direttamente referenti dagli attuali rami ancora presenti e decompressi.

Se si dispone di un unico pacchetto di grandi dimensioni e si desidera sapere cosa sta occupando lo spazio, è possibile elencare gli oggetti che compongono il pacchetto insieme a come vengono memorizzati.

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

Si noti che verify-packaccetta un file indice e non il file pack stesso. Ciò fornisce un rapporto di ogni oggetto nel pacchetto, le sue dimensioni reali e le sue dimensioni impacchettate, nonché informazioni sul fatto che sia stato "eliminato" e, in tal caso, l'origine della catena delta.

Per vedere se ci sono oggetti insolitamente grandi nel tuo repository puoi ordinare l'output numericamente sulla terza della quarta colonna (ad es | sort -k3n.).

Da questo output sarete in grado di vedere il contenuto di qualsiasi oggetto usando il git showcomando, sebbene non sia possibile vedere esattamente dove nella cronologia di commit del repository viene fatto riferimento all'oggetto. Se devi fare questo, prova qualcosa da questa domanda .


1
Questo ha trovato grandi oggetti fantastici. La risposta accettata si è sbarazzata di loro.
Ian Kelling,

2
La differenza tra git gc e git reimballa secondo linus torvalds. metalinguist.wordpress.com/2007/12/06/…
spuder

32

Solo FYI, il motivo principale per cui potresti finire con oggetti indesiderati che vengono tenuti in giro è che git mantiene un reflog.

Il reflog è lì per salvare il tuo culo quando elimini accidentalmente il tuo ramo principale o in qualche modo altrimenti danneggi catastroficamente il tuo repository.

Il modo più semplice per risolvere questo problema è di troncare i reflog prima di comprimere (assicurati solo di non voler mai tornare a nessuno dei commit nel reflog).

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

Ciò è diverso dal fatto git gc --prune=todayche scade immediatamente l'intero reflog.


1
Questo è stato per me! Sono passato da circa 5 GB a 32 MB.
Hawkee,

Questa risposta sembrava più facile da fare, ma purtroppo non ha funzionato per me. Nel mio caso stavo lavorando su un repository appena clonato. È questa la ragione?
Mert

13

Se vuoi trovare quali file occupano spazio nel tuo repository git, esegui

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

Quindi, estrarre il riferimento BLOB che occupa più spazio (l'ultima riga) e controllare il nome file che occupa così tanto spazio

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

Questo potrebbe anche essere un file che hai rimosso git rm, ma git lo ricorda perché ci sono ancora riferimenti ad esso, come tag, telecomandi e reflog.

Una volta che sai di quale file vuoi sbarazzarti, ti consiglio di usare git forget-blob

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

È facile da usare, basta farlo

git forget-blob file-to-forget

Ciò rimuoverà ogni riferimento da git, rimuoverà il BLOB da ogni commit nella cronologia ed eseguirà la garbage collection per liberare spazio.


7

Lo script git-fatfiles della risposta di Vi è adorabile se vuoi vedere le dimensioni di tutti i tuoi blob, ma è così lento da essere inutilizzabile. Ho rimosso il limite di output di 40 righe e ho provato a utilizzare tutta la RAM del mio computer invece di terminare. Quindi l'ho riscritto: questo è migliaia di volte più veloce, ha aggiunto funzionalità (opzionale) e alcuni strani bug sono stati rimossi - la vecchia versione darebbe conteggi imprecisi se sommi l'output per vedere lo spazio totale utilizzato da un file.

#!/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];
}

Denominare questo git-fatfiles.pl ed eseguirlo. Per visualizzare lo spazio su disco utilizzato da tutte le revisioni di un file, utilizzare l' --sumopzione Per vedere la stessa cosa, ma per i file all'interno di ciascuna directory, utilizzare l' --directoriesopzione. Se si installa il Numero :: :: Bytes umana modulo CPAN (run "CPAN Numero :: :: Bytes Umani"), vengono formattate le dimensioni: "21M /path/to/file.mp4".


4

Sei sicuro di contare solo i file .pack e non i file .idx? Sono nella stessa directory dei file .pack, ma non hanno nessuno dei dati del repository (come indica l'estensione, non sono altro che indici per il pacchetto corrispondente - infatti, se conosci il comando corretto, puoi ricrearli facilmente dal file pack e git stesso lo fa durante la clonazione, poiché solo un file pack viene trasferito utilizzando il protocollo git nativo).

Come esempio rappresentativo, ho dato un'occhiata al mio clone locale del repository linux-2.6:

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

Ciò indica che un'espansione di circa il 7% dovrebbe essere comune.

Ci sono anche i file all'esterno objects/; nella mia esperienza personale, di loro indexe gitk.cachetendono ad essere i più grandi (per un totale di 11 M nel mio clone del repository linux-2.6).


3

Altri oggetti git archiviati .gitincludono alberi, commit e tag. I commit e i tag sono piccoli, ma gli alberi possono diventare grandi in particolare se hai un numero molto grande di piccoli file nel tuo repository. Quanti file e quanti commit hai?


Buona domanda. 19 filiali con circa 40 file in ciascuna. git count-objects -v dice "in-pack: 1570". Non sono sicuro di cosa significhi o come contare il numero di commit che ho. Alcune centinaia immagino.
Ian Kelling,

Ok, allora non sembra che sia la risposta. Alcune centinaia saranno insignificanti rispetto a 145 MB.
Greg Hewgill,


2

prima di eseguire git filter-branch e git gc, è necessario rivedere i tag presenti nel repository. Qualsiasi sistema reale che ha la codifica automatica per cose come l'integrazione continua e le distribuzioni renderà gli oggetti non censurati ancora rifatti da questi tag, quindi non puoi rimuoverli e continuerai a chiederti perché le dimensioni del repository sono ancora così grandi.

Il modo migliore per sbarazzarsi di tutte le cose indesiderate è eseguire git-filter & git gc e quindi spingere master in un nuovo repository nudo. Il nuovo repository nudo avrà l'albero pulito.


1

Questo può accadere se hai aggiunto accidentalmente una grande quantità di file e li hai messi in scena, non necessariamente commetterli. Questo può accadere in un railsapp quando si esegue bundle install --deploymente poi accidentalmente git add .poi vedere tutti i file aggiunti sotto vendor/bundleli unstage ma già avuto nella storia git, quindi si deve applicare la risposta di Vi e il cambiamento video/parasite-intro.avidal vendor/bundlequindi eseguire il secondo comando che fornisce.

Puoi vedere la differenza con git count-objects -vcui nel mio caso prima di applicare lo script c'era un pacchetto di dimensioni: di 52K e dopo averlo applicato era di 3,8K.


1

Vale la pena controllare stacktrace.log. Fondamentalmente si tratta di un registro degli errori per i commit della traccia non riusciti. Di recente ho scoperto che il mio stacktrace.log è 65,5 GB e la mia app è 66,7 GB.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.