Comment supprimer des objets blob non référencés de mon référentiel git

124

J'ai un dépôt GitHub qui avait deux branches - master et release.

La branche de publication contenait des fichiers de distribution binaires qui contribuaient à une très grande taille de dépôt (> 250 Mo), j'ai donc décidé de nettoyer les choses.

J'ai d'abord supprimé la branche de version distante, via git push origin :release

Ensuite, j'ai supprimé la branche de publication locale. J'ai d'abord essayé git branch -d release, mais git a dit "erreur: La branche 'release' n'est pas un ancêtre de votre HEAD actuel." ce qui est vrai, alors je l'ai fait git branch -D releasepour le forcer à être supprimé.

Mais la taille de mon référentiel, à la fois localement et sur GitHub, était toujours énorme. Alors j'ai parcouru la liste habituelle des commandes git, comme git gc --prune=today --aggressive, sans chance.

En suivant les instructions de Charles Bailey au SO 1029969, j'ai pu obtenir une liste de SHA1 pour les plus gros blobs. J'ai ensuite utilisé le script de SO 460331 pour trouver les blobs ... et les cinq plus gros n'existent pas, bien que de plus petits blobs soient trouvés, donc je sais que le script fonctionne.

Je pense que ces blogs sont les binaires de la branche release, et ils ont en quelque sorte été abandonnés après la suppression de cette branche. Quelle est la bonne façon de s'en débarrasser?

kkrugler
la source
Quelle version de Git utilisez-vous? Et avez-vous essayé stackoverflow.com/questions/1106529/... ?
VonC
git version 1.6.2.3 J'avais essayé gc et élaguer avec divers arguments. Je n'avais pas essayé de reconditionner -a -d -l, je l'ai juste lancé, aucun changement.
kkrugler
2
Nouvelles informations - un nouveau clone de GitHub n'a plus les blobs non référencés, et est réduit à "seulement" 84 Mo sur 250 Mo.
kkrugler

Réponses:

219

... et sans plus tarder, puis-je vous présenter cette commande utile, "git-gc-all", garantie de supprimer toutes vos ordures git jusqu'à ce qu'elles puissent apparaître des variables de configuration supplémentaires:

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

Vous devrez peut-être également exécuter quelque chose comme ça en premier, oh mon Dieu, c'est compliqué !!

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

Vous devrez peut-être également supprimer certaines balises, merci Zitrax:

git tag | xargs git tag -d

J'ai mis tout cela dans un script: git-gc-all-féroce .

Sam Watkins
la source
1
Intéressant. Une bonne alternative à ma réponse plus générale. +1
VonC
10
Cela mérite plus de votes. Il s'est finalement débarrassé de beaucoup d'objets git que d'autres méthodes conserveraient. Merci!
Jean-Philippe Pellet
1
Vote positif. Wow, je ne sais pas ce que je viens de faire mais ça semble nettoyer beaucoup. Pouvez-vous expliquer ce qu'il fait? J'ai le sentiment qu'il a effacé tout mon objects. Quels sont ceux-ci et pourquoi sont-ils (apparemment) hors de propos?
Redsandro
2
@Redsandro, si je comprends bien, ces commandes "git rm origin", "rm" et "git update-ref -d" suppriment les références aux anciens commits pour les télécommandes et autres, ce qui pourrait empêcher le garbage collection. Les options de "git gc" lui disent de ne pas conserver divers anciens commits, sinon il les conservera pendant un certain temps. Par exemple, gc.rerereresolved est destiné aux "enregistrements de fusion en conflit que vous avez résolus précédemment", conservés par défaut pendant 60 jours. Ces options se trouvent dans la page de manuel git-gc. Je ne suis pas un expert en git et je ne sais pas exactement ce que font toutes ces choses. Je les ai trouvés dans les pages de manuel et en grepping .git pour les refs de commit.
Sam Watkins
1
Un objet git est un fichier ou une arborescence compressé ou un commit dans votre dépôt git, y compris d'anciens éléments de l'historique. git gc efface les objets inutiles. Il conserve les objets qui sont encore nécessaires pour votre dépôt actuel et son historique.
Sam Watkins
81

