Найти файлы в git repo размером более x мегабайт, которых нет в HEAD - PullRequest
57 голосов
/ 18 ноября 2008

У меня есть Git-репозиторий, в котором я храню случайные вещи. В основном случайные скрипты, текстовые файлы, веб-сайты, которые я разработал, и т. Д.

Существуют некоторые большие двоичные файлы, которые я удалял с течением времени (обычно 1-5 МБ), которые увеличивают размер хранилища, что мне не нужно в истории ревизий.

В основном я хочу быть в состоянии сделать ..

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

.. затем можно просмотреть каждый результат, проверяя, не нужен ли он, а затем удалить его (возможно, используя filter-branch)

Ответы [ 10 ]

53 голосов
/ 18 ноября 2008

Это адаптация сценария git-find-blob, который я разместил ранее :

#!/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";
    }
}
44 голосов
/ 30 октября 2011

Более компактный скрипт ruby:

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

Использование:

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)
15 голосов
/ 11 апреля 2012

Python-скрипт для того же действия (на основе этого поста ):

#!/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
6 голосов
/ 06 апреля 2013

Вы хотите использовать BFG Repo-Cleaner , более быструю и простую альтернативу git-filter-branch, специально предназначенную для удаления больших файлов из репозиториев Git.

Загрузите BFG jar (требуется Java 6 или выше) и выполните эту команду:

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

Любые файлы размером более 1M (которых нет в вашем последнем коммите) будут удалены из истории вашего репозитория Git. Затем вы можете использовать git gc для удаления мертвых данных:

$ git gc --prune=now --aggressive

BFG, как правило, 10-50x быстрее, чем работает git-filter-branch, и параметры настраиваются в соответствии с этими двумя распространенными вариантами использования:

  • Удаление Сумасшедшие большие файлы
  • Удаление Пароли, учетные данные и другие Личные данные

Полное раскрытие: я являюсь автором репо-уборщика BFG.

6 голосов
/ 13 января 2012

Ой ... этот первый сценарий (Аристотель) довольно медленный. В репозитории git.git, ища файлы размером более 100 Кб, он жует процессор примерно за 6 минут.

По-видимому, на нем также напечатано несколько неправильных SHA - часто будет напечатан SHA, который не имеет ничего общего с именем файла, упомянутым в следующей строке.

Вот более быстрая версия. Формат вывода другой, но он очень быстрый, и, насколько я могу судить, он также правильный.

Программа немного длиннее , но во многом это словоблудие.

#!/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";
4 голосов
/ 19 ноября 2008

Сценарий Аристота покажет вам, что вы хотите. Вы также должны знать, что удаленные файлы все еще будут занимать место в репо.

По умолчанию Git хранит изменения в течение 30 дней, прежде чем их можно будет собирать мусором. Если вы хотите удалить их сейчас:

$ git reflog expire --expire=1.minute refs/heads/master
     # all deletions up to 1 minute  ago available to be garbage-collected
$ git fsck --unreachable 
     # lists all the blobs(file contents) that will be garbage-collected 
$ git prune 
$ git gc

Дополнительный комментарий: Хотя я большой поклонник Git, Git не дает никаких преимуществ для хранения вашей коллекции «случайных скриптов, текстовых файлов, веб-сайтов» и двоичных файлов. Git отслеживает изменения в содержимом, особенно историю скоординированных изменений во многих текстовых файлах, и делает это очень эффективно и результативно. Как показывает ваш вопрос, Git не имеет хороших инструментов для отслеживания изменений отдельных файлов. И он не отслеживает изменения в двоичных файлах, поэтому любая ревизия сохраняет еще одну полную копию в репозитории.

Конечно, использование Git - отличный способ познакомиться с его работой.

3 голосов
/ 16 мая 2012
#!/bin/bash
if [ "$#" != 1 ]
then
  echo 'git large.sh [size]'
  exit
fi

declare -A big_files
big_files=()
echo printing results

while read commit
do
  while read bits type sha size path
  do
    if [ "$size" -gt "$1" ]
    then
      big_files[$sha]="$sha $size $path"
    fi
  done < <(git ls-tree --abbrev -rl $commit)
done < <(git rev-list HEAD)

for file in "${big_files[@]}"
do
  read sha size path <<< "$file"
  if git ls-tree -r HEAD | grep -q $sha
  then
    echo $file
  fi
done

Источник

1 голос
/ 07 сентября 2017

Этот bash "one-liner" отображает все объекты BLOB-объектов в хранилище, размер которых превышает 10 МБ и которые отсутствуют в HEAD, отсортированном от наименьшего к наибольшему.

Это очень быстро , легко копировать и вставлять и требует только стандартные утилиты GNU.

git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk -v min_mb=10 '/^blob/ && $3 >= min_mb*2^20 {print substr($0,6)}' \
| grep -vF "$(git ls-tree -r HEAD | awk '{print $3}')" \
| sort --numeric-sort --key=2 \
| cut --complement --characters=13-40 \
| numfmt --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

Будет сгенерировано следующее:

2ba44098e28f   12MiB path/to/hires-image.png
bd1741ddce0d   63MiB path/to/some-video-1080p.mp4

Для получения дополнительной информации, включая формат вывода, более подходящий для дальнейшей обработки сценария, см. Мой оригинальный ответ на аналогичный вопрос.

1 голос
/ 27 апреля 2012

My python упрощение https://stackoverflow.com/a/10099633/131881

#!/usr/bin/env python
import os, sys

bigfiles = []
for revision in os.popen('git rev-list HEAD'):
    for f in os.popen('git ls-tree -zrl %s' % revision).read().split('\0'):
        if f:
            mode, type, commit, size, path = f.split(None, 4)
            if int(size) > int(sys.argv[1]):
                bigfiles.append((int(size), commit, path))

for f in sorted(set(bigfiles)):
    print f
0 голосов
/ 11 сентября 2014

Немного опоздал на вечеринку, но git-fat имеет эту встроенную функциональность.

Просто установите его с помощью pip и запустите git fat -a find 100000, где число в конце указано в байтах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...