Come trovare / identificare grandi commit nella storia di Git?


366

Ho un repository git da 300 MB. La dimensione totale dei miei file attualmente estratti è di 2 MB e la dimensione totale del resto del repository git è di 298 MB. Questo è fondamentalmente un repository solo codice che non dovrebbe essere più di qualche MB.

Sospetto che qualcuno abbia commesso accidentalmente alcuni file di grandi dimensioni (video, immagini, ecc.) E poi li abbia rimossi ... ma non da Git, quindi la cronologia contiene ancora file di grandi dimensioni inutili. Come trovare i file di grandi dimensioni nella cronologia di Git? Ci sono oltre 400 commit, quindi andare uno a uno non è pratico.

NOTA : la mia domanda non riguarda come rimuovere il file , ma come trovarlo in primo luogo.



Risposte:


143

Ho trovato questo script molto utile in passato per trovare oggetti di grandi dimensioni (e non ovvi) in un repository git:


#!/bin/bash
#set -x 

# Shows you the largest objects in your repo's pack file.
# Written for osx.
#
# @see https://stubbisms.wordpress.com/2009/07/10/git-script-to-show-largest-pack-objects-and-trim-your-waist-line/
# @author Antony Stubbs

# set the internal field separator to line break, so that we can iterate easily over the verify-pack output
IFS=$'\n';

# list all objects including their size, sort by size, take top 10
objects=`git verify-pack -v .git/objects/pack/pack-*.idx | grep -v chain | sort -k3nr | head`

echo "All sizes are in kB's. The pack column is the size of the object, compressed, inside the pack file."

output="size,pack,SHA,location"
allObjects=`git rev-list --all --objects`
for y in $objects
do
    # extract the size in bytes
    size=$((`echo $y | cut -f 5 -d ' '`/1024))
    # extract the compressed size in bytes
    compressedSize=$((`echo $y | cut -f 6 -d ' '`/1024))
    # extract the SHA
    sha=`echo $y | cut -f 1 -d ' '`
    # find the objects location in the repository tree
    other=`echo "${allObjects}" | grep $sha`
    #lineBreak=`echo -e "\n"`
    output="${output}\n${size},${compressedSize},${other}"
done

echo -e $output | column -t -s ', '

Questo ti darà il nome dell'oggetto (SHA1sum) del BLOB e quindi puoi usare uno script come questo:

... per trovare il commit che punta a ciascuno di quei BLOB.


31
Questa risposta è stata davvero utile, perché mi ha inviato al post sopra. Mentre la sceneggiatura del post funzionava, la trovavo dolorosamente lenta. Quindi l'ho riscritto ed è ora molto più veloce su grandi repository. Dai
Nick K9,

7
Includere le istruzioni complete nelle risposte e non solo i collegamenti fuori sede; Cosa facciamo quando stubbisms.wordpress.com scende inevitabilmente eh?
ThorSummoner,

@ NickK9, in modo interessante, ho un output diverso dal tuo script e dall'altro. ci sono un sacco di oggetti più grandi che i tuoi sembrano mancare. C'è qualcosa che mi manca?
UpAndAdam,

Oh fico! Grazie per rendere il mio script più veloce @nick \ k9: D @UpAndAdam, stai dicendo che il mio script ha prodotto un output errato?
Antony Stubbs,

1
Questi commenti fanno sembrare che stiamo segnalando dimensioni in byte, ma ottengo kilobyte.
Kat,

683

🚀 Una fodera incredibilmente veloce conchiglia 🚀

Questo script di shell visualizza tutti gli oggetti BLOB nel repository, ordinati dal più piccolo al più grande.

Per il mio repository di esempio, ha funzionato circa 100 volte più velocemente degli altri trovati qui.
Sul mio fidato sistema Athlon II X4, gestisce il repository del kernel Linux con i suoi 5,6 milioni di oggetti in poco più di un minuto .

Lo script di base

git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| sed -n 's/^blob //p' \
| sort --numeric-sort --key=2 \
| cut -c 1-12,41- \
| $(command -v gnumfmt || echo numfmt) --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

