Pourquoi mon dépôt git est-il si grand?

142

145M = .git / objets / pack /

J'ai écrit un script pour additionner les tailles des différences de chaque commit et du commit avant de revenir en arrière depuis la pointe de chaque branche. J'obtiens 129 Mo, ce qui est sans compression et sans tenir compte des mêmes fichiers dans les branches et de l'historique commun entre les branches.

Git prend toutes ces choses en compte, donc je m'attendrais à un dépôt beaucoup plus petit. Alors, pourquoi .git est-il si grand?

J'ai fait:

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

Pour répondre au nombre de fichiers / commits, j'ai 19 branches environ 40 fichiers chacune. 287 commits, trouvés en utilisant:

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

Cela ne devrait pas prendre des dizaines de mégaoctets pour stocker des informations à ce sujet.

Ian Kelling
la source
5
Linus recommande ce qui suit par rapport au gc agressif. Cela fait-il une différence significative? git repack -a -d --depth = 250 --window = 250
Greg Bacon
merci gbacon, mais aucune différence.
Ian Kelling
C'est parce que vous manquez le -f. metalinguist.wordpress.com/2007/12/06/…
spuder
git repack -a -dmon rétrécis 956MB repo à 250Mo . Grand succès! Merci!
xanderiel

Réponses:

68

J'ai récemment extrait le mauvais référentiel distant dans le local ( git remote add ...et git remote update). Après avoir supprimé la référence distante, les branches et les balises indésirables, j'avais encore 1,4 Go (!) D'espace gaspillé dans mon référentiel. Je n'ai pu m'en débarrasser qu'en le clonant avec git clone file:///path/to/repository. Notez que le file://fait toute la différence lors du clonage d'un référentiel local - seuls les objets référencés sont copiés, pas toute la structure de répertoires.

Edit: Voici la doublure d'Ian pour recréer toutes les branches dans le nouveau repo:

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
pgs
la source
1
sensationnel. MERCI. .git = 15M maintenant !! après le clonage, voici un petit 1 liner pour conserver vos branches précédentes. d1 = # repo d'origine; d2 = # nouveau repo; cd $ d1; pour b dans $ (git branch | cut -c 3-); faire git checkout $ b; x = $ (git rev-parse HEAD); cd $ d2; git checkout -b $ b $ x; cd $ d1; done
Ian Kelling
si vous cochez cette case, vous pouvez ajouter la ligne 1 à votre réponse pour qu'elle soit formatée en code.
Ian Kelling
1
J'ai bêtement ajouté un tas de fichiers vidéo à mon dépôt et j'ai dû réinitialiser --soft HEAD ^ et recommencer. Le répertoire .git / objects était énorme après cela, et c'était la seule façon de le récupérer. Cependant, je n'aimais pas la façon dont l'unique doublure changeait les noms de mes branches (il montrait l'origine / le nom de la branche au lieu de simplement le nom de la branche). Je suis donc allé un peu plus loin et j'ai exécuté une opération sommaire - j'ai supprimé le répertoire .git / objects de l'original et mis celui du clone. Cela a fait l'affaire, laissant toutes les branches d'origine, les références, etc. intactes, et tout semble fonctionner (croiser les doigts).
Jack Senechal le
1
merci pour le conseil sur le fichier: // clone, qui a fait l'affaire pour moi
adam.wulf
3
@vonbrand si vous liez en dur à un fichier et supprimez le fichier d'origine, rien ne se passe sauf qu'un compteur de référence est décrémenté de 2 à 1. Ce n'est que si ce compteur est décrémenté à 0 que l'espace est libéré pour les autres fichiers sur le fs. Donc non, même si les fichiers étaient liés en dur, rien ne se passerait si l'original était supprimé.
stefreak
157

Quelques scripts que j'utilise:

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

Si vous voulez plus de lignes, consultez également la version Perl dans une réponse voisine: https://stackoverflow.com/a/45366030/266720

