Tại sao kho git của tôi rất lớn?


141

145M = .git / đối tượng / gói /

Tôi đã viết một kịch bản để thêm kích thước của sự khác biệt của từng cam kết và cam kết trước khi nó đi ngược lại từ đầu của mỗi nhánh. Tôi nhận được 129 MB, không nén và không tính các tệp giống nhau trên các nhánh và lịch sử chung giữa các nhánh.

Git đưa tất cả những điều đó vào tài khoản vì vậy tôi mong đợi kho lưu trữ nhỏ hơn nhiều. Vậy tại sao .git lại lớn như vậy?

Tôi đã thực hiện:

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

Để trả lời về số lượng tệp / cam kết, tôi có 19 nhánh khoảng 40 tệp trong mỗi tệp. 287 cam kết, được tìm thấy bằng cách sử dụng:

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

Không nên lấy 10 megabyte để lưu trữ thông tin về việc này.


5
Linus đề nghị sau đây trên gc tích cực. Liệu nó có làm nên sự khác biệt đáng kể? git repack -a -d --depth = 250 --window = 250
Greg Bacon

cảm ơn gbacon, nhưng không có sự khác biệt
Ian Kelling

Đó là bởi vì bạn đang thiếu -f. metalinguist.wordpress.com/2007/12/06/...
spuder

git repack -a -dthu hẹp repo 956MB của tôi xuống còn 250MB . Thành công lớn! Cảm ơn!
xanderiel

Câu trả lời:


68

Gần đây tôi đã kéo sai kho lưu trữ từ xa vào kho cục bộ ( git remote add ...git remote update). Sau khi xóa ref từ xa không mong muốn, các nhánh và thẻ tôi vẫn còn 1,4GB (!) Dung lượng bị lãng phí trong kho lưu trữ của mình. Tôi chỉ có thể thoát khỏi điều này bằng cách nhân bản nó với git clone file:///path/to/repository. Lưu ý rằng file://thế giới tạo ra sự khác biệt khi nhân bản một kho lưu trữ cục bộ - chỉ các đối tượng được tham chiếu được sao chép, không phải toàn bộ cấu trúc thư mục.

Chỉnh sửa: Đây là một lớp lót của Ian để tạo lại tất cả các nhánh trong repo mới:

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
ồ CẢM ƠN BẠN. .git = 15M ngay bây giờ !! Sau khi nhân bản, đây là một lớp lót nhỏ để bảo tồn các nhánh trước của bạn. d1 = # repo gốc; d2 = # repo mới; cd $ d1; cho b bằng $ (nhánh git | cắt -c 3-); kiểm tra git $ b; x = $ (git rev-parse CHÍNH); cd $ d2; kiểm tra git -b $ b $ x; cd $ d1; xong
Ian Kelling

nếu bạn kiểm tra điều này, bạn có thể thêm 1 lớp lót vào câu trả lời của mình để nó được định dạng dưới dạng mã.
Ian Kelling

1
Tôi dại dột thêm một loạt các tập tin video vào repo của mình và phải thiết lập lại --soft Head ^ và khuyên dùng. Sau đó, .git / object dir rất lớn và đây là cách duy nhất khiến nó quay trở lại. Tuy nhiên tôi không thích cách một lớp lót thay đổi tên chi nhánh của tôi (nó hiển thị tên gốc / tên nhánh thay vì chỉ tên nhánh). Vì vậy, tôi đã tiến thêm một bước và thực hiện một số phẫu thuật sơ sài - Tôi đã xóa thư mục .git / object khỏi bản gốc, và đặt vào đó từ bản sao. Điều đó đã tạo ra mánh khóe, để lại tất cả các nhánh ban đầu, ref, v.v., và mọi thứ dường như hoạt động (bắt chéo ngón tay).
Jack Senechal

1
cảm ơn về mẹo về tập tin: // clone, điều đó đã giúp tôi rất nhiều
adam.wulf

3
@vonbrand nếu bạn cứng liên kết đến một tệp và xóa tệp gốc, không có gì xảy ra ngoại trừ bộ đếm tham chiếu bị giảm từ 2 xuống 1. Chỉ khi bộ đếm đó bị giảm xuống 0 thì không gian mới được giải phóng cho các tệp khác trên fs. Vì vậy, không, ngay cả khi các tệp được liên kết cứng sẽ không có gì xảy ra nếu bản gốc bị xóa.
stefreak

