なぜ私のgitリポジトリはとても大きいのですか?


141

145M = .git / objects / pack /

各ブランチの先端から戻る前に、各コミットとコミットの違いのサイズを合計するスクリプトを書きました。私は129MBを取得しました。これは、圧縮なしで、ブランチ間で同じファイルやブランチ間の共通の履歴を考慮していません。

Gitはこれらすべてのことを考慮に入れているので、はるかに小さいリポジトリを期待します。では、なぜ.gitはそれほど大きいのでしょうか。

終わったよ:

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

いくつのファイル/コミットについて回答するために、私はそれぞれに40のファイルについて19のブランチを持っています。287コミット、以下を使用して見つかりました:

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

これに関する情報を格納するのに数十メガバイトは必要ありません。


5
Linusは、アグレッシブなGCよりも以下を推奨しています。大きな違いはありますか?git repack -a -d --depth = 250 --window = 250
Greg Bacon

gbaconに感謝しますが、違いはありません。
イアンケリング

これは、-fがないためです。metalinguist.wordpress.com/2007/12/06/...
spuder

git repack -a -d私の956MBリポジトリを250MBに縮小しました。大成功!ありがとう!
xanderiel

回答:


68

最近、間違ったリモートリポジトリをローカルリポジトリにプルしました(git remote add ...およびgit remote update)。不要なリモート参照、ブランチ、タグを削除した後も、リポジトリに1.4GB(!)の無駄なスペースが残っていました。私はそれをクローンすることによってこれを取り除くことができましたgit clone file:///path/to/repositoryfile://ローカルリポジトリのクローンを作成する場合、によって違いが生じることに注意してください。コピーされるのは、ディレクトリ構造全体ではなく、参照されるオブジェクトのみです。

編集:これは、新しいリポジトリのすべてのブランチを再作成するためのIanの1つのライナーです。

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 = 15Mになりました!! 複製後、以前のブランチを保持するための小さなライナーがここにあります。d1 =#original repo; d2 =#new repo; cd $ d1; $(gitブランチ|カット-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 dirはその後巨大になり、これがそれを取り戻す唯一の方法でした。しかし、私は1つのライナーが私のブランチ名を変更する方法が好きではありませんでした(ブランチ名だけではなくorigin / branchnameが表示されていました)。それで、私はさらに一歩進んでいくつかの大ざっぱな手術を実行しました-.git / objectsディレクトリを元のディレクトリから削除し、クローンから1つ入れました。これでトリックが行われ、元のブランチ、参照などはすべてそのまま残り、すべてが機能しているように見えます(クロスフィンガー)。
Jack Senechal、2011年

1
file://クローンに関するヒントをありがとう、それは私のためのトリックをしました
adam.wulf

3
@vonbrandファイルにハードリンクして元のファイルを削除した場合、参照カウンターが2から1に減らされる以外は何も起こりません。そのカウンターが0に減らされた場合のみ、fs上の他のファイルのためにスペースが解放されます。したがって、ファイルがハードリンクされていても、元のファイルが削除されても何も起こりません。
stefreak 2013年

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

注:2番目のスクリプトは、Gitから情報を完全に削除するように設計されています(reflogからのすべての情報を含む)。注意して使用してください。


2
最後に...皮肉なことに、この検索の前半でこの回答を見つけましたが、複雑すぎるように見えました...他のことを試した後、これは理にかなって、ほらほらし始めました!
msanteler 2014

