ทำไมคลังเก็บคอมไพล์ของฉันถึงใหญ่มาก?


141

145M = .git / วัตถุ / แพ็ค /

ฉันเขียนสคริปต์เพื่อเพิ่มขนาดของความแตกต่างของแต่ละการกระทำและการกระทำก่อนที่จะย้อนกลับจากปลายของแต่ละสาขา ฉันได้รับ 129MB ซึ่งไม่มีการบีบอัดและไม่มีบัญชีสำหรับไฟล์เดียวกันข้ามสาขาและประวัติทั่วไประหว่างสาขา

Git คำนึงถึงสิ่งเหล่านั้นทั้งหมดดังนั้นฉันจึงคาดหวังที่เก็บข้อมูลที่มีขนาดเล็กกว่ามาก เหตุใด. git จึงใหญ่มาก

ฉันเคยทำ:

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

เพื่อตอบเกี่ยวกับจำนวนไฟล์ / คำสั่งฉันมี 19 สาขาเกี่ยวกับ 40 ไฟล์ในแต่ละไฟล์ 287 คอมมิตพบโดยใช้:

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

ไม่ควรใช้ 10 เมกะไบต์เพื่อเก็บข้อมูลเกี่ยวกับเรื่องนี้


5
ไลนัสแนะนำต่อไปนี้มากกว่าก้าวร้าว gc มันสร้างความแตกต่างอย่างมีนัยสำคัญหรือไม่? git repack -a -d --depth = 250 --window = 250
Greg Bacon

ขอบคุณ gbacon แต่ไม่มีความแตกต่าง
เอียน Kelling

นั่นเป็นเพราะคุณพลาด -f metalinguist.wordpress.com/2007/12/06/…
spuder

git repack -a -dหดของฉัน956MB repo เพื่อ250MB ความสำเร็จที่ดี! ขอบคุณ!
xanderiel

คำตอบ:


68

ฉันเพิ่งดึงที่เก็บระยะไกลที่ไม่ถูกต้องลงในโลคัลหนึ่ง ( git remote add ...และgit remote update) หลังจากลบการอ้างอิงรีโมตที่ไม่ต้องการแล้วกิ่งก้านและแท็กฉันยังมีพื้นที่ว่างเหลืออยู่ 1.4GB (!) ในที่เก็บของฉัน git clone file:///path/to/repositoryฉันเป็นเพียงสามารถที่จะกำจัดนี้โดยการโคลนด้วย โปรดทราบว่าการfile://สร้างโลกที่แตกต่างเมื่อทำการโคลนที่เก็บในเครื่อง - เฉพาะวัตถุที่อ้างอิงเท่านั้นที่จะถูกคัดลอกข้ามไม่ใช่โครงสร้างไดเรกทอรีทั้งหมด

แก้ไข: นี่คือสายการบินหนึ่งของ Ian สำหรับการสร้างสาขาทั้งหมดใน repo ใหม่:

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 = 15 ล้านตอนนี้ !! หลังจากโคลนนี่คือซับเล็กน้อยสำหรับรักษากิ่งก่อนหน้าของคุณ d1 = # repo ดั้งเดิม; d2 = # repo ใหม่; cd $ d1; สำหรับ b ใน $ (สาขา git | cut -c 3-); ทำการชำระเงิน git $ b; x = $ (git rev-parse HEAD); cd $ d2; git checkout -b $ b $ x; cd $ d1; เสร็จแล้ว
Ian Kelling

หากคุณตรวจสอบสิ่งนี้คุณสามารถเพิ่ม 1 ซับในคำตอบของคุณเพื่อจัดรูปแบบเป็นรหัส
เอียน Kelling

1
ฉันเพิ่มไฟล์วิดีโอลงใน repo ของฉันอย่างโง่เขลาและต้องรีเซ็ต --soft HEAD ^ และให้คำแนะนำ .git / objects dir นั้นใหญ่มากหลังจากนั้นและนี่เป็นวิธีเดียวที่จะทำให้มันกลับมา อย่างไรก็ตามฉันไม่ชอบวิธีที่สายการบินหนึ่งเปลี่ยนชื่อสาขาของฉันไปรอบ ๆ (มันแสดงที่มา / branchname แทนเพียงแค่ branchname) ดังนั้นฉันจึงก้าวไปอีกขั้นและทำการผ่าตัดแบบร่างบางส่วน - ฉันลบไดเรกทอรี. git / วัตถุออกจากต้นฉบับและใส่ไว้ในโคลน นั่นเป็นกลอุบายที่ปล่อยให้สาขาเดิมทั้งหมดเป็นต้นเหมือนเดิมและทุกอย่างดูเหมือนจะทำงาน (ข้ามนิ้ว)
Jack Senechal

