git gc – aggressivo vs git repack

Sto cercando modi per ridurre le dimensioni di un repository git . La ricerca mi porta a git gc --aggressive molto git gc --aggressive più delle volte. Ho anche letto che questo non è l’approccio preferito.

Perché? di cosa dovrei essere a conoscenza se sto eseguendo gc --aggressive ?

git repack -a -d --depth=250 --window=250 è raccomandato su gc --aggressive . Perché? In che modo il repack riduce la dimensione di un repository? Inoltre, non sono abbastanza chiaro sulle bandiere – --depth e – --window .

Cosa dovrei scegliere tra gc e repack ? Quando dovrei usare gc e repack ?

Oggigiorno non c’è differenza: git gc --aggressive opera secondo la proposta di Linus del 2007; vedi sotto. A partire dalla versione 2.11 (4 ° trim. 2016), git è impostato su una profondità di 50. Una finestra di dimensione 250 è buona perché analizza una sezione più ampia di ciascun object, ma la profondità a 250 è errata perché rende ogni catena riferita a molto profonde oggetti, che rallenta tutte le future operazioni git per l’utilizzo del disco marginalmente inferiore.


Sfondo storico

Linus suggerì (vedi sotto per il post completo della mailing list) usando git gc --aggressive solo quando hai, nelle sue parole, “un pacco davvero pessimo” o “delta veramente orribilmente cattivo”, comunque “quasi sempre, in altri casi, è davvero una brutta cosa da fare. “Il risultato potrebbe persino lasciare il tuo repository in condizioni peggiori rispetto a quando hai iniziato!

Il comando che suggerisce di fare correttamente dopo aver importato “una storia lunga e complessa” lo è

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

Ma questo presuppone che tu abbia già rimosso una porzione indesiderata dalla cronologia del tuo repository e di aver seguito l’elenco di controllo per la riduzione di un repository trovato nella documentazione di git filter-branch .

git-filter-branch può essere usato per sbarazzarsi di un sottoinsieme di file, solitamente con una combinazione di --index-filter e --subdirectory-filter . Le persone si aspettano che il repository risultante sia più piccolo dell’originale, ma sono necessari alcuni passaggi per renderlo più piccolo, perché Git cerca di non perdere i tuoi oggetti finché non glielo dici. Innanzitutto assicurati che:

  • Hai davvero rimosso tutte le varianti di un nome file, se un blob è stato spostato nel corso della sua durata. git log --name-only --follow --all -- filename può aiutarti a trovare i git log --name-only --follow --all -- filename .

  • Hai davvero filtrato tutti i ref: usa --tag-name-filter cat -- --all quando chiami git filter-branch .

Quindi ci sono due modi per ottenere un repository più piccolo. Un modo più sicuro è quello di clonare, che mantiene intatto il tuo originale.

  • Clonalo con git clone file:///path/to/repo . Il clone non avrà gli oggetti rimossi. Vedi git-clone. (Si noti che la clonazione con un percorso semplice collega solo tutto!)

Se davvero non vuoi clonarlo, per qualsiasi motivo, controlla invece i seguenti punti (in questo ordine). Questo è un approccio molto distruttivo, quindi fai un backup o torna a clonarlo. Sei stato avvertito.

  • Rimuovi i ref originali da git-filter-branch: dire

     git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d 
  • Scadono tutti i reflog con git reflog expire --expire=now --all .

  • Garbage raccogliere tutti gli oggetti non referenziati con git gc --prune=now (o se git gc non è abbastanza nuovo da supportare argomenti per --prune , usa git repack -ad; git prune invece).


 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> 

Il 6 dicembre 2007, Daniel Berlin ha scritto:

In realtà, git-gc --aggressive fa questa cosa stupida per comprimere i file a volte indipendentemente dal fatto che tu abbia convertito da un repository SVN o meno.

Assolutamente. git --aggressive è per lo più stupido. È davvero utile solo nel caso di “So di avere un pacco davvero pessimo, e voglio buttare via tutte le decisioni sbagliate che ho preso”.

Per spiegarlo, vale la pena di spiegare (probabilmente ne sei a conoscenza, ma lasciami comunque andare alle basi) come funzionano le catene delta git e come sono così diverse dalla maggior parte degli altri sistemi.

In altri SCM, una catena delta è generalmente fissa. Potrebbe essere “avanti” o “indietro” e potrebbe evolversi un po ‘mentre si lavora con il repository, ma generalmente si tratta di una catena di modifiche a un singolo file rappresentato come una sorta di singola quadro SCM. In CVS, è ovviamente il file *,v , e molti altri sistemi fanno cose piuttosto simili.

Git fa anche catene delta, ma le fa molto di più “liberamente”. Non esiste un’ quadro fissa. I delta sono generati contro qualsiasi altra versione casuale che git ritenga essere un buon candidato delta (con varie euristiche di successo), e non ci sono assolutamente regole di raggruppamento.