Comme décrit ici , si vous souhaitez supprimer définitivement tout ce qui est référencé uniquement via reflog , utilisez simplement

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

git reflog expire --expire-unreachable=now --all supprime toutes les références de commits inaccessibles dans reflog .

git gc --prune=now supprime les commits eux-mêmes.

Attention : Seule l'utilisation git gc --prune=nowne fonctionnera pas car ces commits sont toujours référencés dans le reflog. Par conséquent, la suppression du reflog est obligatoire. Notez également que si vous l'utilisez, rerereil a des références supplémentaires non effacées par ces commandes. Voir git help rererepour plus de détails. De plus, tous les commits référencés par des branches ou des balises locales ou distantes ne seront pas supprimés car ils sont considérés comme des données précieuses par git.

jiasli
la source
14
Cela a fonctionné, mais j'ai perdu mes cachettes sauvegardées dans le processus (rien de majeur dans mon cas, juste une mise en garde pour les autres)
Amro
1
pourquoi pas - agressif?
JoelFan
3
Je pense que cette réponse a besoin d'un avertissement clair, de préférence en haut. Ma suggestion de modification a été rejetée, car je suppose que je devrais la suggérer à l'auteur dans un commentaire? Veuillez accepter cette modification stackoverflow.com/review/suggested-edits/26023988 ou ajouter un avertissement à votre guise. En outre, cela supprime toutes vos cachettes . Cela devrait également être mentionné dans l'avertissement!
Inigo
J'ai testé avec la version 2.17 de git et les commits cachés ne seront pas supprimés par les commandes ci-dessus. Êtes-vous sûr de n'avoir exécuté aucune commande supplémentaire?
Mikko Rantalainen
1
git fetch --pruneréduire davantage la taille en supprimant les objets blob locaux.
hectorpal le
33

Comme mentionné dans cette réponse SO ,git gc peut en fait augmenter la taille du repo!

Voir aussi ce fil

Maintenant, git a un mécanisme de sécurité pour ne pas supprimer les objets non référencés immédiatement lors de l'exécution de ' git gc'.
Par défaut, les objets non référencés sont conservés pendant une période de 2 semaines. Ceci vous permet de récupérer facilement des branches ou des commits supprimés accidentellement, ou d'éviter une course où un objet qui vient d'être créé en cours de création mais pas encore référencé pourrait être supprimé par un 'git gc ' processus exécuté en parallèle.

Donc, pour donner ce délai de grâce aux objets emballés mais non référencés, le processus de reconditionnement pousse ces objets non référencés hors du pack dans leur forme libre afin qu'ils puissent être vieillis et éventuellement élagués.
Les objets qui ne sont plus référencés ne sont généralement pas si nombreux. Avoir 404855 objets non référencés est beaucoup, et être envoyé ces objets en premier lieu via un clone est stupide et un gaspillage complet de bande passante réseau.

Quoi qu'il en soit ... Pour résoudre votre problème, il vous suffit d'exécuter ` git gc` ' ' avec l' --prune=nowargument pour désactiver cette période de grâce et vous débarrasser immédiatement de ces objets non référencés (sûr uniquement si aucune autre activité git n'a lieu en même temps, ce qui devrait être facile à assurer sur un poste de travail).

Et BTW, en utilisant ' git gc --aggressive' avec une version ultérieure de git (ou ' git repack -a -f -d --window=250 --depth=250')

Le même fil mentionne :

 git config pack.deltaCacheSize 1

Cela limite la taille du cache delta à un octet (le désactivant effectivement) au lieu de la valeur par défaut de 0, ce qui signifie illimité. Avec cela, je suis capable de reconditionner ce référentiel en utilisant la git repackcommande ci-dessus sur un système x86-64 avec 4 Go de RAM et en utilisant 4 threads (il s'agit d'un quad core). Cependant, l'utilisation de la mémoire résidente atteint près de 3,3 Go.

Si votre machine est SMP et que vous ne disposez pas de suffisamment de RAM, vous pouvez réduire le nombre de threads à un seul:

git config pack.threads 1

