515

我有一个 300 MB 的 git 存储库。我当前签出的文件的总大小为 2 MB,其余 git repo 的总大小为 298 MB。这基本上是一个不超过几 MB 的纯代码仓库。

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

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

4

13 回答 13

1122

超快的外壳单线

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

对于我的示例存储库,它的运行速度比此处找到的其他存储库快约100 倍。
在我信赖的 Athlon II X4 系统上,它可以在一分钟内处理包含 560 万个对象的Linux 内核存储库

基本脚本

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 问题

了解显示的文件大小的含义

该脚本显示的是每个文件在工作目录中的大小。如果您想查看一个文件在未签出的情况下占用了多少空间,您可以%(objectsize:disk)使用%(objectsize). 但是,请注意,该指标也有其注意事项,如文档中所述。

更复杂的尺寸统计

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

因此,如果这里的脚本没有为您剪裁(并且您有一个相当新的 git 版本),请查看git-filter-repo --analyzegit rev-list --disk-usage示例)。

于 2017-03-02T00:53:13.617 回答
184

我在ETH Zurich Department of Physics 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“查找大文件”对话框

于 2013-12-16T11:23:20.410 回答
172

在过去,我发现此脚本对于在 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 的提交。

于 2012-05-16T16:01:20.903 回答
36

步骤 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

来源

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

编辑

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

于 2013-12-08T22:39:36.113 回答
17

您应该使用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
于 2014-03-11T18:45:18.073 回答
14

如果您只想拥有大文件的列表,那么我想为您提供以下单行:

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删除历史记录中需要的内容。

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

于 2015-10-07T13:05:19.680 回答
7

如果您使用的是 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
于 2016-05-14T23:19:04.460 回答
5

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
于 2020-05-22T19:52:47.680 回答
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
于 2018-11-15T05:23:10.083 回答
3

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

关键概念

在 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

要删除此类 blob,请使用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 个虚拟内核:

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

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

于 2019-12-10T15:51:02.550 回答
3

对于 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.

于 2021-03-16T10:32:50.050 回答
0

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

从分析、验证和选择根本原因开始。用来git-repo-analysis帮忙。

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

于 2017-05-26T11:38:06.470 回答
0

出于与其他人相同的原因,我偶然发现了这一点。但是引用的脚本对我来说不太适用。我已经制作了一个更像是我见过的那些的混合体,它现在住在这里 - https://gitlab.com/inorton/git-size-calc

于 2018-01-25T22:03:06.983 回答