Questa è generalmente un’ottima cosa. Va bene per varie ragioni concettuali ( vale a dire , git internamente non ha mai nemmeno bisogno di preoccuparsi dell’intera catena di revisione – non pensa affatto in termini di delta), ma è anche grandioso perché eliminare le inflessibili regole del delta significa che git non ha alcun problema con la fusione di due file insieme, ad esempio – semplicemente non ci sono arbitrari *,v “file di revisione” che hanno un significato nascosto.

Significa anche che la scelta dei delta è una domanda molto più aperta. Se si limita la catena del delta a un solo file, in realtà non si hanno molte scelte su cosa fare riguardo ai delta, ma in git, può davvero essere un problema completamente diverso.

Ed è qui che entra in gioco l’ --aggressive nome – l’ --aggressive . Mentre git in genere cerca di riutilizzare le informazioni delta (perché è una buona idea, e non spreca tempo nella ricerca della CPU per trovare tutti i delta migliori che abbiamo trovato prima) a volte vuoi dire “iniziamo dappertutto, con una lavagna vuota, e ignoriamo tutte le informazioni delta precedenti, e proviamo a generare un nuovo set di delta”.

Insum, non è aggressivo, ma sprecare tempo nella CPU a rifare una decisione che abbiamo già fatto prima!

A volte è una buona cosa. Alcuni strumenti di importazione in particolare potrebbero generare delta davvero orribilmente brutto. Qualunque cosa utilizzi git fast-import , ad esempio, probabilmente non ha molto di un grande layout delta, quindi potrebbe valere la pena di dire “Voglio iniziare da una lavagna pulita”.

Ma quasi sempre, in altri casi, è davvero una brutta cosa da fare. Ridurrà il tempo della CPU, e soprattutto se in passato aveste fatto un buon lavoro a deltaing, il risultato finale non riutilizzerà tutti quei buoni delta che avete già trovato, quindi in realtà finirete con un molto risultato finale peggiore anche!

git gc --aggressive una patch a Junio ​​per rimuovere semplicemente la documentazione git gc --aggressive . Può essere utile, ma in genere è utile solo quando capisci a un livello molto profondo ciò che sta facendo e quella documentazione non ti aiuta a farlo.

In generale, fare git gc incrementale è l’approccio giusto, e meglio di fare git gc --aggressive . Riutilizzerà i vecchi delta, e quando quei vecchi delta non possono essere trovati (la ragione per fare GC incrementali in primo luogo!) Ne creeranno di nuovi.

D’altra parte, è sicuramente vero che una “importazione iniziale di una storia lunga e coinvolta” è un punto in cui può valere la pena spendere un sacco di tempo per trovare i delta veramente buoni . Quindi, ogni utente dopo (purché non utilizzi git gc --aggressive per annullarlo!) git gc --aggressive il vantaggio di quell’evento singolo. Quindi, soprattutto per i progetti di grandi dimensioni con una lunga storia, probabilmente vale la pena di fare un lavoro extra, dicendo al delta trovare codice per scatenarsi.

Quindi l’equivalente di git gc --aggressivegit gc --aggressive – ma fatto correttamente – è quello di fare (durante la notte) qualcosa del genere

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

dove quella cosa di profondità è solo quanto possono essere profonde le catene delta (renderle più lunghe per la vecchia storia – ne vale la pena lo spazio in testa), e la cosa della finestra riguarda la grandezza di una finestra object che vogliamo che ogni delta candidate effettui la scansione.