1
ขอบคุณสำหรับเคล็ดลับเกี่ยวกับไฟล์: // clone นั่นเป็นกลอุบายสำหรับฉัน
adam.wulf

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-กำจัด (สำหรับ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 อย่างสมบูรณ์ (รวมถึงข้อมูลทั้งหมดจาก reflogs) ใช้ด้วยความระมัดระวัง


2
ในที่สุด ... แดกดันฉันเห็นคำตอบนี้ก่อนหน้านี้ในการค้นหาของฉัน แต่มันดูซับซ้อนเกินไป ... หลังจากลองสิ่งอื่น ๆ แล้วอันนี้ก็เริ่มที่จะทำให้รู้สึกและ voila!
msanteler

@msanteler git-fatfilesสคริปต์เดิม ( ) เกิดขึ้นเมื่อฉันถามคำถามเกี่ยวกับ IRC (Freenode / # git) ฉันบันทึกเวอร์ชันที่ดีที่สุดลงในไฟล์จากนั้นโพสต์เป็นคำตอบที่นี่ (ฉันไม่สามารถเป็นผู้เขียนดั้งเดิมในบันทึก IRC ได้)
วิ

วิธีนี้ใช้ได้ผลดีมากในตอนแรก แต่เมื่อฉันดึงหรือดึงจากระยะไกลอีกครั้งมันก็แค่คัดลอกไฟล์ขนาดใหญ่ทั้งหมดกลับเข้าไปในไฟล์เก็บถาวร ฉันจะป้องกันได้อย่างไร
pir

1
@felbo จากนั้นปัญหาอาจไม่ได้อยู่ในที่เก็บข้อมูลในเครื่องของคุณเท่านั้น แต่ในที่เก็บอื่น ๆ ด้วย บางทีคุณอาจจำเป็นต้องทำตามขั้นตอนทุกที่หรือบังคับให้ทุกคนละทิ้งสาขาดั้งเดิมและเปลี่ยนเป็นสาขาที่เขียนใหม่ ไม่ใช่เรื่องง่ายในทีมใหญ่และต้องการความร่วมมือระหว่างผู้พัฒนาและ / หรือการแทรกแซงของผู้จัดการ บางครั้งการออกจาก loadstone ด้านในอาจเป็นทางเลือกที่ดีกว่า
วิ

1
ฟังก์ชั่นนี้ยอดเยี่ยม แต่มันช้าอย่างไม่น่าเชื่อ มันไม่สามารถทำได้แม้กระทั่งในคอมพิวเตอร์ของฉันหากฉันลบขีด จำกัด 40 บรรทัด FYI ฉันเพิ่งเพิ่มคำตอบด้วยฟังก์ชันที่มีประสิทธิภาพมากขึ้น ลองใช้ดูว่าคุณต้องการใช้ตรรกะนี้กับที่เก็บขนาดใหญ่หรือไม่หรือถ้าคุณต้องการดูขนาดผลรวมต่อไฟล์หรือต่อโฟลเดอร์
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
สิ่งนี้พบว่ามีวัตถุขนาดใหญ่ที่ยอดเยี่ยม คำตอบที่ยอมรับได้กำจัดพวกเขา
เอียน Kelling

2
ความแตกต่างระหว่าง git gc และ git repack ตาม linus torvalds metalinguist.wordpress.com/2007/12/06/…
spuder

31

เพียงแค่ FYI เหตุผลที่ดีที่สุดที่คุณอาจเจอกับวัตถุที่ไม่พึงประสงค์ที่ถูกเก็บไว้ก็คือ git จะทำการอ้างอิงใหม่

การอ้างอิงจะอยู่ที่นั่นเพื่อบันทึกก้นของคุณเมื่อคุณลบสาขาหลักของคุณโดยไม่ตั้งใจหรือสร้างความเสียหายอย่างหนักกับที่เก็บของคุณ

วิธีที่ง่ายที่สุดในการแก้ไขปัญหานี้คือตัดทอน reflogs ของคุณก่อนที่จะบีบอัด (ตรวจสอบให้แน่ใจว่าคุณไม่ต้องการกลับไปใช้คอมมิชชันใด ๆ

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

สิ่งนี้แตกต่างจากการgit gc --prune=todayหมดอายุการอ้างอิงทั้งหมดในทันที


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

สคริปต์ git-fatfiles จากคำตอบของ Vi นั้นน่ารักถ้าคุณต้องการดูขนาดของ blobs ทั้งหมดของคุณ แต่มันช้ามากจนใช้ไม่ได้ ฉันลบขีด จำกัด เอาต์พุต 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 แต่ไม่มีข้อมูลที่เก็บใด ๆ (เป็นส่วนขยายบ่งชี้ว่าพวกเขาไม่มีอะไรมากไปกว่าดัชนีสำหรับแพ็คที่สอดคล้องกัน - ในความเป็นจริงถ้าคุณรู้คำสั่งที่ถูกต้องคุณสามารถ สร้างมันขึ้นมาใหม่จากไฟล์ pack ได้อย่างง่ายดายและ git เองก็ทำเมื่อทำการโคลนนิ่งเนื่องจากไฟล์ pack เท่านั้นที่ถูกถ่ายโอนโดยใช้โปรโตคอล git ดั้งเดิม)

ในฐานะตัวแทนตัวอย่างฉันลองดูที่โคลนในพื้นที่เก็บข้อมูล linux-2.6 ของฉัน:

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

ซึ่งบ่งชี้ว่าการขยายตัวประมาณ 7% น่าจะเป็นเรื่องธรรมดา

นอกจากนี้ยังมีไฟล์ที่อยู่นอกobjects/; ในประสบการณ์ส่วนตัวของพวกเขาindexและgitk.cacheมักจะเป็นคนที่ยิ่งใหญ่ที่สุด (รวม 11 ล้านคนในโคลนของที่เก็บ linux-2.6)


3

วัตถุคอมไพล์อื่น ๆ ที่เก็บไว้ใน.gitรวมถึงต้นไม้กระทำและแท็ก คอมมิทและแท็กนั้นเล็ก แต่ทรีจะใหญ่ขึ้นโดยเฉพาะถ้าคุณมีไฟล์ขนาดเล็กจำนวนมากในที่เก็บของคุณ คุณมีไฟล์กี่ไฟล์และคุณมีข้อผูกพันกี่สัญญา?


คำถามที่ดี. 19 สาขาในแต่ละไฟล์มีประมาณ 40 ไฟล์ git count-objects -v พูดว่า "in-pack: 1570" ไม่แน่ใจว่าสิ่งที่หมายถึงหรือวิธีการนับจำนวนฉันมีความมุ่งมั่น สักสองสามร้อยฉันเดา
Ian Kelling

ตกลงมันฟังดูไม่เหมือนที่เป็นคำตอบแล้ว สองสามร้อยจะไม่มีนัยสำคัญเมื่อเทียบกับ 145 MB
Greg Hewgill

2

คุณลองใช้git repackหรือไม่


คำถามที่ดี. ฉันทำฉันได้รับความประทับใจที่ git gc ทำเช่นนั้นด้วย?
เอียน Kelling

มันทำกับ git gc - อัตโนมัติไม่แน่ใจว่าคุณใช้อะไร
baudtack

2

ก่อนที่จะทำ git filter-branch & git gc คุณควรตรวจสอบแท็กที่มีอยู่ใน repo ของคุณ ระบบจริงใด ๆ ที่มีการติดแท็กอัตโนมัติสำหรับสิ่งต่าง ๆ เช่นการรวมอย่างต่อเนื่องและการปรับใช้จะทำให้วัตถุที่ไม่ได้รับการแก้ไขยังคงถูกแท็กเหล่านี้อยู่ดังนั้น gc ไม่สามารถลบออกได้และคุณจะยังสงสัยอยู่เสมอว่าทำไม

วิธีที่ดีที่สุดในการกำจัดสิ่งที่ไม่ต้องการทั้งหมดคือการเรียกใช้ตัวกรอง git & git gc จากนั้นผลักต้นแบบไปยัง repo ใหม่ที่เปลือยเปล่า Repo เปลือยใหม่จะมีต้นไม้ที่สะอาดขึ้น


1

สิ่งนี้อาจเกิดขึ้นได้หากคุณเพิ่มไฟล์ขนาดใหญ่โดยไม่ตั้งใจและจัดวางไฟล์เหล่านั้น นี้สามารถเกิดขึ้นในrailsแอปเมื่อคุณเรียกใช้bundle install --deploymentแล้วตั้งใจgit add .แล้วคุณจะเห็นไฟล์ทั้งหมดที่เพิ่มภายใต้vendor/bundleคุณ unstage พวกเขา แต่พวกเขามีอยู่แล้วในประวัติศาสตร์คอมไพล์เพื่อให้คุณมีที่จะใช้คำตอบของ Viและการเปลี่ยนแปลง video/parasite-intro.aviโดยvendor/bundleเรียกใช้คำสั่งที่สองที่เขาให้

คุณสามารถเห็นความแตกต่างgit count-objects -vที่ในกรณีของฉันก่อนที่จะใช้สคริปต์มีขนาดแพ็ค: 52K และหลังจากใช้เป็น 3.8K


1

คุ้มค่าที่จะตรวจสอบ stacktrace.log เป็นพื้นบันทึกข้อผิดพลาดสำหรับการติดตามการกระทำที่ล้มเหลว ฉันเพิ่งพบว่า stacktrace.log ของฉันคือ 65.5GB และแอปของฉันคือ 66.7GB

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.