Quando esegui il codice sopra, otterrai un output leggibile come questo:

...
0d99bb931299  530KiB path/to/some-image.jpg
2ba44098e28f   12MiB path/to/hires-image.png
bd1741ddce0d   63MiB path/to/some-video-1080p.mp4

Utenti macOS : poiché numfmtnon è disponibile su macOS, è possibile omettere l'ultima riga e gestire dimensioni di byte non elaborate oppurebrew install coreutils .

filtraggio

Per ottenere un ulteriore filtraggio , inserire una delle seguenti righe prima della sortriga .

Per escludere i file presenti inHEAD , inserire la seguente riga:

| grep -vF --file=<(git ls-tree -r HEAD | awk '{print $3}') \

Per mostrare solo i file che superano le dimensioni indicate (ad es. 1 MiB = 2 20  B), inserire la seguente riga:

| awk '$2 >= 2^20' \

Uscita per computer

Per generare un output più adatto per ulteriori elaborazioni da parte dei computer, omettere le ultime due righe dello script di base. Fanno tutta la formattazione. Questo ti lascerà con qualcosa del genere:

...
0d99bb93129939b72069df14af0d0dbda7eb6dba 542455 path/to/some-image.jpg
2ba44098e28f8f66bac5e21210c2774085d2319b 12446815 path/to/hires-image.png
bd1741ddce0d07b72ccf69ed281e09bf8a2d0b2f 65183843 path/to/some-video-1080p.mp4

Rimozione file

Per la rimozione effettiva dei file, dai un'occhiata a questa domanda SO sull'argomento .


14
Questo merita molto più del mio voto! Un ringraziamento speciale per la fornitura di output leggibile sia dal computer che dall'uomo.
Michel Jung,

2
Questo è estremamente veloce e facile da usare!
Chin

32
Per utilizzare questo su Mac è necessario brew install coreutilse quindi sostituire cutcon gcute numfmtcon gnumfmt.
Nick Sweeting,

2
Consentitemi di sottolineare nuovamente: è molto più veloce di tutti gli altri elenchi che ho visto.
Sridhar Sarnobat,

4
questo rende un alias git fantastico :) git largenessuno?
anarcat

160

Ho trovato una soluzione "one-liner" sulla pagina wiki del Dipartimento di Fisica dell'ETH di Zurigo (vicino alla fine di quella pagina). Basta fare un git gcper rimuovere spazzatura stantia e quindi

git rev-list --objects --all \
  | grep "$(git verify-pack -v .git/objects/pack/*.idx \
           | sort -k 3 -n \
           | tail -10 \
           | awk '{print$1}')"

ti darà i 10 file più grandi nel repository.

C'è anche una soluzione più pigra ora disponibile, GitExtensions ora ha un plugin che lo fa nell'interfaccia utente (e gestisce anche le riscritture della cronologia).

Finestra di dialogo "Trova file di grandi dimensioni" di GitExtensions


8
Quel one-liner funziona solo se vuoi ottenere il singolo file più grande (cioè usa tail -1). Newline si mette in mezzo per qualcosa di più grande. Puoi usare sed per convertire le nuove righe in modo che grep git rev-list --objects --all | grep -E `git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -10 | awk '{print$1}' | sed ':a;N;$!ba;s/\n/|/g'`
funzioni

10
grep: a70783fca9bfbec1ade1519a41b6cc4ee36faea0: nessun file o directory
Jonathan Allard,

1
Il link wiki è stato spostato su: readme.phys.ethz.ch/documentation/git_advanced_hints
outsmartin

11
Trovare GitExtensions è come trovare la pentola d'oro e la fine dell'arcobaleno - grazie!
ckapilla,

3
Esiste anche un'estensione che stampa la dimensione dei file?
Michael,

27

Passaggio 1 Scrivi tutti i file SHA1 in un file di testo:

git rev-list --objects --all | sort -k 2 > allfileshas.txt

Passaggio 2 Ordinare i BLOB dal più grande al più piccolo e scrivere i risultati nel file di testo:

