Come rimuovere i BLOB non referenziati dal mio repository git


124

Ho un repository GitHub che aveva due rami: master e release.

Il ramo di rilascio conteneva file di distribuzione binari che contribuivano a una dimensione del repository molto grande (> 250 MB), quindi ho deciso di ripulire le cose.

Per prima cosa ho eliminato il ramo di rilascio remoto, tramite git push origin :release

Quindi ho eliminato il ramo di rilascio locale. Per prima cosa ho provato git branch -d release, ma git ha detto "errore: il ramo 'release' non è un antenato del tuo attuale HEAD." il che è vero, quindi ho fatto git branch -D releaseper forzare l'eliminazione.

Ma le dimensioni del mio repository, sia a livello locale che su GitHub, erano ancora enormi. Quindi ho eseguito il solito elenco di comandi git, tipo git gc --prune=today --aggressive, senza fortuna.

Seguendo le istruzioni di Charles Bailey in SO 1029969 sono stato in grado di ottenere un elenco di SHA1 per i blob più grandi. Ho quindi utilizzato lo script di SO 460331 per trovare i blob ... e i cinque più grandi non esistono, anche se vengono trovati blob più piccoli, quindi so che lo script funziona.

Penso che questi blog siano i binari del ramo di rilascio e in qualche modo sono rimasti in giro dopo l'eliminazione di quel ramo. Qual è il modo giusto per sbarazzarsene?


Quale versione di Git stai usando? E hai provato stackoverflow.com/questions/1106529/… ?
VonC

git versione 1.6.2.3 Avevo provato gc e prune con vari argomenti. Non avevo provato a reimballare -a -d -l, l'ho appena eseguito, nessuna modifica.
kkrugler

2
Nuove informazioni: un nuovo clone di GitHub non ha più i BLOB non referenziati ed è ridotto a "solo" 84 MB da 250 MB.
kkrugler

Risposte:


219

... e senza ulteriori indugi, posso presentarti questo utile comando, "git-gc-all", garantito per rimuovere tutta la tua spazzatura git fino a quando potrebbero venire fuori variabili di configurazione extra:

git -c gc.reflogExpire=0 -c gc.reflogExpireUnreachable=0 -c gc.rerereresolved=0 -c gc.rerereunresolved=0 -c gc.pruneExpire=now gc

Potrebbe anche essere necessario eseguire prima qualcosa di simile, oh caro, git è complicato !!

