57 votes

Trouver les fichiers dans le repo git de plus de x mégaoctets, qui n'existent pas dans HEAD

J'ai un dépôt Git dans lequel je stocke des choses aléatoires. Principalement des scripts aléatoires, des fichiers texte, des sites web que j'ai conçus, etc.

Il y a quelques gros fichiers binaires que j'ai supprimés au fil du temps (généralement de 1 à 5 Mo), qui augmentent la taille du référentiel et dont je n'ai pas besoin dans l'historique des révisions.

En gros, je veux être capable de faire

me@host:~$ [magic command or script]
aad29819a908cc1c05c3b1102862746ba29bafc0 : example/blah.psd : 3.8MB : 130 days old
6e73ca29c379b71b4ff8c6b6a5df9c7f0f1f5627 : another/big.file : 1.12MB : 214 days old

puis être en mesure de passer en revue chaque résultat, en vérifiant s'il n'est plus nécessaire et en le supprimant (probablement en utilisant l'option filter-branch )

53voto

Aristotle Pagaltzis Points 43253

Il s'agit d'une adaptation de le site git-find-blob script que j'ai posté précédemment. :

#!/usr/bin/perl
use 5.008;
use strict;
use Memoize;

sub usage { die "usage: git-large-blob <size[b|k|m]> [<git-log arguments ...>]\n" }

@ARGV or usage();
my ( $max_size, $unit ) = ( shift =~ /^(\d+)([bkm]?)\z/ ) ? ( $1, $2 ) : usage();

my $exp = 10 * ( $unit eq 'b' ? 0 : $unit eq 'k' ? 1 : 2 );
my $cutoff = $max_size * 2**$exp; 

sub walk_tree {
    my ( $tree, @path ) = @_;
    my @subtree;
    my @r;

    {
        open my $ls_tree, '-|', git => 'ls-tree' => -l => $tree
            or die "Couldn't open pipe to git-ls-tree: $!\n";

        while ( <$ls_tree> ) {
            my ( $type, $sha1, $size, $name ) = /\A[0-7]{6} (\S+) (\S+) +(\S+)\t(.*)/;
            if ( $type eq 'tree' ) {
                push @subtree, [ $sha1, $name ];
            }
            elsif ( $type eq 'blob' and $size >= $cutoff ) {
                push @r, [ $size, @path, $name ];
            }
        }
    }

    push @r, walk_tree( $_->[0], @path, $_->[1] )
        for @subtree;

    return @r;
}

memoize 'walk_tree';

open my $log, '-|', git => log => @ARGV, '--pretty=format:%T %h %cr'
    or die "Couldn't open pipe to git-log: $!\n";

my %seen;
while ( <$log> ) {
    chomp;
    my ( $tree, $commit, $age ) = split " ", $_, 3;
    my $is_header_printed;
    for ( walk_tree( $tree ) ) {
        my ( $size, @path ) = @$_;
        my $path = join '/', @path;
        next if $seen{ $path }++;
        print "$commit $age\n" if not $is_header_printed++;
        print "\t$size\t$path\n";
    }
}

3 votes

J'ai des difficultés à comprendre ce code. Avez-vous des exemples de l'utilisation de votre belle commande ?

3 votes

aha. pas d'arguments. il a juste fallu un certain temps pour qu'il affiche quelque chose à l'écran. git-large-blob 500k

1 votes

Je recommande la réponse de Mislav plutôt que celle-ci. Il m'a donné des réponses plus précises.

44voto

mislav Points 7379

Ruby script plus compact :

#!/usr/bin/env ruby -w
head, treshold = ARGV
head ||= 'HEAD'
Megabyte = 1000 ** 2
treshold = (treshold || 0.1).to_f * Megabyte

big_files = {}

IO.popen("git rev-list #{head}", 'r') do |rev_list|
  rev_list.each_line do |commit|
    commit.chomp!
    for object in `git ls-tree -zrl #{commit}`.split("\0")
      bits, type, sha, size, path = object.split(/\s+/, 5)
      size = size.to_i
      big_files[sha] = [path, size, commit] if size >= treshold
    end
  end
end

big_files.each do |sha, (path, size, commit)|
  where = `git show -s #{commit} --format='%h: %cr'`.chomp
  puts "%4.1fM\t%s\t(%s)" % [size.to_f / Megabyte, path, where]
end

Utilisation :

ruby big_file.rb [rev] [size in MB]
$ ruby big_file.rb master 0.3
3.8M  example/blah.psd  (aad2981: 4 months ago)
1.1M  another/big.file  (6e73ca2: 2 weeks ago)

3 votes

C'est une excellente réponse, mais elle comporte un défaut. Les gros objets sont stockés dans le hash big_files qui utilise sha comme clé unique. En théorie, tout va bien - chaque objet blob est unique après tout. Cependant, dans la pratique, il est concevable que vous ayez exactement le même fichier à plusieurs endroits dans votre référentiel. Par exemple, il peut s'agir d'un fichier de test qui nécessite différents noms de fichiers mais pas différents contenu physique . Les problèmes surviennent lorsque vous voyez un gros objet avec un chemin dont vous n'avez pas besoin mais qu'à votre insu, ce même fichier existe quelque part ailleurs où il est nécessaire.

1 votes

@ben.snape j'ai ajouté cette ligne juste avant big_files[sha] = ... pour que je puisse au moins savoir quand ça arrive : warn "Another path for #{sha} is #{path}" if big_files.has_key? sha and big_files[sha][0] != path

1 votes

J'ai modifié ce script pour qu'il soit plus adapté aux gros dépôts : ne pas traiter plus de 1000 commits, afficher une sortie dans la console pendant le travail, éviter une erreur dans git show pour les fichiers qui ne sont pas dans l'arbre de travail : gist.github.com/victor-homyakov/690cd2991c77539ca4fe

17voto

SigTerm Points 16055

Python script pour faire la même chose (sur la base de ce poste ) :

#!/usr/bin/env python

import os, sys

def getOutput(cmd):
    return os.popen(cmd).read()

if (len(sys.argv) <> 2):
    print "usage: %s size_in_bytes" % sys.argv[0]
else:
    maxSize = int(sys.argv[1])

    revisions = getOutput("git rev-list HEAD").split()

    bigfiles = set()
    for revision in revisions:
        files = getOutput("git ls-tree -zrl %s" % revision).split('\0')
        for file in files:
            if file == "":
                continue
            splitdata = file.split()
            commit = splitdata[2]
            if splitdata[3] == "-":
                continue
            size = int(splitdata[3])
            path = splitdata[4]
            if (size > maxSize):
                bigfiles.add("%10d %s %s" % (size, commit, path))

    bigfiles = sorted(bigfiles, reverse=True)

    for f in bigfiles:
        print f

0 votes

Pour les gros fichiers, il est préférable de faire simplement bigfiles = sorted(set(bigfiles), reverse=True) . Ou, mieux encore, commencez par bigfiles = set() et utiliser bigfiles.add au lieu de bigfiles.append .

0 votes

Oui, mais maintenant il n'y a plus besoin de set Encore une fois ! :)

0 votes

Cela a très bien fonctionné et je connais python donc c'était ma préférence.

6voto

Aïe... ce premier script (par Aristote), est assez lent. Sur le repo git.git, en cherchant des fichiers > 100k, il mâche le CPU pendant environ 6 minutes.

Il semble également que plusieurs SHAs erronés soient imprimés -- souvent un SHA sera imprimé qui n'a rien à voir avec le nom de fichier mentionné dans la ligne suivante.

Voici une version plus rapide. Le format de sortie est différent, mais c'est très rapide, et c'est aussi -- pour autant que je puisse dire -- correct.

Le programme est un peu plus long mais beaucoup de verbiage.

#!/usr/bin/perl
use 5.10.0;
use strict;
use warnings;

use File::Temp qw(tempdir);
END { chdir( $ENV{HOME} ); }
my $tempdir = tempdir( "git-files_tempdir.XXXXXXXXXX", TMPDIR => 1, CLEANUP => 1 );

my $min = shift;
$min =~ /^\d+$/ or die "need a number";

# ----------------------------------------------------------------------

my @refs =qw(HEAD);
@refs = @ARGV if @ARGV;

# first, find blob SHAs and names (no sizes here)
open( my $objects, "-|", "git", "rev-list", "--objects", @refs) or die "rev-list: $!";
open( my $blobfile, ">", "$tempdir/blobs" ) or die "blobs out: $!";

my ( $blob, $name );
my %name;
my %size;
while (<$objects>) {
    next unless / ./;    # no commits or top level trees
    ( $blob, $name ) = split;
    $name{$blob} = $name;
    say $blobfile $blob;
}
close($blobfile);

# next, use cat-file --batch-check on the blob SHAs to get sizes
open( my $sizes, "-|", "< $tempdir/blobs git cat-file --batch-check | grep blob" ) or die "cat-file: $!";

my ( $dummy, $size );
while (<$sizes>) {
    ( $blob, $dummy, $size ) = split;
    next if $size < $min;
    $size{ $name{$blob} } = $size if ( $size{ $name{$blob} } || 0 ) < $size;
}

my @names_by_size = sort { $size{$b} <=> $size{$a} } keys %size;

say "
The size shown is the largest that file has ever attained.  But note
that it may not be that big at the commit shown, which is merely the
most recent commit affecting that file.
";

# finally, for each name being printed, find when it was last updated on each
# branch that we're concerned about and print stuff out
for my $name (@names_by_size) {
    say "$size{$name}\t$name";

    for my $r (@refs) {
        system("git --no-pager log -1 --format='%x09%h%x09%x09%ar%x09$r' $r -- $name");
    }
    print "\n";
}
print "\n";

6voto

Roberto Tyley Points 4352

Vous voulez utiliser le BFG Repo-Cleaner une alternative plus rapide et plus simple à git-filter-branch spécialement conçu pour enlever des fichiers volumineux à partir des dépôts Git.

Télécharger le Bocal BFG (nécessite Java 6 ou plus) et lancez cette commande :

$ java -jar bfg.jar  --strip-blobs-bigger-than 1M  my-repo.git

Tout fichier d'une taille supérieure à 1M (qui n'est pas dans votre répertoire de fichiers) doit être supprimé. dernier site commit) sera supprimé de l'historique de votre dépôt Git. Vous pouvez alors utiliser git gc pour nettoyer les données mortes :

$ git gc --prune=now --aggressive

Le BFG est généralement 10-50x plus rapide que la course git-filter-branch et les options sont adaptées à ces deux cas d'utilisation courants :

  • Suppression de Crazy Big Files
  • Suppression de Mots de passe, informations d'identification et autres Données privées

Révélation complète : je suis l'auteur du BFG Repo-Cleaner.

0 votes

@quetzalcoatl yup, le BFG a trois homonymes : 1) l'arme BFG, 2) le Grand Géant Amical, et 3) ...ce sont les initiales de git-filter-branch, écrites à l'envers :-) Croyez-moi, le BFG est une arme splendide pour la destruction de données !

8 votes

BFG est très bien pour supprimer réellement les fichiers, mais comment savoir dont seront supprimés ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X