如何在git历史记录中查找/识别大型提交?


365

我有一个300 MB的git回购。我当前已签出文件的总大小为2 MB,其余git repo的总大小为298 MB。这基本上是一个仅代码的回购,不应超过几个MB。

我怀疑有人不小心提交了一些大文件(视频,图像等),然后将它们删除了……但不是从git中删除,因此历史记录中仍然包含无用的大文件。如何在git历史记录中找到大文件?有超过400次提交,因此一步一步进行是不实际的。

注意:我的问题不是关于如何删除文件,而是如何首先找到它。



Answers:


143

我发现该脚本在过去非常有用,可以在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 ', '

这将为您提供Blob的对象名称(SHA1sum),然后您可以使用如下脚本:

...查找指向每个blob的提交。


31
这个答案确实很有帮助,因为它使我发到了上面的帖子。虽然帖子的脚本有效,但我发现它非常缓慢。因此,我重写了它,现在在大型存储库中,它的运行速度显着提高。看看:gist.github.com/nk9/b150542ef72abc7974cb
尼克K9

7
请在答案中包括完整的说明,而不仅仅是站外链接;如果stubbisms.wordpress.com不可避免地掉线,我们该怎么办?
ThorSummoner 2014年

@ NickK9有趣的是,我从您的脚本和其他脚本中获得了不同的输出。您似乎错过了一堆更大的物体。有什么我想念的吗?
UpAndAdam

太酷了!感谢您使我的脚本更快@nick \ k9:D @UpAndAdam,您是说我的脚本产生了错误的输出吗?
安东尼·斯塔布斯

1
这些注释使我们听起来好像在报告字节大小,但我得到了千字节。

680

shell极快的外壳单线🚀

此shell脚本显示存储库中的所有blob对象,从最小到最大排序。

对于我的示例仓库,它的运行速度比此处找到的其他仓库快100倍
在我值得信赖的Athlon II X4系统上,它在一分钟内即可处理拥有560万个对象的Linux Kernel存储库

基本脚本

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

当您运行上述代码时,您将获得类似以下内容的易于阅读的输出

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

macOS用户:由于numfmt在macOS上不可用,因此您可以省略最后一行并处理原始字节大小或brew install coreutils

筛选

要实现进一步过滤,请在该行之前sort插入以下任意

排除中存在的文件HEAD,请插入以下行:

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

仅显示超过给定大小的文件(例如1 MiB = 2 20  B),请插入以下行:

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

电脑输出

要生成更适合计算机进一步处理的输出,请省略基本脚本的最后两行。他们进行所有格式化。这将使您具有以下内容:

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

文件删除

对于实际的文件删除,请查看关于主题的该SO问题


14
这不仅值得我支持!特别感谢您提供计算机和人类可读的输出。
Michel Jung

2
这是非常快速且易于使用的!
Chin

31
要在Mac上使用此,你需要brew install coreutils再更换cutgcutnumfmtgnumfmt
Nick Sweeting

2
让我再强调一遍-这比我所看到的所有其他列表都快得多。
Sridhar Sarnobat

4
这使真棒git别名:) git large任何人?
anarcat

160

我已经在苏黎世联邦理工学院物理系的wiki页面(接近该页面的结尾)上找到了一种单线解决方案。只需执行一次操作git gc即可删除陈旧的垃圾,然后

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

将为您提供存储库中最大的10个文件。

现在还有一个懒惰的解决方案GitExtensions现在有一个插件可以在UI中执行此操作(并处理历史重写)。

GitExtensions的“查找大文件”对话框


8
仅当您要获取最大的单个文件(即使用tail -1)时,该单线才有效。换行符会妨碍更大的事情。您可以使用sed转换换行符,以便grep可以很好地发挥作用: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:无此类文件或目录
Jonathan Allard


11
找到GitExtensions就像找到黄金罐和彩虹的尽头-谢谢!
ckapilla

3
是否有一个扩展名可以打印文件的大小?
迈克尔(Michael)

27

步骤1将所有文件SHA1写入文本文件:

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

步骤2将Blob从最大到最小排序,然后将结果写入文本文件:

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

步骤3a合并两个文本文件以获得文件名/ sha1 /大小信息:

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;

步骤3b如果您的文件名或路径名包含空格,请尝试此步骤3a的变体。它使用cut而不是awk获取所需的列,包括。从第7列到行尾的空格:

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;

现在,您可以查看文件bigtosmall.txt,以确定要从Git历史记录中删除的文件。

步骤4要执行删除(请注意,此部分很慢,因为它将检查历史记录中的每个提交以获取有关所标识文件的数据):

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

资源

从从Git历史记录查找和清除大文件中复制了步骤1-3a

编辑

该文章在2017年下半年的某个时候被删除,但仍可以使用Wayback Machine访问该文章的存档副本


6
一个班轮做同样的事情: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
伊万·奥坎普

1
@Iwan,谢谢你的一线!它不处理包含空格的文件名,这似乎是: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。请注意,join -t'每个geekbraindump.blogspot.ru/2009/04/unix-join-with-tabs.html
Nickolay

2
@Nickolay和bash $'\t'应该会给您一个标签。echo -n $'\t' | xxd -ps->09
Iwan Aucamp

