Найдите файлы в Git repo более x мегабайт, которые не существуют в HEAD
у меня есть репозиторий Git, в котором я храню случайные вещи. В основном случайные скрипты, текстовые файлы, веб-сайты, которые я разработал и так далее.
есть некоторые большие двоичные файлы, которые я удалил с течением времени (как правило, 1-5MB), которые сидят вокруг увеличения размера репозитория, который мне не нужен в истории ревизий.
в основном я хочу быть в состоянии сделать..
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 ответов:
Это адаптация the
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/ ) ? ( , ) : 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"; } }
более компактный Руби скрипт:
#!/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("") 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)
скрипт на 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('') 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
Оуч... этот первый сценарий (Аристотель) довольно медленный. На мерзавца.git repo, ища файлы > 100k, он жует процессор около 6 минут.
Он также, похоже, имеет несколько неправильных shas-часто будет напечатан 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";
вы хотите использовать BFG Repo-Cleaner, более быстрая и простая альтернатива
git-filter-branch
специально разработан для удаления большие файлы из репозиториев Git.скачать BFG jar (требуется Java 6 или выше) и выполните следующую команду:
$ java -jar bfg.jar --strip-blobs-bigger-than 1M my-repo.git
любые файлы размером более 1 м (которые не находятся в вашем последний commit) будет удален из истории вашего репозитория Git. Затем вы можете использовать
git gc
чтобы убрать мертвых данные:$ git gc --prune=now --aggressive
BFG является, как правило,10-50x быстрее, чем работает
git-filter-branch
и параметры адаптированы вокруг этих двух общих случаев использования:
- удаление Сумасшедшие Большие Файлы
- удаление Пароли, Учетные Данные и другие личные данные
полное раскрытие информации: я автор BFG Repo-Cleaner.
сценарий Аристота покажет вам, что вы хотите. Вы также должны знать, что удаленные файлы все еще занимают место в репо.
по умолчанию 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-отличный способ ознакомиться с тем, как он работает.
#!/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 "" ] 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
мой питон упрощение 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(''): 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
этот bash "однострочный" отображает все объекты blob в репозитории, которые больше 10 MiB и не присутствуют в
HEAD
сортировка от самого маленького до самого большого.Это очень быстро, легко копировать и вставлять и требует только стандартных утилит GNU.
git rev-list --objects --all \ | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \ | awk -v min_mb=10 '/^blob/ && >= min_mb*2^20 {print substr(,6)}' \ | grep -vF "$(git ls-tree -r HEAD | awk '{print }')" \ | 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
для получения дополнительной информации, включая формат вывода, более подходящий для дальнейшей обработки сценария, см. my оригинал ответ по аналогичному вопросу.
немного опоздал на вечеринку, но git-fat имеет эту встроенную функциональность.
просто установите его с помощью pip и запустите
git fat -a find 100000
где число в конце-это в байтах.