E qui, potresti voler aggiungere il flag -f (che è il “drop all delta old”, dato che ora stai davvero cercando di assicurarti che questo trovi effettivamente dei buoni candidati.

E poi ci vorranno per sempre e un giorno ( cioè , una cosa “fallo durante la notte”). Ma il risultato finale è che tutti a valle di quel repository otterranno pacchetti molto migliori, senza dover spendere alcuno sforzo da soli.

  Linus 

Quando dovrei usare gc e repack?

Come ho detto in ” Git Garbage collection non sembra funzionare appieno “, un git gc --aggressive non è né sufficiente né abbastanza da solo.

La combinazione più efficace sarebbe aggiungere git repack , ma anche git prune :

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

Nota: Git 2.11 (4 ° trim. 2016) imposterà la profondità aggressiva gc predefinita su 50

Vedi commit 07e7dbf (11 ago 2016) di Jeff King ( peff ) .
(Unita da Junio ​​C Hamano – gitster – in commit 0952ca8 , 21 set 2016)

gc : profondità aggressiva predefinita a 50

git gc --aggressive ” utilizzato per limitare la lunghezza della catena delta a 250, che è troppo profonda per ottenere ulteriori risparmi di spazio ed è dannosa per le prestazioni di runtime.
Il limite è stato ridotto a 50.

Il sumrio è: il default corrente di 250 non risparmia molto spazio e costa CPU. Non è un buon compromesso.

Il flag ” --aggressive ” su git-gc fa tre cose:

  1. usa ” -f ” per eliminare i delta esistenti e ricalcarli da zero
  2. usa “–window = 250” per sembrare più difficile per i delta
  3. usa “–depth = 250” per creare catene delta più lunghe

Gli articoli (1) e (2) sono buoni abbinamenti per un repack “aggressivo”.
Chiedono al repack di fare più lavoro di calcolo nella speranza di ottenere un pacchetto migliore. Paghi i costi durante il repack e altre operazioni vedono solo il beneficio.

L’articolo (3) non è così chiaro.
Consentire catene più lunghe significa meno restrizioni sui delta, il che significa potenzialmente trovarne di migliori e risparmiare spazio.
Ma significa anche che le operazioni che accedono ai delta devono seguire catene più lunghe, il che influisce sulle loro prestazioni.
Quindi è un compromesso, e non è chiaro che il compromesso sia anche buono.

(Vedi impegno per studio )

Potete vedere che i risparmi della CPU per le operazioni regolari migliorano mentre diminuiamo la profondità.
Ma possiamo anche vedere che i risparmi di spazio non sono così grandi come la profondità aumenta. Il risparmio del 5-10% tra 10 e 50 probabilmente vale il compromesso della CPU. Il risparmio dell’1% per passare da 50 a 100 o un altro 0,5% per passare da 100 a 250 probabilmente non lo è.


Parlando di risparmio della CPU, ” git repack ” ha imparato ad accettare l’opzione --threads= e passarla a pack-objects.

Vedi commit 40bcf31 (26 Apr 2017) di Junio ​​C Hamano ( gitster ) .
(Fuso da Junio ​​C Hamano – gitster – in commit 31fb6f4 , 29 maggio 2017)

repack: accept --threads= e passalo agli pack-objects

Lo facciamo già per --window= e --depth= ; questo aiuterà quando l’utente vuole forzare --threads=1 per test riproducibili senza essere influenzato da corse multiple thread.

Il problema con git gc --aggressive è che il nome dell’opzione e la documentazione sono fuorvianti.

Come lo stesso Linus spiega in questa mail , ciò che git gc --aggressive fa di base è questo:

Mentre git in genere cerca di riutilizzare le informazioni delta (perché è una buona idea, e non spreca tempo nella ricerca della CPU per trovare tutti i delta migliori che abbiamo trovato prima), a volte vuoi dire “ricominciamo tutto da capo, con un vuota e ignora tutte le informazioni delta precedenti e prova a generare un nuovo set di delta “.

Di solito non è necessario ricalcolare i delta in git, poiché git determina questi delta molto flessibili. Ha senso solo se sai di avere dei delta davvero pessimi. Come spiega Linus, principalmente gli strumenti che fanno uso di git fast-import rientrano in questa categoria.

La maggior parte delle volte git fa un buon lavoro nel determinare utili delta e l’uso di git gc --aggressive ti lascerà dei delta potenzialmente peggiori mentre sprecerai un sacco di tempo per la CPU.


Linus termina la sua posta con la conclusione che il git repack con una grande --depth e una --window è la scelta migliore nella maggior parte del tempo; soprattutto dopo aver importato un progetto di grandi dimensioni e aver voluto assicurarsi che git trovi dei buoni delta.

Quindi l’equivalente di git gc --aggressivegit gc --aggressive – ma fatto correttamente – è quello di fare (durante la notte) qualcosa del genere

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

dove quella cosa di profondità è solo quanto possono essere profonde le catene delta (renderle più lunghe per la vecchia storia – ne vale la pena lo spazio in testa), e la cosa della finestra riguarda la grandezza di una finestra object che vogliamo che ogni delta candidate effettui la scansione.

E qui, potresti voler aggiungere il flag -f (che è il “drop all delt old”, dato che ora stai effettivamente cercando di assicurarti che questo trovi effettivamente dei buoni candidati.

Attenzione. Non eseguire git gc --agressive con repository che non è sincronizzato con il telecomando se non si dispone di backup.

Questa operazione ricrea i delta da zero e potrebbe portare alla perdita di dati se interrotta con garbo.

Per il mio computer da 8 GB, gc aggressivo ha esaurito la memoria su un repository da 1 GB con piccoli commit 10k. Quando OOM killer ha terminato il processo git, mi ha lasciato un repository quasi vuoto, solo l’albero funzionante e pochi delta sono sopravvissuti.

Naturalmente, non era l’unica copia del repository quindi l’ho appena ricreata e tirata da remoto (il recupero non ha funzionato su un repository rotto e bloccato sul passo “risolvi delta” poche volte ho provato a farlo), ma se il tuo repository è repo locale per singolo sviluppatore senza telecomandi – esegui prima il backup.