157

Một số tập lệnh tôi sử dụng:

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

Nếu bạn muốn có nhiều dòng hơn, hãy xem thêm phiên bản Perl trong câu trả lời lân cận: https://stackoverflow.com/a/45366030/266720

git-eradicate (cho 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

Lưu ý: tập lệnh thứ hai được thiết kế để xóa hoàn toàn thông tin khỏi Git (bao gồm tất cả thông tin từ các reflog). Sử dụng cẩn thận.


2
Cuối cùng ... trớ trêu thay tôi đã thấy câu trả lời này sớm hơn trong tìm kiếm của mình nhưng nó có vẻ quá phức tạp ... sau khi thử những thứ khác, câu hỏi này bắt đầu có ý nghĩa và thì đấy!
msanteler

@msanteler, Tập lệnh cũ ( git-fatfiles) đã xuất hiện khi tôi đặt câu hỏi trên IRC (Freenode / # git). Tôi đã lưu phiên bản tốt nhất vào một tệp, sau đó đăng nó dưới dạng câu trả lời ở đây. (Tôi không thể là tác giả gốc trong nhật ký IRC).
Vi.

Điều này hoạt động rất tốt ban đầu. Nhưng khi tôi tìm nạp hoặc kéo từ xa trở lại, nó chỉ sao chép tất cả các tệp lớn trở lại vào kho lưu trữ. Làm thế nào để tôi ngăn chặn điều đó?
cướp biển

1
@felbo, thì vấn đề có lẽ không chỉ ở kho lưu trữ cục bộ của bạn, mà còn ở các kho khác. Có lẽ bạn cần làm thủ tục ở mọi nơi, hoặc buộc mọi người từ bỏ các nhánh ban đầu và chuyển sang các nhánh viết lại. Không dễ dàng trong một nhóm lớn và cần sự hợp tác giữa các nhà phát triển và / hoặc người quản lý can thiệp. Đôi khi chỉ cần để lại loadstone bên trong có thể là lựa chọn tốt hơn.
Vi.

1
Chức năng này rất tuyệt, nhưng nó chậm đến mức không thể tưởng tượng được. Nó thậm chí không thể hoàn thành trên máy tính của tôi nếu tôi loại bỏ giới hạn 40 dòng. FYI, tôi vừa thêm một câu trả lời với phiên bản hiệu quả hơn của chức năng này. Kiểm tra xem nếu bạn muốn sử dụng logic này trên một kho lưu trữ lớn hoặc nếu bạn muốn xem kích thước được tóm tắt trên mỗi tệp hoặc mỗi thư mục.
piojo

66

git gcđã làm git repacknhư vậy không có ý nghĩa trong việc đóng gói thủ công trừ khi bạn sẽ chuyển một số tùy chọn đặc biệt cho nó.

Bước đầu tiên là để xem liệu phần lớn không gian là (như thường lệ) là cơ sở dữ liệu đối tượng của bạn.

git count-objects -v

Điều này sẽ đưa ra một báo cáo về việc có bao nhiêu đối tượng được giải nén trong kho lưu trữ của bạn, chúng chiếm bao nhiêu dung lượng, bao nhiêu tệp bạn có và bao nhiêu dung lượng chúng chiếm.

Lý tưởng nhất là sau khi đóng gói lại, bạn sẽ không có các đối tượng được giải nén và một tệp gói nhưng hoàn toàn bình thường khi có một số đối tượng không được tham chiếu trực tiếp bởi các nhánh hiện tại vẫn hiện diện và giải nén.

Nếu bạn có một gói lớn duy nhất và bạn muốn biết những gì đang chiếm dung lượng thì bạn có thể liệt kê các đối tượng tạo nên gói cùng với cách chúng được lưu trữ.

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

Lưu ý rằng verify-packcó một tệp chỉ mục chứ không phải chính tệp gói. Điều này đưa ra một báo cáo về mọi đối tượng trong gói, kích thước thật và kích thước đóng gói của nó cũng như thông tin về việc liệu nó có bị "phân tách" hay không và nếu có thì nguồn gốc của chuỗi delta.

Để xem liệu có bất kỳ đối tượng lớn bất thường nào trong kho lưu trữ của bạn không, bạn có thể sắp xếp đầu ra bằng số trên cột thứ ba của cột thứ tư (ví dụ | sort -k3n).

Từ đầu ra này, bạn sẽ có thể thấy nội dung của bất kỳ đối tượng nào bằng cách sử dụng git showlệnh, mặc dù không thể thấy chính xác vị trí trong lịch sử cam kết của kho lưu trữ mà đối tượng được tham chiếu. Nếu bạn cần làm điều này, hãy thử một cái gì đó từ câu hỏi này .


1
Điều này tìm thấy các đối tượng lớn tuyệt vời. Câu trả lời được chấp nhận đã loại bỏ chúng.
Ian Kelling

2
Sự khác biệt giữa git gc và git repack theo linus torvalds. metalinguist.wordpress.com/2007/12/06/...
spuder

30

Chỉ cần FYI, lý do lớn nhất khiến bạn có thể kết thúc với những vật thể không mong muốn được giữ xung quanh là git duy trì một reflog.

Các reflog có mặt để cứu mông của bạn khi bạn vô tình xóa chi nhánh chính của mình hoặc bằng cách nào đó làm hỏng nghiêm trọng kho lưu trữ của bạn.

Cách dễ nhất để khắc phục điều này là cắt bớt các reflog của bạn trước khi nén (chỉ cần đảm bảo rằng bạn không bao giờ muốn quay lại bất kỳ cam kết nào trong reflog).

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

Điều này khác với git gc --prune=todayở chỗ nó hết hạn ngay lập tức.


1
Điều này đã làm điều đó cho tôi! Tôi đã đi từ khoảng 5gb đến 32mb.
Hawkee

Câu trả lời này có vẻ dễ thực hiện hơn nhưng tiếc là nó không hiệu quả với tôi. Trong trường hợp của tôi, tôi đã làm việc trên một kho lưu trữ nhân bản. Đó có phải là lý do?
Mert

13

Nếu bạn muốn tìm những tập tin nào đang chiếm dung lượng trong kho git của bạn, hãy chạy

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

Sau đó, trích xuất tham chiếu blob chiếm nhiều không gian nhất (dòng cuối cùng) và kiểm tra tên tệp đang chiếm quá nhiều dung lượng

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

Đây thậm chí có thể là một tệp mà bạn đã xóa git rm, nhưng git nhớ nó bởi vì vẫn còn các tham chiếu đến nó, chẳng hạn như thẻ, điều khiển từ xa và reflog.

Khi bạn biết bạn muốn loại bỏ tập tin nào, tôi khuyên bạn nên sử dụng git forget-blob

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

Nó rất dễ sử dụng, chỉ cần làm

git forget-blob file-to-forget

Điều này sẽ xóa mọi tham chiếu khỏi git, xóa blob khỏi mọi cam kết trong lịch sử và chạy bộ sưu tập rác để giải phóng không gian.


7

Kịch bản git-fatfiles từ câu trả lời của Vi rất đáng yêu nếu bạn muốn xem kích thước của tất cả các đốm màu của mình, nhưng nó chậm đến mức không thể sử dụng được. Tôi đã xóa giới hạn đầu ra 40 dòng và nó đã cố gắng sử dụng tất cả RAM của máy tính của tôi thay vì hoàn thiện. Vì vậy, tôi đã viết lại: nó nhanh hơn hàng ngàn lần, đã thêm các tính năng (tùy chọn) và một số lỗi lạ đã bị xóa - phiên bản cũ sẽ đưa ra số lượng không chính xác nếu bạn tính tổng đầu ra để xem tổng dung lượng được sử dụng bởi một tệp.

#!/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];
}

Đặt tên git-fatfiles.pl này và chạy nó. Để xem dung lượng đĩa được sử dụng bởi tất cả các phiên bản của tệp, hãy sử dụng --sumtùy chọn. Để xem điều tương tự, nhưng đối với các tệp trong mỗi thư mục, hãy sử dụng --directoriestùy chọn. Nếu bạn cài đặt mô-đun Number :: Bytes :: Human cpan (chạy "cpan Number :: Bytes :: Human"), kích thước sẽ được định dạng: "21M /path/to/file.mp4".


4

Bạn có chắc là bạn chỉ đếm các tệp .pack chứ không phải các tệp .idx? Chúng nằm trong cùng thư mục với các tệp .pack, nhưng không có bất kỳ dữ liệu lưu trữ nào (như phần mở rộng chỉ ra, chúng không có gì khác ngoài chỉ mục cho gói tương ứng - thực tế, nếu bạn biết lệnh chính xác, bạn có thể dễ dàng tạo lại chúng từ tệp pack và git sẽ tự thực hiện khi nhân bản, vì chỉ một tệp pack được truyền bằng giao thức git gốc).

Là một mẫu đại diện, tôi đã xem bản sao cục bộ của kho lưu trữ linux-2.6:

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

Điều này cho thấy sự mở rộng khoảng 7% nên là phổ biến.

Ngoài ra còn có các tập tin bên ngoài objects/; theo kinh nghiệm cá nhân của tôi, trong số họ indexgitk.cachecó xu hướng là những người lớn nhất (tổng cộng 11 triệu trong bản sao của kho lưu trữ linux-2.6).


3

Các đối tượng git khác được lưu trữ .gitbao gồm cây, cam kết và thẻ. Cam kết và thẻ là nhỏ, nhưng cây có thể trở nên lớn đặc biệt nếu bạn có một số lượng rất lớn các tệp nhỏ trong kho lưu trữ của mình. Có bao nhiêu tệp và bạn có bao nhiêu cam kết?


Câu hỏi hay. 19 chi nhánh với khoảng 40 tệp trong mỗi. git Count-object -v nói "in-pack: 1570". Không chắc chắn chính xác điều đó có nghĩa là gì hoặc làm thế nào để đếm xem tôi có bao nhiêu cam kết. Vài trăm tôi đoán.
Ian Kelling

Ok, có vẻ như đó không phải là câu trả lời. Một vài trăm sẽ không đáng kể so với 145 MB.
Greg Hewgill

2

Bạn đã thử sử dụng git repack ?


Câu hỏi hay. Tôi đã làm, tôi cũng có ấn tượng rằng git gc cũng vậy?
Ian Kelling

Nó làm với git gc --auto Không chắc chắn về những gì bạn đã sử dụng.
baudtack

2

trước khi thực hiện git filter-Branch & git gc, bạn nên xem lại các thẻ có trong repo của mình. Bất kỳ hệ thống thực nào có gắn thẻ tự động cho những thứ như tích hợp và triển khai liên tục sẽ khiến các đối tượng không mong muốn vẫn được điều chỉnh bởi các thẻ này, do đó gc không thể xóa chúng và bạn sẽ vẫn tự hỏi tại sao kích thước của repo vẫn còn quá lớn.

Cách tốt nhất để loại bỏ tất cả những thứ không mong muốn là chạy git-filter & git gc và sau đó đẩy master sang một repo trần mới. Repo trần mới sẽ có cây sạch.


1

Điều này có thể xảy ra nếu bạn vô tình thêm một khối lớn các tệp và dàn dựng chúng, không nhất thiết phải cam kết chúng. Điều này có thể xảy ra trong một railsứng dụng khi bạn chạy bundle install --deploymentvà sau đó vô tình git add .thì bạn xem tất cả các tập tin bổ sung dưới vendor/bundlebạn unstage họ nhưng họ đã có trong lịch sử git, vì vậy bạn phải áp dụng câu trả lời Vi và thay đổi video/parasite-intro.avibởi vendor/bundlesau đó chạy lệnh thứ hai ông cung cấp.

Bạn có thể thấy sự khác biệt git count-objects -vtrong trường hợp của tôi trước khi áp dụng tập lệnh có gói kích thước: 52K và sau khi áp dụng là 3,8K.


1

Đó là giá trị kiểm tra stacktrace.log. Về cơ bản, nó là một bản ghi lỗi để truy tìm các xác nhận không thành công. Gần đây tôi đã phát hiện ra rằng stacktrace.log của tôi là 65,5 GB và ứng dụng của tôi là 66,7 GB.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.