Comment trouver / identifier de grands commits dans l'histoire de Git?

366

J'ai un dépôt git de 300 Mo. La taille totale de mes fichiers actuellement extraits est de 2 Mo et la taille totale du reste du dépôt git est de 298 Mo. Il s'agit essentiellement d'un dépôt uniquement en code qui ne devrait pas dépasser quelques Mo.

Je soupçonne que quelqu'un a accidentellement commis de gros fichiers (vidéo, images, etc.), puis les a supprimés ... mais pas de git, donc l'historique contient toujours de gros fichiers inutiles. Comment trouver les gros fichiers dans l'historique git? Il y a plus de 400 commits, donc un par un n'est pas pratique.

REMARQUE : ma question n'est pas de savoir comment supprimer le fichier , mais comment le trouver en premier lieu.

un pantalon
la source

Réponses:

143

J'ai trouvé ce script très utile dans le passé pour trouver des objets volumineux (et non évidents) dans un référentiel 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 ', '

Cela vous donnera le nom d'objet (SHA1sum) du blob, puis vous pouvez utiliser un script comme celui-ci:

... pour trouver le commit qui pointe vers chacun de ces blobs.

Mark Longair
la source
31
Cette réponse a été vraiment utile, car elle m'a envoyé au message ci-dessus. Alors que le script de la publication fonctionnait, je l'ai trouvé douloureusement lent. Je l'ai donc réécrit, et c'est maintenant beaucoup plus rapide sur les grands référentiels. Jetez un œil: gist.github.com/nk9/b150542ef72abc7974cb
Nick K9
7
Veuillez inclure des instructions complètes dans vos réponses et pas seulement des liens hors site; Que faisons-nous lorsque stubbisms.wordpress.com tombe inévitablement en panne?
ThorSummoner
@ NickK9 intéressant, j'obtiens une sortie différente de votre script et de l'autre. il y a un tas d'objets plus gros que le vôtre semble manquer. Y a-t-il quelque chose qui me manque?
UpAndAdam
Oh cool! Merci d'avoir accéléré mon script @nick \ k9: D @UpAndAdam, dites-vous que mon script a produit une sortie incorrecte?
Antony Stubbs
1
Ces commentaires donnent l'impression que nous signalons la taille en octets, mais j'obtiens des kilo-octets.
Kat
685

🚀 Une doublure de coque incroyablement rapide 🚀

Ce script shell affiche tous les objets blob dans le référentiel, triés du plus petit au plus grand.

Pour mon échantillon repo, il a fonctionné environ 100 fois plus vite que les autres trouvés ici.
Sur mon fidèle système Athlon II X4, il gère le référentiel Linux Kernel avec ses 5,6 millions d'objets en un peu plus d'une minute .

Le script de 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

Lorsque vous exécutez le code ci-dessus, vous obtiendrez une belle sortie lisible par l'homme comme ceci:

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

Utilisateurs de macOS : Étant donné qu'il numfmtn'est pas disponible sur macOS, vous pouvez soit omettre la dernière ligne et gérer les tailles d'octets brutes, soit brew install coreutils.

Filtration

Pour obtenir un filtrage supplémentaire , insérez l'une des lignes suivantes avant la sortligne .

Pour exclure les fichiers présents dansHEAD , insérez la ligne suivante:

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

Pour n'afficher que les fichiers dépassant la taille donnée (par exemple 1 Mio = 2 20  B), insérez la ligne suivante:

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

Sortie pour ordinateurs

Pour générer une sortie mieux adaptée à un traitement ultérieur par les ordinateurs, omettez les deux dernières lignes du script de base. Ils font tout le formatage. Cela vous laissera quelque chose comme ceci:

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

Suppression de fichiers

Pour la suppression réelle du fichier, consultez cette question SO sur le sujet .

raphinesse
la source
14
Cela mérite plus que mon vote positif! Remerciements spéciaux pour fournir à la fois une sortie lisible par ordinateur et par l'homme.
Michel Jung
2
C'est extrêmement rapide et facile à utiliser!
Chin
32
Pour l'utiliser sur Mac, vous devez brew install coreutilspuis remplacer cutpar gcutet numfmtpar gnumfmt.
Nick Sweeting
2
Permettez-moi de souligner à nouveau - c'est beaucoup plus rapide que toutes les autres listes que j'ai vues.
Sridhar Sarnobat
4
cela fait un super alias git :) git largequelqu'un?
anarcat
160

J'ai trouvé une solution monoplace sur la page wiki du Département de physique de l'ETH Zurich (près de la fin de cette page). Faites juste un git gcpour enlever les déchets périmés, puis

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

vous donnera les 10 plus gros fichiers du référentiel.

Il existe également une solution plus paresseuse maintenant disponible, GitExtensions a maintenant un plugin qui le fait dans l'interface utilisateur (et gère également les réécritures de l'historique).

Boîte de dialogue GitExtensions 'Rechercher des fichiers volumineux'

