在git回购中find超过x兆字节的文件,这在HEAD中不存在

我有一个Git仓库,我随机存储了大部分的随机脚本,文本文件,我devise的网站等等。

有一些大的二进制文件随着时间的推移(一般为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

这是我以前发布的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"; } } 

更简洁的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) 

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 

哎呀…第一个脚本(亚里士多德),是非常缓慢的。 在git.git回购,寻找文件> 100K,它咀嚼CPU约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"; 

你想要使用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通常比运行git-filter-branch快10-50倍,并且这些选项是围绕这两个常见的用例而定制的:

  • 删除疯狂的大文件
  • 删除密码,证件和其他私人数据

充分披露:我是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 "$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 

资源

我的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 

这个bash“单行”显示了存储库中所有大于10MB的blob对象,并且不存在于从最小到最大sorting的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 

有关更多信息,包括更适合进一步脚本处理的输出格式,请参阅我对类似问题的原始答案 。

晚会有点晚,但是git-fat有这个function。

只需用pip安装它,然后运行git fat -a find 100000 ,最后的数字是Bytes。