@msanteler、前者の(git-fatfiles)スクリプトは、IRC(Freenode /#git)で質問したときに出てきました。最高のバージョンをファイルに保存し、回答としてここに投稿しました。(ただし、IRCログの元の作成者はできません)。
Vi。

これは最初は非常にうまく機能します。しかし、リモートから再度フェッチまたはプルすると、すべての大きなファイルがアーカイブにコピーされます。それを防ぐにはどうすればよいですか?
2015年

1
@felbo、問題はおそらくローカルリポジトリだけでなく、他のリポジトリにもあります。多分あなたはどこでも手順を実行する必要があるか、誰もが元のブランチを放棄して強制的に書き換えられたブランチに切り替える必要があります。大きなチームでは簡単ではなく、開発者間の協力やマネージャーの介入が必要です。場合によっては、ロードストーンをそのままにしておく方が良いオプションになることがあります。
Vi。

1
この機能は素晴らしいですが、想像以上に遅いです。40行の制限を削除すると、コンピューターで終了することもできません。参考までに、この関数のより効率的なバージョンを使用して回答を追加しました。このロジックを大きなリポジトリで使用する場合、またはファイルごとまたはフォルダーごとに合計されたサイズを確認する場合は、確認してください。
ピオジョ2017

66

git gcすでにを行っているgit repackので、特別なオプションを渡さない限り、手動で再パックする意味はありません。

最初のステップは、スペースの大部分が(通常の場合と同様に)オブジェクトデータベースであるかどうかを確認することです。

git count-objects -v

これにより、リポジトリ内に存在するパックされていないオブジェクトの数、オブジェクトが占めるスペース、パックファイルの数、オブジェクトが占めるスペースのレポートが表示されます。

理想的には、再パック後、アンパックされたオブジェクトと1つのパックファイルがないことになりますが、現在のブランチによって直接参照されていないオブジェクトがまだ存在し、アンパックされていることは完全に正常です。

単一の大きなパックがあり、何がスペースを占めているのかを知りたい場合は、パックを構成するオブジェクトとそれらの格納方法を一覧表示できます。

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

ご了承ください verify-packは、パックファイル自体ではなく、インデックスファイル取ります。これにより、パック内のすべてのオブジェクトのレポート、その実際のサイズとパックされたサイズ、および「デリティファイ」されているかどうか、そうである場合はデルタチェーンの起源に関する情報が提供されます。

リポジトリに異常に大きいオブジェクトがあるかどうかを確認するには、4番目の列の3番目の列(など| sort -k3n)で出力を数値で並べ替えます。

この出力から、git showコマンドを使用してオブジェクトのコンテンツを確認できますが、リポジトリのコミット履歴のどこでオブジェクトが参照されているかを正確に確認することはできません。これを行う必要がある場合は、この質問から何かを試してください。


1
これにより、大きなオブジェクトが素晴らしいことがわかりました。受け入れられた答えはそれらを取り除きました。
イアンケリング

2
linus torvaldsによるgit gcとgit repackの違い。metalinguist.wordpress.com/2007/12/06/...
spuder

31

ちなみに、不要なオブジェクトが保持されることになる最大の理由は、gitがreflogを保持していることです。

reflogは、誤ってマスターブランチを削除した場合や、何らかの理由でリポジトリに壊滅的なダメージを与えた場合に、バットを保存するためにあります。

これを修正する最も簡単な方法は、圧縮する前にreflogを切り捨てることです(reflogのコミットに戻らないようにしてください)。

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

これはgit gc --prune=today、reflog全体がすぐに期限切れになるという点で異なります。


1
これは私のためにやった!約5GBから32MBになりました。
Hawkee 2016

この答えは簡単に思えましたが、残念ながらうまくいきませんでした。私の場合、私はちょうどクローンされたリポジトリで作業していました。それが理由ですか?
2017

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、で削除したファイルである可能性もありますが、タグ、リモート、reflogなどの参照が残っているため、gitはそれを記憶しています。

あなたが取り除きたいどのファイルを知ったら、私は使用することをお勧めします 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

.idxファイルではなく.packファイルのみを数えていますか?これらは.packファイルと同じディレクトリにありますが、リポジトリデータはありません(拡張子が示すように、これらは対応するパックのインデックスにすぎません。実際、正しいコマンドがわかっている場合は、ネイティブ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は「in-pack:1570」と表示します。それが何を意味するのか、私がコミットをいくつカウントするのか正確にはわかりません。数百と思います。
イアンケリング

OK、それがその時の答えのようには聞こえません。145 MBと比較すると、数百は重要ではありません。
グレッグヒューギル


2

git filter-branch&git gcを実行する前に、リポジトリに存在するタグを確認する必要があります。継続的インテグレーションやデプロイなどの自動タグ付けを備えた実際のシステムでは、未使用のオブジェクトがこれらのタグによって引き続き参照されるため、gcはそれらを削除できず、リポジトリのサイズが依然として大きい理由について疑問を抱き続けます。

不要なものをすべて取り除く最善の方法は、git-filterとgit gcを実行してから、マスターを新しいベアリポジトリにプッシュすることです。新しい裸のレポはクリーンアップされたツリーになります。


1

これは、大量のファイルを誤って追加してステージングした場合に発生する可能性があり、必ずしもコミットする必要はありません。これは、中に発生する可能性がありrailsますが、実行時にアプリbundle install --deployment、その後、偶然git add .、あなたは下のすべてのファイルが追加表示さvendor/bundleあなたが適用する必要がありますので、あなたがそれらをunstageしかし、彼らはすでにgitの歴史に入ったViにの答えと変化する video/parasite-intro.aviことにより、vendor/bundle、その後、彼が提供する2番目のコマンドを実行します。

あなたはとの違いを確認できgit count-objects -v52Kの、それは3.8Kだっ適用後:スクリプトはサイズ・パックを持っていた適用する前に、私の場合はこれを。


1

stacktrace.logを確認する価値があります。これは基本的に、失敗したコミットのトレースのエラーログです。最近、stacktrace.logが65.5GBで、アプリが66.7GBであることがわかりました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.