git-eradicate (pour 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

Remarque: le deuxième script est conçu pour supprimer complètement les informations de Git (y compris toutes les informations des reflogs). Utiliser avec précaution.

Vi.
la source
2
Enfin ... Ironiquement, j'ai vu cette réponse plus tôt dans ma recherche mais cela avait l'air trop compliqué ... après avoir essayé d'autres choses, celle-ci a commencé à avoir du sens et le tour est joué!
msanteler
@msanteler, l'ancien git-fatfilesscript ( ) est apparu lorsque j'ai posé la question sur IRC (Freenode / # git). J'ai enregistré la meilleure version dans un fichier, puis je l'ai publiée comme réponse ici. (Je ne peux pas l'auteur original dans les journaux IRC cependant).
Vi.
Cela fonctionne très bien au départ. Mais lorsque je récupère ou extrait à nouveau de la télécommande, il copie simplement tous les gros fichiers dans l'archive. Comment éviter cela?
pir
1
@felbo, Alors le problème n'est probablement pas seulement dans votre référentiel local, mais également dans d'autres référentiels. Vous devez peut-être faire la procédure partout ou forcer tout le monde à abandonner les branches d'origine et à passer aux branches réécrites. Ce n'est pas facile dans une grande équipe et nécessite une coopération entre développeurs et / ou une intervention du manager. Parfois, laisser simplement la pierre de chargement à l'intérieur peut être une meilleure option.
Vi.
1
Cette fonction est excellente, mais elle est incroyablement lente. Il ne peut même pas se terminer sur mon ordinateur si je supprime la limite de 40 lignes. Pour info, je viens d'ajouter une réponse avec une version plus efficace de cette fonction. Vérifiez-le si vous souhaitez utiliser cette logique sur un grand référentiel, ou si vous souhaitez voir les tailles additionnées par fichier ou par dossier.
piojo
66

git gcfait déjà un git repack, il n'y a donc aucun sens à reconditionner manuellement à moins que vous ne lui passiez des options spéciales.

La première étape consiste à voir si la majorité de l'espace est (comme ce serait normalement le cas) votre base de données d'objets.

git count-objects -v

Cela devrait donner un rapport sur le nombre d'objets décompressés dans votre référentiel, l'espace qu'ils occupent, le nombre de fichiers pack dont vous disposez et l'espace qu'ils occupent.

Idéalement, après un reconditionnement, vous n'auriez aucun objet décompressé et un fichier de pack mais il est parfaitement normal d'avoir des objets qui ne sont pas directement référencés par les branches actuelles toujours présents et décompressés.

Si vous avez un seul gros paquet et que vous voulez savoir ce qui prend de la place, vous pouvez lister les objets qui composent le paquet ainsi que la façon dont ils sont stockés.

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

Notez que verify-packprend un fichier d'index et non le fichier de pack lui-même. Cela donne un rapport de chaque objet dans le pack, sa taille réelle et sa taille emballée ainsi que des informations sur s'il a été `` deltifié '' et si c'est le cas l'origine de la chaîne delta.

Pour voir s'il y a des objets anormalement grands dans votre référentiel, vous pouvez trier la sortie numériquement sur la troisième des quatrième colonnes (par exemple | sort -k3n).

À partir de cette sortie, vous pourrez voir le contenu de n'importe quel objet à l'aide de la git showcommande, bien qu'il ne soit pas possible de voir exactement où dans l'historique de validation du référentiel l'objet est référencé. Si vous devez faire cela, essayez quelque chose à partir de cette question .

CB Bailey
la source
1
Cela a trouvé les gros objets géniaux. La réponse acceptée les a éliminés.
Ian Kelling
2
La différence entre git gc et git repack selon linus torvalds. metalinguist.wordpress.com/2007/12/06/…
spuder
32

Juste pour info, la principale raison pour laquelle vous pouvez vous retrouver avec des objets indésirables conservés est que git maintient un reflog.

Le reflog est là pour sauver vos fesses lorsque vous supprimez accidentellement votre branche principale ou que vous endommagez d'une manière ou d'une autre de manière catastrophique votre référentiel.

Le moyen le plus simple de résoudre ce problème est de tronquer vos reflogs avant de les compresser (assurez-vous simplement de ne jamais vouloir revenir à l'un des commits du reflog).

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

Ceci est différent du fait git gc --prune=todayque tout le reflog expire immédiatement.

John Gietzen
la source
1
Celui-ci l'a fait pour moi! Je suis passé d'environ 5 Go à 32 Mo.
Hawkee
Cette réponse a semblé plus facile à faire mais n'a malheureusement pas fonctionné pour moi. Dans mon cas, je travaillais sur un référentiel juste cloné. Est-ce la raison?
Mert
13

Si vous voulez trouver les fichiers qui prennent de l'espace dans votre référentiel git, exécutez

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

Ensuite, extrayez la référence blob qui occupe le plus d'espace (la dernière ligne) et vérifiez le nom de fichier qui prend autant d'espace

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

Cela peut même être un fichier avec lequel vous avez supprimé git rm, mais git s'en souvient car il y a encore des références, telles que des balises, des télécommandes et des reflog.

Une fois que vous savez de quel fichier vous voulez vous débarrasser, je vous recommande d'utiliser git forget-blob

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

C'est facile à utiliser, il suffit de faire

git forget-blob file-to-forget

Cela supprimera toutes les références de git, supprimera l'objet blob de chaque commit de l'historique et exécutera un garbage collection pour libérer de l'espace.

nachoparker
la source
7

Le script git-fatfiles de la réponse de Vi est charmant si vous voulez voir la taille de tous vos blobs, mais il est si lent qu'il est inutilisable. J'ai supprimé la limite de sortie de 40 lignes et j'ai essayé d'utiliser toute la RAM de mon ordinateur au lieu de finir. Je l'ai donc réécrit: c'est des milliers de fois plus rapide, a ajouté des fonctionnalités (facultatives) et un bogue étrange a été supprimé - l'ancienne version donnerait des comptes inexacts si vous additionnez la sortie pour voir l'espace total utilisé par un fichier.

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

Nommez ce git-fatfiles.pl et exécutez-le. Pour voir l'espace disque utilisé par toutes les révisions d'un fichier, utilisez l' --sumoption. Pour voir la même chose, mais pour les fichiers dans chaque répertoire, utilisez l' --directoriesoption. Si vous installez le numéro :: Octets :: Human module CPAN (run "CPAN :: Nombre Bytes :: humaines"), les tailles seront formatées: "21M /path/to/file.mp4".

piojo
la source
4

Êtes-vous sûr de ne compter que les fichiers .pack et non les fichiers .idx? Ils sont dans le même répertoire que les fichiers .pack, mais ne contiennent aucune donnée du référentiel (comme l'indique l'extension, ce ne sont rien de plus que des index pour le pack correspondant - en fait, si vous connaissez la commande correcte, vous pouvez recréez-les facilement à partir du fichier pack, et git lui-même le fait lors du clonage, car seul un fichier pack est transféré en utilisant le protocole git natif).

En tant qu'échantillon représentatif, j'ai jeté un coup d'œil à mon clone local du référentiel linux-2.6:

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

Ce qui indique qu'une expansion d'environ 7% devrait être courante.

Il y a aussi les fichiers à l'extérieur objects/; d'après mon expérience personnelle, parmi eux indexet gitk.cacheont tendance à être les plus importants (totalisant 11 millions de dollars dans mon clone du dépôt linux-2.6).

CesarB
la source
3

Les autres objets git stockés dans .gitincluent des arbres, des commits et des balises. Les validations et les balises sont petites, mais les arbres peuvent devenir volumineux, en particulier si vous avez un très grand nombre de petits fichiers dans votre référentiel. Combien de fichiers et combien de commits avez-vous?

Greg Hewgill
la source
Bonne question. 19 succursales avec environ 40 fichiers chacune. git count-objects -v dit "in-pack: 1570". Je ne sais pas exactement ce que cela signifie ou comment compter le nombre de commits que j'ai. Quelques centaines, je suppose.
Ian Kelling le
Ok, il ne semble pas que ce soit la réponse alors. Quelques centaines seront insignifiants par rapport à 145 Mo.
Greg Hewgill
2

Avez-vous essayé d'utiliser git repack ?

baudtack
la source
Bonne question. Je l'ai fait, j'ai aussi eu l'impression que git gc fait ça aussi?
Ian Kelling
C'est le cas avec git gc --auto Je ne suis pas sûr de ce que vous avez utilisé.
baudtack
2

avant de faire git filter-branch & git gc, vous devriez revoir 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 non désirés encore référencés par ces balises, par conséquent gc ne peut pas les supprimer et vous vous demanderez toujours pourquoi la taille du dépôt est toujours si grande.

La meilleure façon de se débarrasser de tous les éléments indésirables est d'exécuter git-filter & git gc, puis de pousser master vers un nouveau dépôt nu. Le nouveau repo nu aura l'arbre nettoyé.

v_abhi_v
la source
1

Cela peut arriver si vous avez accidentellement ajouté un gros morceau de fichiers et les avez mis en scène, sans nécessairement les valider. Cela peut se produire dans une railsapplication lorsque vous exécutez bundle install --deploymentet accidentellement git add .alors vous voir tous les fichiers ajoutés sous vendor/bundlevous les désindexer mais ils déjà entré dans l' histoire git, vous devez appliquer la réponse de Vi et le changement video/parasite-intro.avipar vendor/bundlepuis exécutez la deuxième commande qu'il fournit.

Vous pouvez voir la différence avec git count-objects -vlaquelle dans mon cas avant d'appliquer le script avait un pack de taille: de 52K et après l'application, il était de 3,8K.

juliangonzalez
la source
1

Cela vaut la peine de vérifier le stacktrace.log. Il s'agit essentiellement d'un journal des erreurs pour le suivi des commits qui ont échoué. J'ai récemment découvert que mon stacktrace.log était de 65,5 Go et mon application de 66,7 Go.

Nda
la source