git remote rm origin
rm -rf .git/refs/original/ .git/refs/remotes/ .git/*_HEAD .git/logs/
git for-each-ref --format="%(refname)" refs/original/ | xargs -n1 --no-run-if-empty git update-ref -d

Potrebbe anche essere necessario rimuovere alcuni tag, grazie Zitrax:

git tag | xargs git tag -d

Ho messo tutto questo in una sceneggiatura: git-gc-all-ferocious .


1
Interessante. Una buona alternativa alla mia risposta più generale. +1
VonC

10
Questo merita più voti positivi. Alla fine si è sbarazzato di molti oggetti git che altri metodi avrebbero mantenuto. Grazie!
Jean-Philippe Pellet

1
Upvoted. Wow, non so cosa ho appena fatto, ma sembra che si sia sistemato molto. Puoi approfondire cosa fa? Ho la sensazione che abbia cancellato tutto il mio objects. Cosa sono e perché sono (apparentemente) irrilevanti?
Redsandro

2
@Redsandro, a quanto ho capito, quei comandi "git rm origin", "rm" e "git update-ref -d" rimuovono i riferimenti a vecchi commit per telecomandi e simili, che potrebbero impedire la garbage collection. Le opzioni di "git gc" dicono che non si aggrappa a vari vecchi commit, altrimenti li manterrà per un po '. Ad esempio, gc.rerereresolved è per "record di unione in conflitto risolti in precedenza", conservati per impostazione predefinita per 60 giorni. Queste opzioni si trovano nella manpage git-gc. Non sono un esperto di git e non so esattamente cosa fanno tutte queste cose. Li ho trovati da manpage e grepping .git per commit refs.
Sam Watkins,

1
Un oggetto git è un file compresso o un albero o un commit nel tuo repository git, comprese le vecchie cose dalla cronologia. git gc cancella gli oggetti non necessari. Mantiene gli oggetti che sono ancora necessari per il repository corrente e la sua cronologia.
Sam Watkins

81

Come descritto qui , se vuoi rimuovere definitivamente tutto ciò a cui fa riferimento solo tramite reflog , usa semplicemente

git reflog expire --expire-unreachable=now --all
git gc --prune=now

git reflog expire --expire-unreachable=now --allrimuove tutti i riferimenti di commit non raggiungibili in reflog.

git gc --prune=now rimuove i commit stessi.

Attenzione : solo l'utilizzo git gc --prune=nownon funzionerà poiché tali commit sono ancora referenziati nel reflog. Pertanto, la cancellazione del reflog è obbligatoria. Nota anche che se lo usi rerereha riferimenti aggiuntivi non cancellati da questi comandi. Vedi git help rerereper maggiori dettagli. Inoltre, eventuali commit a cui fanno riferimento rami o tag locali o remoti non verranno rimossi perché sono considerati dati preziosi da git.


14
Ha funzionato, ma in qualche modo ho perso le mie scorte salvate nel processo (niente di grave nel mio caso, solo un avvertimento per gli altri)
Amro

1
perché no, aggressivo?
JoelFan

3
Penso che questa risposta necessiti di un chiaro avvertimento, preferibilmente in alto. Il mio suggerimento di modifica è stato rifiutato, perché immagino che dovrei suggerirlo all'autore in un commento? Accetta questa modifica stackoverflow.com/review/suggested-edits/26023988 o aggiungi un avviso a modo tuo. Inoltre, questo fa cadere tutte le tue scorte . Anche questo dovrebbe essere inserito nell'avvertimento!
Inigo

Ho provato con git versione 2.17 e i commit nascosti non verranno rimossi dai comandi precedenti. Sei sicuro di non aver eseguito alcun comando aggiuntivo?
Mikko Rantalainen

1
git fetch --pruneridurre ulteriormente le dimensioni perché l'eliminazione dei BLOB locali.
hectorpal

33

Come accennato in questa risposta SO , git gcpuò effettivamente aumentare la dimensione del repo!

Vedi anche questo thread

Ora git ha un meccanismo di sicurezza per non eliminare subito gli oggetti non referenziati durante l'esecuzione ' git gc'.
Per impostazione predefinita, gli oggetti non referenziati vengono conservati per un periodo di 2 settimane. Questo per semplificare il recupero di rami o commit cancellati accidentalmente o per evitare una corsa in cui un oggetto appena creato in fase di elaborazione ma non ancora referenziato potrebbe essere cancellato da un git gcprocesso " " in esecuzione in parallelo.

Quindi, per concedere quel periodo di grazia agli oggetti imballati ma non referenziati, il processo di reimballaggio spinge quegli oggetti non referenziati fuori dallo zaino nella loro forma sciolta in modo che possano essere invecchiati ed eventualmente potati.
Gli oggetti che diventano non referenziati di solito non sono poi così tanti. Avere 404855 oggetti non referenziati è abbastanza, e l'invio di quegli oggetti in primo luogo tramite un clone è stupido e un completo spreco di larghezza di banda di rete.

Comunque ... Per risolvere il tuo problema, devi semplicemente eseguire ' git gc' con l' --prune=nowargomento per disabilitare quel periodo di grazia e sbarazzarti immediatamente di quegli oggetti non referenziati (sicuro solo se non si svolgono altre attività git allo stesso tempo, cosa che dovrebbe essere facile da garantire su una workstation).

E BTW, usando " git gc --aggressive" con una versione successiva di git (o " git repack -a -f -d --window=250 --depth=250")

Lo stesso thread menziona :

 git config pack.deltaCacheSize 1

Ciò limita la dimensione della cache delta a un byte (disabilitandola efficacemente) invece del valore predefinito di 0 che significa illimitato. Con ciò sono in grado di reimpacchettare quel repository usando il git repackcomando sopra su un sistema x86-64 con 4 GB di RAM e utilizzando 4 thread (questo è un quad core). L'utilizzo della memoria residente cresce fino a quasi 3,3 GB.

Se la tua macchina è SMP e non hai RAM sufficiente, puoi ridurre il numero di thread a uno solo:

git config pack.threads 1

Inoltre, puoi limitare ulteriormente l'utilizzo della memoria con il --window-memory argument" git repack".
Ad esempio, l'utilizzo --window-memory=128Mdovrebbe mantenere un limite superiore ragionevole sull'utilizzo della memoria della ricerca delta sebbene ciò possa comportare una corrispondenza delta meno ottimale se il repository contiene molti file di grandi dimensioni.


Sul fronte del ramo del filtro, puoi considerare (con cautela) questo script

#!/bin/bash
set -o errexit

# Author: David Underhill
# Script to permanently delete files/folders from your git repository.  To use 
# it, cd to your repository's root and then run the script with a list of paths
# you want to delete, e.g., git-delete-history path1 path2

if [ $# -eq 0 ]; then
    exit 0
fi

# make sure we're at the root of git repo
if [ ! -d .git ]; then
    echo "Error: must run this script from the root of a git repository"
    exit 1
fi

# remove all paths passed as arguments from the history of the repo
files=$@
git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch $files" HEAD

# remove the temporary history git-filter-branch otherwise leaves behind for a long time
rm -rf .git/refs/original/ && git reflog expire --all &&  git gc --aggressive --prune

stackoverflow.com/questions/359424/… è anche un buon inizio per l' filter-branchutilizzo dei comandi.
VonC

Ciao VonC - NI avevo provato git gc prune = ora senza fortuna. Sembra davvero un bug di Git, in quanto mi sono ritrovato con blob non referenziati localmente a seguito di una cancellazione di ramo, ma questi non sono presenti con un nuovo clone del repository GitHub ... quindi è solo un problema di repository locale. Ma ho altri file che voglio cancellare, quindi lo script a cui hai fatto riferimento sopra è fantastico - grazie!
kkrugler,


12

Ogni volta che la tua TESTA si muove, git ne tiene traccia nel file reflog. Se hai rimosso i commit, hai ancora "commit pendenti" perché sono ancora referenziati da reflogper ~ 30 giorni. Questa è la rete di sicurezza quando elimini i commit per sbaglio.

Puoi utilizzare il git reflogcomando rimuovi commit specifici, repack, ecc., O solo il comando di alto livello:

git gc --prune=now

5

Puoi usare git forget-blob.

L'utilizzo è piuttosto semplice git forget-blob file-to-forget. Puoi ottenere maggiori informazioni qui

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

Scomparirà da tutti i commit nella cronologia, reflog, tag e così via

Ogni tanto mi imbatto nello stesso problema, e ogni volta che devo tornare a questo post e ad altri, ecco perché ho automatizzato il processo.

Crediti a collaboratori come Sam Watkins


2

Prova a usare git-filter-branch : non rimuove i blob di grandi dimensioni, ma può rimuovere i file di grandi dimensioni che specifichi dall'intero repository. Per me riduce la dimensione del repo da centinaia di MB a 12 MB.


6
Ora che è un comando spaventoso :) Dovrò fare un tentativo, quando il mio git-fu si sente più forte.
kkrugler

puoi dirlo di nuovo. Sono sempre diffidente nei confronti dei comandi che manipolano la cronologia di un repository. Le cose tendono ad andare molto storte quando più persone spingono e tirano da quel repository e improvvisamente un mucchio di oggetti che git si aspetta non ci sono.
Jonathan Dumaine

1

A volte, la ragione per cui "gc" non fa molto bene è che c'è un rebase incompiuto o uno stash basato su un vecchio commit.


Oppure il vecchio commit fa riferimento a HEAD, ORIG_HEAD, FETCH_HEAD, reflog o qualche altra cosa che git continua automaticamente a cercare di assicurarsi che non perda mai nulla di prezioso. Se vuoi davvero perdere tutti quelli, devi fare il possibile per farlo.
Mikko Rantalainen

1

Per aggiungere un altro suggerimento, non dimenticare di usare git remote prune per eliminare i rami obsoleti dei tuoi telecomandi prima di utilizzare git gc

puoi vederli con git branch -a

È spesso utile quando si recupera da github e repository biforcati ...


1

Prima di fare git filter-branche git gc, dovresti rivedere i tag presenti nel tuo repository. Qualsiasi sistema reale che abbia la codifica automatica per cose come l'integrazione continua e le distribuzioni renderà gli oggetti indesiderati ancora referenziati da questi tag, quindi gcnon è possibile rimuoverli e continuerai a chiederti perché la dimensione del repository è ancora così grande.

Il miglior modo per sbarazzarsi di tutte le cose non-voluto è quello di eseguire git-filtere git gcquindi spingere master per un nuovo repo nudo. Il nuovo repository nudo avrà l'albero ripulito.

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.