为什么我的git仓库这么大?


141

145M = .git / objects / pack /

我编写了一个脚本来累加每个提交和每个提交的差异的大小,然后再从每个分支的顶端向后退。我得到129MB,没有压缩,也没有考虑分支之间的相同文件和分支之间的通用历史记录。

Git考虑了所有这些因素,因此我希望存储库要小得多。那么.git为什么这么大?

我弄完了:

git fsck --full
git gc --prune=today --aggressive
git repack

要回答多少文件/提交,我有19个分支,每个分支约40个文件。287次提交,使用以下方法找到:

git log --oneline --all|wc -l

存储有关此信息的时间不应超过10兆字节。


5
Linus建议以下而不是积极的gc。它有很大的不同吗?git repack -a -d --depth = 250 --window = 250
Greg Bacon

谢谢gbacon,但没有区别。
伊恩·凯灵


git repack -a -d将我的956MB存储库缩小到250MB。巨大的成功!谢谢!
xanderiel 2015年

Answers:


68

我最近将错误的远程存储库拉到了本地存储库(git remote add ...git remote update)中。删除不需要的远程引用,分支和标签后,我的存储库中仍然有1.4GB(!)浪费的空间。我只能通过用克隆它来摆脱它git clone file:///path/to/repository。请注意,file://克隆本地存储库时会产生很大的变化-仅复制引用的对象,而不复制整个目录结构。

编辑:这是伊恩(Ian)在新仓库中重新创建所有分支的班轮:

d1=#original repo
d2=#new repo (must already exist)
cd $d1
for b in $(git branch | cut -c 3-)
do
    git checkout $b
    x=$(git rev-parse HEAD)
    cd $d2
    git checkout -b $b $x
    cd $d1
done

1
哇。谢谢。.git = 1500万!!克隆后,这里有一些衬纸,用于保存您以前的分支。d1 =#原始回购;d2 =#new repo; cd $ d1; 对于$(git branch | cut -c 3-)中的b;git checkout $ b; x = $(git rev-parse HEAD); cd $ d2; git checkout -b $ b $ x; cd $ d1; 完成
伊恩·凯灵

如果选中此选项,则可以在答案中添加1个衬纸,使其格式设置为代码。
伊恩·凯灵

1
我愚蠢地将一堆视频文件添加到我的仓库中,不得不重置--soft HEAD ^并重新提交。此后,.git / objects目录很大,这是使它退回的唯一方法。但是,我不喜欢那种划线员在改变我的分支名称的方式(它显示的是起源/分支名称,而不仅仅是分支名称)。因此,我又走了一步,进行了一些粗略的手术-我从原始目录中删除了.git / objects目录,并从克隆目录中放入了它。这样就达到了目的,使所有原始分支,引用等保持不变,并且一切似乎正常(交叉手指)。
Jack Senechal 2011年

1
感谢您提供有关file://克隆的技巧,这对我
有用

3
@vonbrand如果您硬链接到文件并删除原始文件,则除了将引用计数器从2减为1之外,什么都不会发生。只有当该计数器减为0时,fs上的其他文件才可以释放空间。因此,即使文件被硬链接,删除原始文件也不会发生任何事情。
stefreak

157

我使用的一些脚本:

git-fatfiles

git rev-list --all --objects | \
    sed -n $(git rev-list --objects --all | \
    cut -f1 -d' ' | \
    git cat-file --batch-check | \
    grep blob | \
    sort -n -k 3 | \
    tail -n40 | \
    while read hash type size; do 
         echo -n "-e s/$hash/$size/p ";
    done) | \
    sort -n -k1
...
89076 images/screenshots/properties.png
103472 images/screenshots/signals.png
9434202 video/parasite-intro.avi

如果需要更多行,请参见附近的答案中的Perl版本:https : //stackoverflow.com/a/45366030/266720

git-eradicate(用于video/parasite.avi):

git filter-branch -f  --index-filter \
    'git rm --force --cached --ignore-unmatch video/parasite-intro.avi' \
     -- --all
rm -Rf .git/refs/original && \
    git reflog expire --expire=now --all && \
    git gc --aggressive && \
    git prune

注意:第二个脚本旨在从Git完全删除信息(包括reflog中的所有信息)。请谨慎使用。


2
最后,具有讽刺意味的是,我在搜索中较早地看到了这个答案,但是看起来太复杂了……尝试了其他方法之后,这个答案就变得有意义了,瞧!
msanteler 2014年