git gc && git verify-pack -v .git/objects/pack/pack-*.idx | egrep "^\w+ blob\W+[0-9]+ [0-9]+ [0-9]+$" | sort -k 3 -n -r > bigobjects.txt

Passaggio 3a Combina entrambi i file di testo per ottenere informazioni sul nome file / sha1 / size:

for SHA in `cut -f 1 -d\  < bigobjects.txt`; do
echo $(grep $SHA bigobjects.txt) $(grep $SHA allfileshas.txt) | awk '{print $1,$3,$7}' >> bigtosmall.txt
done;

Passaggio 3b Se si hanno nomi di file o nomi di percorso contenenti spazi provare questa variante del passaggio 3a. Usa cutinvece di awkottenere le colonne desiderate incl. spazi dalla colonna 7 alla fine della riga:

for SHA in `cut -f 1 -d\  < bigobjects.txt`; do
echo $(grep $SHA bigobjects.txt) $(grep $SHA allfileshas.txt) | cut -d ' ' -f'1,3,7-' >> bigtosmall.txt
done;

Ora puoi guardare il file bigtosmall.txt per decidere quali file vuoi rimuovere dalla tua cronologia di Git.

Passaggio 4 Per eseguire la rimozione (notare che questa parte è lenta poiché esaminerà ogni commit nella cronologia per i dati sul file identificato):

git filter-branch --tree-filter 'rm -f myLargeFile.log' HEAD

fonte

I passaggi 1-3a sono stati copiati dalla ricerca e l'eliminazione di file di grandi dimensioni dalla cronologia di Git

MODIFICARE

L'articolo è stato cancellato nella seconda metà del 2017, ma è ancora possibile accedervi tramite una Wayback Machine .


6
Una fodera per fare la stessa cosa:git gc && join -e ERROR -a 2 -j 1 -o 2.1,2.3,1.2 --check-order <( git rev-list --objects --all | sort -k 1 ) <( git verify-pack -v .git/objects/pack/pack-*.idx | gawk '( NF == 5 && $2 == "blob" ){print}' | sort -k1 ) | sort -k2gr
Iwan Aucamp

1
@Iwan, grazie per il one-liner! Essa non gestisce i nomi di file con spazi tra loro, questo sembra: join -t' ' -e ERROR -a 2 -j 1 -o 2.1,2.3,1.2 --check-order <( git rev-list --objects --all | sed 's/[[:space:]]/\t/' | sort -k 1 ) <( git verify-pack -v .git/objects/pack/pack-*.idx | gawk '( NF == 5 && $2 == "blob" ){print}' | sort -k1 | sed 's/[[:space:]]\+/\t/g' ) | sort -k2gr | less. Nota che devi inserire il carattere TAB effettivo dopo join -t'con CTRL + V <TAB> per geekbraindump.blogspot.ru/2009/04/unix-join-with-tabs.html
Nickolay

2
@Nickolay con bash $'\t'dovrebbe darti una scheda. echo -n $'\t' | xxd -ps->09
Iwan Aucamp,

1
@IwanAucamp: ancora meglio, grazie per il suggerimento! (Peccato che non riesco a modificare il commento precedente .. vabbè.)
Nickolay,

1
@ Sridhar-Sarnobat L'articolo è stato salvato dalla Wayback Machine! :) web.archive.org/web/20170621125743/http://www.naleid.com/blog/…
friederbluemle

18

È necessario utilizzare BFG Repo-Cleaner .

Secondo il sito Web:

Il BFG è un'alternativa più semplice e veloce al ramo git-filter per eliminare i dati errati dalla cronologia del repository Git:

  • Rimozione di file Big Crazy
  • Rimozione di password, credenziali e altri dati privati

La procedura classica per ridurre le dimensioni di un repository sarebbe:

git clone --mirror git://example.com/some-big-repo.git
java -jar bfg.jar --strip-biggest-blobs 500 some-big-repo.git
cd some-big-repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push

4
BFG Repo-Cleaner è molto buono. È velocissimo e funziona in modo molto affidabile.
fschmitt,

30
Questo non ti dice come elencare tutti i file più grandi però.
Andi Jay,