skolima
la source
8
Ce one-liner ne fonctionne que si vous souhaitez obtenir le fichier le plus volumineux (c'est-à-dire utiliser la queue -1). Les sauts de ligne gênent tout ce qui est plus gros. Vous pouvez utiliser sed pour convertir les nouvelles lignes afin que grep joue bien: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'`
Throctukes
10
grep: a70783fca9bfbec1ade1519a41b6cc4ee36faea0: Aucun fichier ou répertoire de ce type
Jonathan Allard
1
Le lien wiki déplacé vers: readme.phys.ethz.ch/documentation/git_advanced_hints
outsmartin
11
Trouver GitExtensions, c'est comme trouver le pot d'or et la fin de l'arc-en-ciel - merci!
ckapilla
3
Existe-t-il également une extension qui imprime la taille des fichiers?
Michael
27

Étape 1 Écrivez tous les fichiers SHA1 dans un fichier texte:

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

Étape 2 Triez les blobs du plus grand au plus petit et écrivez les résultats dans un fichier texte:

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

Étape 3a Combinez les deux fichiers texte pour obtenir le nom du fichier / sha1 / informations sur la taille:

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;

Étape 3b Si vous avez des noms de fichiers ou des chemins contenant des espaces, essayez cette variante de l'étape 3a. Il utilise cutau lieu de awkpour obtenir les colonnes souhaitées incl. espaces de la colonne 7 à la fin de la ligne:

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;

Vous pouvez maintenant consulter le fichier bigtosmall.txt afin de décider quels fichiers vous souhaitez supprimer de votre historique Git.

Étape 4 Pour effectuer la suppression (notez que cette partie est lente car elle va examiner chaque commit de votre historique pour les données sur le fichier que vous avez identifié):

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

La source

Les étapes 1 à 3a ont été copiées à partir de la recherche et de la purge de gros fichiers de l'historique Git

ÉDITER

L'article a été supprimé au cours du second semestre 2017, mais une copie archivée de celui-ci est toujours accessible à l'aide de Wayback Machine .

friederbluemle
la source
6
Un paquebot pour faire la même chose: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, merci pour le one-liner! Il ne gère pas les noms de fichiers avec des espaces en eux, cela semble: 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. Notez que vous devez entrer le caractère TAB réel après join -t'avec CTRL + V <TAB> per geekbraindump.blogspot.ru/2009/04/unix-join-with-tabs.html
Nickolay
2
@Nickolay avec bash $'\t'devrait vous donner un onglet. echo -n $'\t' | xxd -ps->09
Iwan Aucamp
1
@IwanAucamp: encore mieux, merci pour le conseil! (Dommage que je ne puisse pas modifier le commentaire précédent .. eh bien.)
Nickolay
1
@ Sridhar-Sarnobat L'article a été enregistré par la Wayback Machine! :) web.archive.org/web/20170621125743/http://www.naleid.com/blog/…
friederbluemle
18

Vous devez utiliser BFG Repo-Cleaner .

Selon le site Internet:

Le BFG est une alternative plus simple et plus rapide à git-filter-branch pour nettoyer les mauvaises données de l'historique de votre référentiel Git:

  • Suppression de gros fichiers fous
  • Suppression des mots de passe, des informations d'identification et d'autres données privées

La procédure classique pour réduire la taille d'un référentiel serait:

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
Warren Seine
la source
4
BFG Repo-Cleaner est très bon. Il s'éclaircit rapidement et fonctionne de manière très fiable.
fschmitt
30
Cela ne vous dit cependant pas comment répertorier tous les fichiers les plus volumineux.
Andi Jay
5
Le problème est que vous ne pouvez pas simplement voir quels sont les gros fichiers sans les supprimer. Je ne me sens pas à l'aise de le faire sans un premier essai qui répertorie simplement les gros fichiers.
Sridhar Sarnobat
Que fait --strip-biggest-blobs 500-il?
2540625
git rejettera les modifications apportées par cet outil.
Christopher
9

Si vous voulez seulement avoir une liste de fichiers volumineux, alors je voudrais vous fournir le one-liner suivant:

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

Dont la sortie sera:

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

La dernière entrée de la liste pointe vers le plus gros fichier de votre historique git.

Vous pouvez utiliser cette sortie pour vous assurer que vous ne supprimez pas avec BFG des éléments dont vous auriez eu besoin dans votre historique.

schmijos
la source
2
Impressionnant!! Cependant, vous devez noter que vous devez cloner le référentiel avec les options --mirror avant d'exécuter cette commande.
Andi Jay
Je suis curieux, à quoi servent les 1.1, 1.2, 2.3chiffres?
Ympostor
Les nombres sont une liste de <filenumber>.<field>spécification de l'ordre de la combinaison. Voir man.cx/join pour plus d'informations.
schmijos
6

Si vous êtes sous Windows, voici un script PowerShell qui imprimera les 10 plus gros fichiers de votre référentiel:

$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
Julia Schwarz
la source
1
Cela produit une réponse différente de @raphinesse, manquant un tas des plus gros fichiers sur mon référentiel. De plus, lorsqu'un fichier volumineux comporte de nombreuses modifications, seule la plus grande taille est indiquée.
kristianp
Ce script a échoué pour moi, avec l'erreur: You cannot call a method on a null-valued expression. At line: 2 char: 1. Cependant, cette réponse a fonctionné: stackoverflow.com/a/57793716/2441655 (elle est également plus courte)
Venryx
4

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

Nous utilisons la commande ci-dessous dans notre pipeline CI, elle s'arrête si elle trouve des gros fichiers dans le dépôt 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
Vojtech Vitek
la source
2

Je n'ai pas pu utiliser la réponse la plus populaire car le --batch-checkcommutateur de ligne de commande vers Git 1.8.3 (que je dois utiliser) n'accepte aucun argument. Les étapes suivantes ont été essayées sur CentOS 6.5 avec Bash 4.1.2

Concepts clés

Dans Git, le terme blob implique le contenu d'un fichier. Notez qu'un commit peut changer le contenu d'un fichier ou d'un nom de chemin. Ainsi, le même fichier peut faire référence à un autre blob en fonction de la validation. Un certain fichier peut être le plus gros de la hiérarchie de répertoires dans un commit, mais pas dans un autre. Par conséquent, la question de trouver des commits volumineux au lieu de fichiers volumineux met les choses dans la bonne perspective.

Pour les impatients

La commande pour imprimer la liste des blobs dans l'ordre décroissant de taille est:

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

Exemple de sortie:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e blob 305971200
7c357f2c2a7b33f939f9b7125b155adbd7890be2 blob 289163620

Pour supprimer de tels blobs, utilisez le BFG Repo Cleaner , comme mentionné dans d'autres réponses. Étant donné un fichier blobs.txtqui contient uniquement les hachages de blob, par exemple:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e
7c357f2c2a7b33f939f9b7125b155adbd7890be2

Faire:

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

La question est de trouver les commits, ce qui est plus de travail que de trouver des blobs. Pour le savoir, lisez la suite.

La poursuite des travaux

Étant donné un hachage de validation, une commande qui affiche les hachages de tous les objets qui lui sont associés, y compris les blobs, est:

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

Donc, si nous avons de telles sorties disponibles pour toutes les validations dans le référentiel, étant donné un hachage de blob, le groupe de validations sont celles qui correspondent à l'une des sorties. Cette idée est encodée dans le script suivant:

#!/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

Si le contenu est enregistré dans un fichier nommé find-commits.shalors une invocation typique sera comme sous:

cat blobs.txt | find-commits.sh

Comme précédemment, le fichier blobs.txtrépertorie les hachages d'objets blob, un par ligne. La create_db()fonction enregistre un cache de toutes les listes de commit dans un sous-répertoire du répertoire courant.

Quelques statistiques de mes expériences sur un système avec deux processeurs Intel (R) Xeon (R) CPU E5-2620 2.00GHz présentés par l'OS comme 24 cœurs virtuels:

  • Nombre total de commits dans le repo = près de 11 000
  • Vitesse de création de fichiers = 126 fichiers / s. Le script crée un seul fichier par commit. Cela se produit uniquement lorsque le cache est créé pour la première fois.
  • Surcharge de création de cache = 87 s.
  • Vitesse de recherche moyenne = 522 commits / s. L'optimisation du cache a entraîné une réduction de 80% du temps d'exécution.

Notez que le script est monothread. Par conséquent, un seul cœur serait utilisé à la fois.

pdp
la source
2

Solution Powershell pour Windows Git, trouvez les plus gros fichiers:

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
Aaron
la source
0

Comment puis-je retrouver les gros fichiers dans l'historique git?

Commencez par analyser, valider et sélectionner la cause première. Utilisez git-repo-analysispour aider.

Vous pouvez également trouver de la valeur dans les rapports détaillés générés par BFG Repo-Cleaner , qui peuvent être exécutés très rapidement en clonant vers une gouttelette Digital Ocean en utilisant leur débit réseau de 10 Mo / s.

Josh Habdas
la source
Je pense que vous avez une bonne réponse générale dans la suggestion de BFG, mais vous la gâtez en ne donnant aucun détail, puis en suggérant d'utiliser un service tiers différent (également sans aucune explication). Pouvez-vous en nettoyer certains pour fournir un exemple en ligne de commande de cette utilisation de BFG?
phord
0

Je suis tombé dessus pour la même raison que n'importe qui d'autre. Mais les scripts cités ne fonctionnaient pas tout à fait pour moi. J'en ai fait un qui est plus un hybride de ceux que j'ai vus et il vit maintenant ici - https://gitlab.com/inorton/git-size-calc

IanNorton
la source