如何在git历史中找到/识别大提交?

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

我怀疑有人不小心提交了一些大文件(视频、图像等),然后删除了它们……但不是从git,所以历史仍然包含无用的大文件。如何在git历史中找到大文件?有400多个提交,所以一个接一个的提交是不实际的。

请注意:我的问题不是关于如何删除文件,但如何< em >找到< / em >在第一个地方。

178831 次浏览

我发现这个脚本在过去在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的提交。

将所有sha1文件写入一个文本文件:

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

将blobs从大到小进行排序,并将结果写入文本文件:

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

合并两个文本文件,得到文件名/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;

如果您的文件名或路径名包含空间,请尝试步骤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历史记录中删除哪些文件。

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

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

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

编辑

这篇文章在2017年下半年的某个时候被删除了,但它的存档副本仍然可以使用机器Wayback访问。

我在苏黎世联邦理工学院物理系维基页面(接近该页的末尾)上找到了一个一行程序解决方案。只需执行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 '查找大文件'对话框”></p></div>
                                                                            </div>
                                </div>
                            </div>
                        </div>
                                                <div class=

您应该使用高炉煤气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

如果你只想要一个大文件的列表,那么我想为你提供以下一行代码:

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历史中最大的文件。

您可以使用此输出来确保没有删除历史记录中使用高炉煤气的内容。

请注意,您需要使用--mirror克隆存储库才能正常工作。

如果你在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

🚀一个极其快速的shell一行程序🚀

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

对于我的示例回购,它比这里找到的其他回购运行了快100倍
在我可靠的Athlon II X4系统上,它处理Linux内核存储库及其在一分钟多一点中的560万个对象

基础脚本

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 = 220. 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问题

理解显示文件大小的含义

这个脚本显示的是工作目录中每个文件的大小。如果您想查看未签出的文件占用了多少空间,可以使用%(objectsize:disk)而不是%(objectsize)。然而,请注意,这个指标也有它的警告,正如在文档中提到的。

更复杂的大小统计信息

有时,一个大文件列表不足以找出问题所在。例如,您不会发现包含大量小文件的目录或分支。

因此,如果这里的脚本不适合你(并且你有一个不错的最新版本的git),请查看git-filter-repo --analyzegit rev-list --disk-usage (例子)。

如何在git历史记录中追踪大文件?

从分析、确认和选择根本原因开始。使用git-repo-analysis来帮助。

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

我偶然发现这个的原因和其他人一样。但是引用的脚本并不适合我。我做了一个更像是我见过的那些的混合体,它现在生活在这里- https://gitlab.com/inorton/git-size-calc

# EYZ0试试。

我们在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

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

关键概念

在Git中,术语表示文件的内容。请注意,提交可能会更改文件或路径名的内容。因此,根据提交的不同,同一个文件可以引用不同的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 Cleaner,正如在其他答案中提到的那样。给定一个文件blobs.txt,它只包含blob散列,例如:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e
7c357f2c2a7b33f939f9b7125b155adbd7890be2

做的事:

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

这个问题是关于查找提交的,这比查找blob要复杂得多。要知道,请继续往下读。

进一步的工作

给定一个提交哈希值,打印与之相关的所有对象(包括blob)的哈希值的命令是:

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

所以,如果我们在repo中所有提交都有这样的输出,那么给定一个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列出了一行一个的blob散列。create_db()函数将所有提交清单的缓存保存在当前目录的子目录中。

我在一个系统上做了一些实验,这个系统有两个Intel(R) Xeon(R) CPU E5-2620 2.00GHz处理器,由操作系统提供24个虚拟核:

  • 在repo中提交的总数=近11000
  • 文件创建速度= 126个文件/秒。该脚本每次提交创建一个文件。这只在第一次创建缓存时发生。
  • 缓存创建开销= 87秒。
  • 平均搜索速度= 522次提交/秒。缓存优化使运行时间减少了80%。

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

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

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

对于Windows,我写了一个Powershell版本的这个答案:

function Get-BiggestBlobs {
param ([Parameter(Mandatory)][String]$RepoFolder, [int]$Count = 10)
Write-Host ("{0} biggest files:" -f $Count)
git -C $RepoFolder rev-list --objects --all | git -C $RepoFolder cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | ForEach-Object {
$Element = $_.Trim() -Split '\s+'
$ItemType = $Element[0]
if ($ItemType -eq 'blob') {
New-Object -TypeName PSCustomObject -Property @{
ObjectName = $Element[1]
Size = [int]([int]$Element[2] / 1kB)
Path = $Element[3]
}
}
} | Sort-Object Size | Select-Object -last $Count | Format-Table ObjectName, @{L='Size [kB]';E={$_.Size}}, Path -AutoSize
}

您可能需要根据自己的情况微调它是显示kB还是MB,还是仅显示Bytes。

可能存在性能优化的潜力,因此,如果您担心这一点,可以自由地进行试验。

要获得所有更改,只需省略| Select-Object -last $Count
要获得更便于机器阅读的版本,只需省略| Format-Table @{L='Size [kB]';E={$_.Size}}, Path -AutoSize.

像这样使用git-filter-repo--analyze特性:

$ cd my-repo-folder
$ git-filter-repo --analyze
$ less .git/filter-repo/analysis/path-all-sizes.txt

为了对“差异大小”有个感觉;git历史中的最后一次提交

git log --stat

这将以行为单位显示差异大小:添加的行,删除的行