@msanteler,git-fatfiles当我在IRC(Freenode /#git)上提问时,前一个()脚本出现了。我将最佳版本保存到文件中,然后将其发布为答案。(尽管我无法在IRC日志中显示原始作者)。
六。

最初这很好用。但是,当我再次从远程获取或拉回时,它只是将所有大文件复制回存档中。我该如何预防?
2015年

1
@felbo,那么问题可能不仅存在于您的本地存储库中,还存在于其他存储库中。也许您需要在任何地方执行该过程,或者强迫每个人放弃原始分支并切换到重写的分支。在大型团队中这并不容易,需要开发人员和/或经理干预之间的合作。有时,将负载石留在内部可能是更好的选择。
六。

1
这个功能很棒,但是速度却非常慢。如果取消40行限制,它甚至无法在我的计算机上完成。仅供参考,我刚刚添加了一个答案,其中包含此功能的更有效版本。如果要在大型存储库上使用此逻辑,或者要查看每个文件或每个文件夹的总大小,请检查一下。
piojo

66

git gc已经进行了,git repack所以手动重新包装没有任何意义,除非您要向其传递一些特殊选项。

第一步是查看大多数空间是否是对象数据库(通常是这种情况)。

git count-objects -v

这应该提供一个报告,说明存储库中有多少个未打包的对象,它们占用了多少空间,打包文件有多少以及它们占用了多少空间。

理想情况下,重新打包后,您将没有解包的对象和一个打包文件,但是完全保留存在并解包的当前分支没有直接引用的某些对象是完全正常的。

如果您只有一个大包装,并且想知道什么占用了空间,那么您可以列出构成包装的对象以及它们的存储方式。

git verify-pack -v .git/objects/pack/pack-*.idx

请注意,verify-pack它使用索引文件而不是包文件本身。这将提供有关包装中每个对象,其真实大小和包装大小的报告,以及有关是否已“脱粒”以及是否为三角链起源的信息。

要查看存储库中是否有异常大的对象,可以在第四列的第三列(例如| sort -k3n)上对输出进行数字排序。

从该输出中,您将可以使用git show命令查看任何对象的内容,尽管无法确切看到对象在存储库的提交历史记录中的位置。如果您需要这样做,请尝试从该问题中尝试一些事情。


1
这发现大物件很棒。被接受的答案摆脱了他们。
伊恩·凯灵

2
git gc和git repack之间的差异取决于linus torvalds。metalinguist.wordpress.com/2007/12/06/...
spuder

30

仅供参考,您最终可能会保留不需要的对象的最大原因是git维护了reflog。

当您不小心删除主分支或以其他方式对存储库造成灾难性破坏时,reflog可以保存您的屁股。

解决此问题的最简单方法是在压缩之前截断reflog(只需确保您永远不想回到reflog中的任何提交)。

git gc --prune=now --aggressive
git repack

这与git gc --prune=today立即使整个reflog失效的不同。


1
这个为我做到了!我从大约5GB到32MB。
Hawkee

这个答案似乎更容易实现,但不幸的是对我没有用。就我而言,我正在开发一个刚刚克隆的存储库。那是原因吗?
Mert

13

如果要查找哪些文件正在git存储库中占用空间,请运行

git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5

然后,提取占用最多空间的blob引用(最后一行),并检查占用那么多空间的文件名

git rev-list --objects --all | grep <reference>

这甚至可能是您使用删除的文件git rm,但是git会记住它,因为仍然有对该文件的引用,例如标记,远程和reflog。

一旦知道要删除的文件,我建议使用 git forget-blob

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

易于使用,只需做

git forget-blob file-to-forget

这将从git中删除每个引用,从历史记录中的每个提交中删除blob,然后运行垃圾回收以释放空间。


7

如果您想查看所有blob的大小,Vi的答案中的git-fatfiles脚本很不错,但是它是如此之慢以至于无法使用。我取消了40行的输出限制,它试图使用计算机的所有RAM而不是完成内存。因此,我重写了它:速度提高了数千倍,增加了功能(可选),并且删除了一些奇怪的错误-如果您对输出求和以查看文件使用的总空间,则旧版本的计数将不准确。

#!/usr/bin/perl
use warnings;
use strict;
use IPC::Open2;
use v5.14;

# Try to get the "format_bytes" function:
my $canFormat = eval {
    require Number::Bytes::Human;
    Number::Bytes::Human->import('format_bytes');
    1;
};
my $format_bytes;
if ($canFormat) {
    $format_bytes = \&format_bytes;
}
else {
    $format_bytes = sub { return shift; };
}

# parse arguments:
my ($directories, $sum);
{
    my $arg = $ARGV[0] // "";
    if ($arg eq "--sum" || $arg eq "-s") {
        $sum = 1;
    }
    elsif ($arg eq "--directories" || $arg eq "-d") {
        $directories = 1;
        $sum = 1;
    }
    elsif ($arg) {
        print "Usage: $0 [ --sum, -s | --directories, -d ]\n";
        exit 1;
    } 
}

# the format is [hash, file]
my %revList = map { (split(' ', $_))[0 => 1]; } qx(git rev-list --all --objects);
my $pid = open2(my $childOut, my $childIn, "git cat-file --batch-check");

# The format is (hash => size)
my %hashSizes = map {
    print $childIn $_ . "\n";
    my @blobData = split(' ', <$childOut>);
    if ($blobData[1] eq 'blob') {
        # [hash, size]
        $blobData[0] => $blobData[2];
    }
    else {
        ();
    }
} keys %revList;
close($childIn);
waitpid($pid, 0);

# Need to filter because some aren't files--there are useless directories in this list.
# Format is name => size.
my %fileSizes =
    map { exists($hashSizes{$_}) ? ($revList{$_} => $hashSizes{$_}) : () } keys %revList;


my @sortedSizes;
if ($sum) {
    my %fileSizeSums;
    if ($directories) {
        while (my ($name, $size) = each %fileSizes) {
            # strip off the trailing part of the filename:
            $fileSizeSums{$name =~ s|/[^/]*$||r} += $size;
        }
    }
    else {
        while (my ($name, $size) = each %fileSizes) {
            $fileSizeSums{$name} += $size;
        }
    }

    @sortedSizes = map { [$_, $fileSizeSums{$_}] }
        sort { $fileSizeSums{$a} <=> $fileSizeSums{$b} } keys %fileSizeSums;
}
else {
    # Print the space taken by each file/blob, sorted by size
    @sortedSizes = map { [$_, $fileSizes{$_}] }
        sort { $fileSizes{$a} <=> $fileSizes{$b} } keys %fileSizes;

}

for my $fileSize (@sortedSizes) {
    printf "%s\t%s\n", $format_bytes->($fileSize->[1]), $fileSize->[0];
}

将其命名为git-fatfiles.pl并运行它。要查看文件的所有修订版使用的磁盘空间,请使用该--sum选项。要查看相同的内容,但对于每个目录中的文件,请使用--directories选项。如果您安装Number :: Bytes :: Human cpan模块(运行“ cpan Number :: Bytes :: Human”),则大小将被格式化为:“ 21M /path/to/file.mp4”。


4

您确定只计算.pack文件而不是.idx文件吗?它们与.pack文件位于同一目录中,但没有任何存储库数据(如扩展名所示,它们只不过是相应包的索引-实际上,如果您知道正确的命令,则可以可以很容易地从打包文件中重新创建它们,克隆时git会自己完成,因为只有打包文件是使用本地git协议传输的。

作为一个代表性示例,我看了一下linux-2.6存储库的本地克隆:

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

这表明大约有7%的增长是很普遍的。

外面也有文件objects/; 在我个人的经验,他们indexgitk.cache往往是最大的人(我在linux-2.6库的克隆共计11M)。


3

存储在.git其中的其他git对象包括树,提交和标签。提交和标签很小,但是如果存储库中有大量小文件,则树会变得很大。您有多少个文件和多少个提交?


好问题。19个分支,每个分支约40个文件。git count-objects -v表示“内装:1570”。不确定到底是什么意思或如何计算我的提交次数。我猜有几百个。
伊恩·凯林

好的,这听起来不是答案。相比145 MB,几百个微不足道。
Greg Hewgill 09年


2

在执行git filter-branch和git gc之前,您应该查看存储库中存在的标签。任何具有自动标记功能(例如持续集成和部署)的真实系统都会使未使用的对象仍然受到这些标记的干扰,因此gc无法删除它们,您仍然会怀疑为什么repo的大小仍然如此之大。

摆脱所有不需要的东西的最好方法是运行git-filter和git gc,然后将master推送到一个新的裸仓库。新的裸仓库将具有清理过的树。


1

如果您意外添加了大量文件并将其暂存,而不必提交它们,则可能发生这种情况。这可能会在rails运行时在应用程序中发生,bundle install --deployment然后不经意间便会git add .看到vendor/bundle您添加到其下的所有文件都被取消登台,但它们已经进入git历史记录,因此您必须应用Vi的答案 video/parasite-intro.avi通过以下方式进行更改vendor/bundle然后运行他提供的第二个命令。

您可以看到git count-objects -v在我的情况下,在应用脚本之前,其size-pack为52K,而在应用之后为3.8K,这是有区别的。


1

值得检查stacktrace.log。基本上,这是一个错误日志,用于跟踪失败的提交。我最近发现我的stacktrace.log是65.5GB,我的应用程序是66.7GB。

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.