1
@IwanAucamp:更好,谢谢你的提示!(太糟糕了,我无法编辑以前的评论。哦,很好。)
Nickolay 2015年

1
@ Sridhar-Sarnobat该文章由Wayback Machine保存!:)web.archive.org/web/20170621125743/http://www.naleid.com/blog/…–
friederbluemle

18

您应该使用BFG Repo-Cleaner

根据网站:

BFG是git-filter-branch的一种更简单,更快的替代方法,用于从Git存储库历史记录中清除不良数据:

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

减少存储库大小的经典过程是:

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

4
BFG Repo-Cleaner非常好。它闪电般迅速,并且工作非常可靠。
fschmitt

30
但是,这并不能告诉您如何列出所有最大的文件。
安迪·杰伊

5
问题是您不能只查看什么是大文件而没有实际删除它们。如果没有先列出大文件的空运行,我对此感到不舒服。
Sridhar Sarnobat

怎么--strip-biggest-blobs 500办?
2540625

git将拒绝此工具所做的更改。
克里斯托弗

9

如果您只想列出大文件,那么我想为您提供以下内容:

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

其输出将是:

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

列表中的最后一个条目指向git历史记录中最大的文件。

您可以使用此输出来确保您不会删除历史记录中需要的BFG内容。


2
太棒了!! 但是,您应该注意,在运行此命令之前,需要使用--mirror选项克隆存储库。
安迪·杰伊

我很好奇,这是什么1.1, 1.2, 2.3数字?
ympostor

这些数字是<filenumber>.<field>指定组合顺序的列表。有关更多信息,请参见man.cx/join
schmijos

6

如果您使用的是Windows,则以下是一个PowerShell脚本,它将打印存储库中最大的10个文件:

$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

1
这会产生与@raphinesse不同的答案,会丢失我存储库中的一堆最大文件。同样,当一个大文件进行了大量修改时,只会报告最大的文件。
kristianp

该脚本对我而言失败,并显示错误:You cannot call a method on a null-valued expression. At line: 2 char: 1。但是,此答案有效:stackoverflow.com/a/57793716/2441655(它也更短)
Venryx

4

尝试 git ls-files | xargs du -hs --threshold=1M

我们在CI管道中使用以下命令,如果它在git repo中发现任何大文件,它将停止:

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

2

我无法使用最受欢迎的答案,因为 --batch-check命令行切换到Git 1.8.3(必须使用)不接受任何参数。在带有Bash 4.1.2的CentOS 6.5上尝试了以下步骤

关键概念

在Git中,术语blob表示文件的内容。请注意,提交可能会更改文件或路径名的内容。因此,根据提交,同一文件可以引用不同的Blob。某个文件在一次提交中可能是目录层次结构中最大的文件,而在另一次提交中则不是。因此,查找大型提交而不是大型文件的问题将问题放在了正确的角度。

对于急躁的人

按大小降序打印blob列表的命令是:

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

样本输出:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e blob 305971200
7c357f2c2a7b33f939f9b7125b155adbd7890be2 blob 289163620

要删除此类斑点,请使用BFG Repo清洁剂,如其他答案所述。给定一个blobs.txt仅包含斑点哈希的文件,例如:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e
7c357f2c2a7b33f939f9b7125b155adbd7890be2

做:

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

问题是关于查找提交,这比查找b​​lob还要多。要知道,请继续阅读。

进一步的工作

给定一个提交哈希,打印与之关联的所有对象(包括斑点)的哈希的命令为:

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

因此,如果我们为仓库中的所有提交提供了这样的输出,那么给定一个blob哈希,那一堆提交就是与任何输出匹配的提交。这个想法编码在以下脚本中:

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

如果内容保存在名为的文件中,find-commits.sh则典型的调用如下:

cat blobs.txt | find-commits.sh

如前所述,该文件blobs.txt列出了斑点哈希,每行列出一个。该create_db()函数将所有提交列表的缓存保存在当前目录的子目录中。

根据我在具有两个Intel®Xeon®CPU E5-2620 2.00GHz处理器的系统上进行的实验得出的一些统计信息,该操作系统由24个虚拟内核提供:

  • 回购中的提交总数=接近11,000
  • 文件创建速度= 126个文件/秒。该脚本为每个提交创建一个文件。仅在首次创建缓存时才会发生这种情况。
  • 缓存创建开销= 87 s。
  • 平均搜索速度= 522次提交/秒。缓存优化使运行时间减少了80%。

请注意,脚本是单线程的。因此,任何时候只能使用一个内核。


2

Windows git的Powershell解决方案,找到最大的文件:

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

0

如何跟踪git历史记录中的大文件?

首先分析,验证和选择根本原因。采用git-repo-analysis提供帮助。

您还可以在BFG Repo-Cleaner生成的详细报告中找到一些价值,这些报告可以通过使用其10MiB / s的网络吞吐量克隆到Digital Ocean液滴来快速运行。


我认为您在BFG建议中有一个不错的一般答案,但您由于不提供任何详细信息,然后建议使用其他第三方服务(也没有任何解释)而破坏了它。您可以清理一些内容以提供此BFG用法的命令行示例吗?
16:30嘲讽

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.