5
Il problema è che non puoi semplicemente VEDERE quali sono i file di grandi dimensioni senza rimuoverli. Non mi sento a mio agio nel farlo prima senza una corsa a secco che elenca semplicemente i file di grandi dimensioni.
Sridhar Sarnobat,

Che cosa significa --strip-biggest-blobs 500fare?
2540625,

git rifiuterà le modifiche apportate da questo strumento.
Christopher

9

Se vuoi solo avere un elenco di file di grandi dimensioni, allora vorrei fornirti il ​​seguente one-liner:

join -o "1.1 1.2 2.3" <(git rev-list --objects --all | sort) <(git verify-pack -v objects/pack/*.idx | sort -k3 -n | tail -5 | sort) | sort -k3 -n

La cui produzione sarà:

commit       file name                                  size in bytes

72e1e6d20... db/players.sql 818314
ea20b964a... app/assets/images/background_final2.png 6739212
f8344b9b5... data_test/pg_xlog/000000010000000000000001 1625545
1ecc2395c... data_development/pg_xlog/000000010000000000000001 16777216
bc83d216d... app/assets/images/background_1forfinal.psd 95533848

L'ultima voce dell'elenco punta al file più grande nella tua cronologia git.

Puoi utilizzare questo output per assicurarti di non eliminare elementi con GGG che avresti avuto bisogno nella tua cronologia.


2
Eccezionale!! Tuttavia, è necessario notare che è necessario clonare il repository con le opzioni --mirror prima di eseguire questo comando.
Andi Jay,

Sono curioso, a cosa servono i 1.1, 1.2, 2.3numeri?
ympostor

I numeri sono un elenco che <filenumber>.<field>specifica l'ordine della combinazione. Vedi man.cx/join per maggiori informazioni.
schmijos,

6

Se sei su Windows, ecco uno script di PowerShell che stamperà i 10 file più grandi nel tuo repository:

$revision_objects = git rev-list --objects --all;
$files = $revision_objects.Split() | Where-Object {$_.Length -gt 0 -and $(Test-Path -Path $_ -PathType Leaf) };
$files | Get-Item -Force | select fullname, length | sort -Descending -Property Length | select -First 10

1
Questo produce una risposta diversa da @raphinesse, mancando un mucchio dei file più grandi sul mio repository. Anche quando un file di grandi dimensioni presenta molte modifiche, viene riportata solo la dimensione più grande.
kristianp,

Questo script non riuscita per me, con l'errore: You cannot call a method on a null-valued expression. At line: 2 char: 1. Tuttavia, questa risposta ha funzionato: stackoverflow.com/a/57793716/2441655 (è anche più breve)
Venryx,

4

Provare git ls-files | xargs du -hs --threshold=1M .

Usiamo il comando seguente nella nostra pipeline CI, si ferma se trova file di grandi dimensioni nel repository git:

test $(git ls-files | xargs du -hs --threshold=1M 2>/dev/null | tee /dev/stderr | wc -l) -gt 0 && { echo; echo "Aborting due to big files in the git repository."; exit 1; } || true

2

Non sono stato in grado di utilizzare la risposta più popolare perché --batch-check opzione da riga di comando su Git 1.8.3 (che devo usare) non accetta alcun argomento. I passaggi seguenti sono stati provati su CentOS 6.5 con Bash 4.1.2

Concetti chiave

In Git, il termine BLOB implica il contenuto di un file. Si noti che un commit potrebbe cambiare il contenuto di un file o percorso. Pertanto, lo stesso file potrebbe fare riferimento a un BLOB diverso a seconda del commit. Un certo file potrebbe essere il più grande nella gerarchia di directory in un commit, mentre non in un altro. Pertanto, la questione di trovare commit di grandi dimensioni anziché file di grandi dimensioni, pone le cose nella prospettiva corretta.

Per l'impaziente

Il comando per stampare l'elenco di BLOB in ordine decrescente di dimensioni è:

git cat-file --batch-check < <(git rev-list --all --objects  | \
awk '{print $1}')  | grep blob  | sort -n -r -k 3

Uscita campione:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e blob 305971200
7c357f2c2a7b33f939f9b7125b155adbd7890be2 blob 289163620

Per rimuovere tali BLOB, utilizzare BFG Repo Cleaner , come indicato in altre risposte. Dato un file blobs.txtche contiene solo gli hash BLOB, ad esempio:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e
7c357f2c2a7b33f939f9b7125b155adbd7890be2

Fare:

java -jar bfg.jar -bi blobs.txt <repo_dir>

La domanda è su come trovare i commit, che è più lavoro che trovare blob. Per sapere, continua a leggere.

Ulteriori lavori

Dato un hash di commit, un comando che stampa gli hash di tutti gli oggetti ad esso associati, inclusi i BLOB, è:

git ls-tree -r --full-tree <commit_hash>

Quindi, se abbiamo tali output disponibili per tutti i commit nel repository, quindi dato un hash BLOB, il gruppo di commit è quello che corrisponde a uno qualsiasi degli output. Questa idea è codificata nel seguente script:

#!/bin/bash
DB_DIR='trees-db'

find_commit() {
    cd ${DB_DIR}
    for f in *; do
        if grep -q $1 ${f}; then
            echo ${f}
        fi
    done
    cd - > /dev/null
}

create_db() {
    local tfile='/tmp/commits.txt'
    mkdir -p ${DB_DIR} && cd ${DB_DIR}
    git rev-list --all > ${tfile}

    while read commit_hash; do
        if [[ ! -e ${commit_hash} ]]; then
            git ls-tree -r --full-tree ${commit_hash} > ${commit_hash}
        fi
    done < ${tfile}
    cd - > /dev/null
    rm -f ${tfile}
}

create_db

while read id; do
    find_commit ${id};
done

Se i contenuti vengono salvati in un file denominato, find-commits.shuna chiamata tipica sarà come sotto:

cat blobs.txt | find-commits.sh

Come in precedenza, il file blobs.txtelenca gli hash BLOB, uno per riga. Ilcreate_db() funzione salva una cache di tutti gli elenchi di commit in una sottodirectory nella directory corrente.

Alcune statistiche dai miei esperimenti su un sistema con due processori Intel (R) Xeon (R) CPU E5-2620 a 2,00 GHz presentati dal sistema operativo come 24 core virtuali:

  • Numero totale di commit nel repository = quasi 11.000
  • Velocità di creazione del file = 126 file / s. Lo script crea un singolo file per commit. Ciò si verifica solo quando la cache viene creata per la prima volta.
  • Overhead di creazione della cache = 87 s.
  • Velocità di ricerca media = 522 commit / s. L'ottimizzazione della cache ha comportato una riduzione dell'80% del tempo di esecuzione.

Si noti che lo script è a thread singolo. Pertanto, verrà utilizzato un solo core alla volta.


2

Soluzione Powershell per Windows Git, trova i file più grandi:

git ls-tree -r -t -l --full-name HEAD | Where-Object {
 $_ -match '(.+)\s+(.+)\s+(.+)\s+(\d+)\s+(.*)'
 } | ForEach-Object {
 New-Object -Type PSObject -Property @{
     'col1'        = $matches[1]
     'col2'      = $matches[2]
     'col3' = $matches[3]
     'Size'      = [int]$matches[4]
     'path'     = $matches[5]
 }
 } | sort -Property Size -Top 10 -Descending

0

Come posso rintracciare i file di grandi dimensioni nella cronologia di Git?

Inizia analizzando, convalidando e selezionando la causa principale. Usa git-repo-analysisper aiutare.

Potresti anche trovare un po 'di valore nei report dettagliati generati da BFG Repo-Cleaner , che possono essere eseguiti molto rapidamente clonando su un droplet Digital Ocean usando il loro throughput di rete di 10 MiB / s.


Penso che tu abbia una bella risposta generale nel suggerimento BFG, ma lo rovini non fornendo alcun dettaglio e quindi suggerendo di utilizzare un servizio di terze parti diverso (anche senza alcuna spiegazione). Puoi ripulirlo un po 'per fornire un esempio da riga di comando di questo utilizzo di BFG?
phord,

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.