De plus, vous pouvez limiter davantage l'utilisation de la mémoire avec le --window-memory argumentto ' git repack'.
Par exemple, l'utilisation --window-memory=128Mdoit conserver une limite supérieure raisonnable sur l'utilisation de la mémoire de recherche delta bien que cela puisse entraîner une correspondance delta moins optimale si le dépôt contient beaucoup de fichiers volumineux.


Sur le front de la branche filtre, vous pouvez considérer (avec prudence) ce 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
VonC
la source
stackoverflow.com/questions/359424/… est également un bon début pour l' filter-branchutilisation de la commande.
VonC
Salut VonC - NI avait essayé git gc prune = maintenant sans chance. Cela ressemble vraiment à un bogue git, en ce sens que je me suis retrouvé avec des blobs non référencés localement après une suppression de branche, mais ceux-ci ne sont pas là avec un nouveau clone du dépôt GitHub ... donc c'est juste un problème de dépôt local. Mais j'ai des fichiers supplémentaires que je veux effacer, donc le script que vous avez référencé ci-dessus est génial - merci!
kkrugler
19

git gc --prune=now, ou niveau bas git prune --expire now.

Jakub Narębski
la source
12

Chaque fois que votre HEAD bouge, git le suit dans le fichier reflog. Si vous avez supprimé des commits, vous avez toujours des "commits en suspens", car ils sont toujours référencés par le reflogpendant ~ 30 jours. Ceci est le filet de sécurité lorsque vous supprimez des commits par accident.

Vous pouvez utiliser la git reflogcommande remove specific commits, repack, etc., ou simplement la commande de haut niveau:

git gc --prune=now
vdboor
la source
5

Vous pouvez utiliser git forget-blob.

L'utilisation est assez simple git forget-blob file-to-forget. Vous pouvez obtenir plus d'informations ici

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

Il disparaîtra de tous les commits de votre historique, reflog, tags, etc.

Je rencontre le même problème de temps en temps, et chaque fois que je dois revenir sur ce post et sur d'autres, c'est pourquoi j'ai automatisé le processus.

Crédits à des contributeurs tels que Sam Watkins

nachoparker
la source
2

Essayez d'utiliser git-filter-branch - cela ne supprime pas les gros blobs, mais il peut supprimer les gros fichiers que vous spécifiez de l'ensemble du dépôt. Pour moi, cela réduit la taille du repo de centaines de Mo à 12 Mo.

W55tKQbuRu28Q4xv
la source
6
Maintenant c'est une commande effrayante :) Je vais devoir essayer quand mon git-fu se sentira plus fort.
kkrugler
Tu peux le répéter. Je me méfie toujours des commandes qui manipulent l'historique d'un référentiel. Les choses ont tendance à mal tourner lorsque plusieurs personnes poussent et tirent de ce référentiel et que soudainement un tas d'objets auxquels Git s'attend ne sont pas là.
Jonathan Dumaine
1

Parfois, la raison pour laquelle "gc" ne fait pas beaucoup de bien est qu'il y a un rebase ou un stash inachevé basé sur un ancien commit.

StellarVortex
la source
Ou l'ancien commit est référencé par HEAD, ORIG_HEAD, FETCH_HEAD, reflog ou autre chose que git continue automatiquement d'essayer de s'assurer qu'il ne perd jamais rien de précieux. Si vous voulez vraiment perdre tout cela, vous devez faire un effort supplémentaire pour le faire.
Mikko Rantalainen
1

Pour ajouter une autre astuce, n'oubliez pas d'utiliser git remote prune pour supprimer les branches obsolètes de vos télécommandes avant d'utiliser git gc

vous pouvez les voir avec git branch -a

C'est souvent utile lorsque vous récupérez à partir de github et de dépôts fourchus ...

Tanguy
la source
1

Avant de faire git filter-branchet git gc, vous devez examiner les balises présentes dans votre dépôt. Tout système réel qui a un marquage automatique pour des choses comme l'intégration continue et les déploiements rendra les objets indésirables encore référencés par ces balises, donc gcne peut pas les supprimer et vous vous demanderez toujours pourquoi la taille du dépôt est toujours aussi grande.

La meilleure façon de se débarrasser de tous les trucs non voulu est de courir git-filteret git gcpuis pousser maître à un nouveau repo nu. Le nouveau repo nu aura l'arbre nettoyé.

v_abhi_v
la source