Git depom neden bu kadar büyük?


141

145M = .git / nesneler / paket /

Her dalın ucundan geriye doğru gitmeden önce her bir taahhüdün ve taahhüdün farklılıklarının boyutlarını eklemek için bir senaryo yazdım. 129MB, sıkıştırma olmadan ve şubeler arasında aynı dosyaları ve şubeler arasındaki ortak geçmişi hesaba katmadan elde ediyorum.

Git tüm bunları hesaba katar, böylece çok daha küçük bir depo beklerdim. Peki .git neden bu kadar büyük?

Yaptım:

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

Kaç dosya / taahhüt hakkında cevap vermek için, her birinde yaklaşık 40 dosya olmak üzere 19 şubem var. 287 taahhüt, bulundu kullanılarak:

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

Bu konuda bilgi depolamak 10 megabayt almamalıdır.


5
Linus, agresif gc üzerinden aşağıdakileri önerir. Önemli bir fark yaratıyor mu? git repack -a -d - derinlik = 250 - pencere = 250
Greg Bacon

teşekkürler gbacon, ama fark yok.
Ian Kelling

Çünkü -f'yi kaçırıyorsunuz. metalinguist.wordpress.com/2007/12/06/…
spuder

git repack -a -dbüzülmüş benim 956MB Repo için 250MB . Büyük başarı! Teşekkürler!
xanderiel

Yanıtlar:


68

Son zamanlarda yanlış uzak depoyu yerel bir ( git remote add ...ve git remote update) içine çekti . İstenmeyen uzak referansı, dalları ve etiketleri sildikten sonra depomda hala 1,4 GB (!) Boşa boşa harcadım. Bundan sadece klonlayarak kurtulabildim git clone file:///path/to/repository. file://Yerel bir havuzu klonlarken fark yaratan bir dünya yarattığına dikkat edin - dizin yapısının tamamı değil yalnızca başvurulan nesneler kopyalanır.

Düzenleme: İşte yeni repodaki tüm dalları yeniden oluşturmak için Ian'ın bir astarı:

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
vay. TEŞEKKÜR EDERİM. .git = 15M şimdi !! klonlamadan sonra, önceki dallarınızı korumak için biraz 1 astar var. d1 = # orijinal repo; d2 = # yeni repo; cd $ d1; b için $ (git branch | cut -c 3-); git check çıkış $ b; x = $ (git rev-parse HEAD); cd $ d2; git kasası -b $ b $ x; cd $ d1; bitti
Ian Kelling

bunu işaretlerseniz, cevabınıza kod olarak biçimlendirilecek şekilde 1 astarı ekleyebilirsiniz.
Ian Kelling

1
Ben aptalca bir sürü video dosyaları repo ekledim ve --soft HEAD ^ sıfırlamak ve tavsiye etmek zorunda kaldı. Bundan sonra .git / objects dir çok büyüktü ve bu onu geri almanın tek yoluydu. Ancak, bir astarın şube isimlerimi değiştirmesinin şeklini beğenmedim (sadece şube adı yerine orijin / şube adını gösterdi). Bu yüzden bir adım daha ileri gittim ve kabataslak ameliyat yaptım. .Git / objects dizinini orijinalden sildim ve klondan birini koydum. Bu, tüm orijinal dalları, referansları vb.Tüm bıraktı ve her şey işe yarıyor gibi görünüyor (parmakları çaprazlamak).
Jack Senechal

1
dosya hakkında ipucu için teşekkürler: // klon, benim için hile yaptı
adam.wulf

3
@vonbrand bir dosyaya sabit bağlantı kurar ve orijinal dosyayı silerseniz, bir referans sayacının 2'den 1'e düşürülmesi dışında hiçbir şey olmaz. Sadece bu sayaç 0'a azaltılırsa fs'deki diğer dosyalar için alan boşaltılır. Bu yüzden hayır, dosyalar sabit bağlı olsa bile, orijinal silinirse hiçbir şey olmazdı.
stefreak

157

Kullandığım bazı komut dosyaları:

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

Daha fazla satır istiyorsanız, komşu bir cevaptaki Perl sürümüne de bakın: https://stackoverflow.com/a/45366030/266720

git-eradicate (için 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

Not: İkinci komut dosyası, Git'ten bilgileri tamamen kaldırmak için tasarlanmıştır (refloglardaki tüm bilgiler dahil). Dikkatle kullanın.


2
Sonunda ... İronik olarak bu cevabı araştırmamın başlarında gördüm ama çok karmaşık görünüyordu ... başka şeyler denedikten sonra, bu mantıklı ve voila yapmaya başladı!
msanteler

@msanteler, git-fatfilesIRC (Freenode / # git) sorusunu sorduğumda eski ( ) komut dosyası ortaya çıktı. En iyi sürümü bir dosyaya kaydettim ve sonra bir cevap olarak gönderdim. (Her ne kadar IRC günlüklerinde orijinal yazar olamaz).
Vi.

Bu başlangıçta çok iyi çalışıyor. Ancak uzaktan kumandayı tekrar aldığımda veya çektiğimde, tüm büyük dosyaları arşive geri kopyalar. Bunu nasıl önleyebilirim?
pir

1
@felbo, O zaman sorun muhtemelen sadece yerel deponuzda değil, diğer depolarda da var. Belki prosedürü her yerde yapmanız veya herkesi orijinal şubelerden vazgeçmeye ve yeniden yazılan şubelere geçmeye zorlamanız gerekir. Büyük bir ekipte kolay değildir ve geliştiriciler ve / veya yönetici müdahalesi arasında işbirliğine ihtiyaç duyar. Bazen sadece yük taşını içeride bırakmak daha iyi bir seçenek olabilir.
Vi.

1
Bu işlev harika, ama hayal bile edilemeyecek kadar yavaş. 40 satır sınırını kaldırırsam bilgisayarımda bile bitemez. Bilginize, bu fonksiyonun daha verimli bir versiyonuyla bir cevap ekledim. Bu mantığı büyük bir depoda kullanmak istiyorsanız veya dosya veya klasör başına toplanan boyutları görmek istiyorsanız göz atın.
piojo

66

git gczaten bunu git repackyaparsanız, ona bazı özel seçenekler geçmeyecek olmadıkça elle yeniden paketlemenin bir anlamı yoktur.

İlk adım, alanın büyük çoğunluğunun (normalde olduğu gibi) nesne veritabanınız olup olmadığını görmektir.

git count-objects -v

Bu, deponuzda kaç adet paketsiz nesne bulunduğunu, ne kadar yer kapladıklarını, ne kadar paket dosyanız olduğunu ve ne kadar yer kapladıklarını rapor etmelidir.

İdeal olarak, bir yeniden paketlemeden sonra, paketlenmemiş nesneleriniz ve bir paket dosyanız olmaz, ancak mevcut dallar tarafından doğrudan referans alınmayan ve paketlenmemiş bazı nesnelerin olması normaldir.

Tek bir büyük paketiniz varsa ve alanın ne olduğunu bilmek istiyorsanız, paketi oluşturan nesneleri nasıl depolandıklarını listeleyebilirsiniz.

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

Not verify-packbir dizin dosyasını değil, paket dosyası kendisi götürür. Bu, paketteki her nesnenin, gerçek boyutunun ve paketlenmiş boyutunun yanı sıra, 'delifte edilmiş' olup olmadığı ve delta zincirinin kaynağı hakkında bilgi verir.

Deponuzda alışılmadık derecede büyük nesneler olup olmadığını görmek için çıktıyı dördüncü sütunların üçte birinde sayısal olarak sıralayabilirsiniz (örn. | sort -k3n).

Bu çıktıdan, git showkomutu kullanarak herhangi bir nesnenin içeriğini görebileceksiniz , ancak nesnenin kesin geçmişinde tam olarak nereye başvurulduğunu görmek mümkün değildir. Bunu yapmanız gerekiyorsa, bu sorudan bir şey deneyin .


1
Bu büyük nesneleri harika buldu. Kabul edilen cevap onlardan kurtuldu.
Ian Kelling

2
Linus torvalds'a göre git gc ve git repack arasındaki fark. metalinguist.wordpress.com/2007/12/06/…
spuder

31

Sadece FYI, istenmeyen nesnelerin etrafta tutulmasının en büyük nedeni git'in bir reflog tutmasıdır.

Reflog, ana dalınızı yanlışlıkla sildiğinizde veya deponuza bir şekilde felaketle zarar verdiğinizde poponuzu kurtarmak için oradadır.

Bunu düzeltmenin en kolay yolu, sıkıştırmadan önce refloglarınızı kısaltmaktır (sadece reflogdaki taahhütlerin hiçbirine geri dönmek istemediğinizden emin olun).

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

Bu, git gc --prune=todaytüm reflogun derhal sona ermesinden farklıdır.


1
Bu benim için yaptı! Ben yaklaşık 5 gb 32mb gitti.
Hawkee

Bu cevabı yapmak daha kolay görünüyordu ama maalesef benim için işe yaramadı. Benim durumumda klonlanmış bir depo üzerinde çalışıyordum. Nedeni bu mu?
Mert

13

Git deponuzda hangi dosyaların yer kapladığını bulmak istiyorsanız, çalıştırın

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

Ardından, en fazla yer kaplayan (son satır) blob referansını çıkarın ve çok fazla yer kaplayan dosya adını kontrol edin

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

Bu git rm, kaldırdığınız bir dosya bile olabilir , ancak git bunu hatırlar, çünkü etiketler, uzaktan kumandalar ve reflog gibi hala referanslar vardır.

Hangi dosyadan kurtulmak istediğinizi öğrendikten sonra, git forget-blob

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

Kullanımı kolaydır, sadece

git forget-blob file-to-forget

Bu, git'ten her referansı kaldıracak, blob'u tarihteki her taahhütten kaldıracak ve alanı boşaltmak için çöp toplama çalıştıracak.


7

Tüm lekelerinizin boyutunu görmek istiyorsanız, Vi'nin cevabından git-fatfiles betiği çok güzel, ancak kullanılamaz olmak için çok yavaş. 40 satırlık çıkış sınırını kaldırdım ve bitirmek yerine tüm bilgisayarımın RAM'ini kullanmaya çalıştı. Bu yüzden yeniden yazdım: bu binlerce kat daha hızlı, özellikler ekledi (isteğe bağlı) ve bazı garip hatalar kaldırıldı - bir dosya tarafından kullanılan toplam alanı görmek için çıktıyı toplarsanız eski sürüm yanlış sayımlar verir.

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

Bu git-fatfiles.pl olarak adlandırın ve çalıştırın. Bir dosyanın tüm revizyonları tarafından kullanılan disk alanını görmek için --sumseçeneği kullanın. Aynı şeyi görmek için, ancak her dizindeki dosyalar için --directoriesseçeneği kullanın. Eğer yüklerseniz sayısı :: Bayt :: İnsan ( "Sayı :: Bayt :: İnsan cpan" run) cpan modülü, boyutları biçimlendirilir: "21M /path/to/file.mp4".


4

.İdx dosyalarını değil, yalnızca .pack dosyalarını saydığınızdan emin misiniz? Bunlar .pack dosyalarıyla aynı dizindedir, ancak veri havuzundan herhangi birine sahip değildir (uzantının belirttiği gibi, ilgili paket için dizinlerden başka bir şey değildir - aslında, doğru komutu biliyorsanız, bunları kolayca paket dosyasından yeniden oluşturun ve git, klonlama sırasında kendisi yapar, çünkü yalnızca bir paket dosyası yerel git protokolü kullanılarak aktarılır).

Temsili bir örnek olarak, linux-2.6 deposunun yerel klonuna bir göz attım:

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

Bu, yaklaşık% 7'lik bir genişlemenin yaygın olması gerektiğini gösterir.

Dışarıda dosyalar da var objects/; benim kişisel deneyim, bunlardan indexve gitk.cache(linux-2.6 depo benim klonu içinde 11M toplam) En büyük olanlar olma eğilimindedirler.


3

Depolanan diğer git nesneleri .gitarasında ağaçlar, taahhütler ve etiketler bulunur. Taahhütler ve etiketler küçüktür, ancak özellikle deponuzda çok sayıda küçük dosyanız varsa ağaçlar büyüyebilir. Kaç tane dosyanız ve kaç tane işleminiz var?


İyi soru. Her birinde yaklaşık 40 dosya bulunan 19 şube. git count-objects -v "paket içi: 1570" diyor. Bunun ne anlama geldiğinden veya kaç tane taahhüt aldığımı nasıl sayacağımı tam olarak bilmiyorum. Birkaç yüz sanırım.
Ian Kelling

Tamam, bu cevap gibi görünmüyor. 145 MB ile karşılaştırıldığında birkaç yüz önemsiz olacak.
Greg Hewgill


2

git filter-branch & git gc yapmadan önce deponuzda bulunan etiketleri gözden geçirmelisiniz. Sürekli entegrasyon ve dağıtımlar gibi şeyler için otomatik etiketleme olan herhangi bir gerçek sistem, istenmeyen etiketleri hala bu etiketlerle yeniden başvuruda bulunduracaktır, bu nedenle gc bunları kaldıramaz ve yine de repo boyutunun neden hala bu kadar büyük olduğunu merak etmeye devam edersiniz.

Tüm istenmeyen şeylerden kurtulmanın en iyi yolu git-filter & git gc'yi çalıştırmak ve daha sonra ustayı yeni bir çıplak repoya itmektir. Yeni çıplak repo temizlenmiş ağaç olacak.


1

Bu, büyük bir dosya yığınını yanlışlıkla eklediyseniz ve bunları sahnelediyseniz, ille de taahhüt etmezseniz olabilir. Bu gerçekleşebilir railsçalıştırdığınızda app bundle install --deploymentve sonra yanlışlıkla git add .o zaman altındaki tüm dosyaları eklenen bakın vendor/bundlebunları unstage ama zaten git tarihine girdi uygulamak zorunda, Vi yanıtını ve değişim video/parasite-intro.aviile vendor/bundleo sağlayan ikinci komutunu çalıştırın.

Sen ile farkı görebilirsiniz git count-objects -v52K ve uygulamadan sonra 3.8K oldu: bir boyut-paketi senaryoyu vardı uygulamadan önce benim durumumda.


1

Stacktrace.log dosyasını kontrol etmeye değer. Temel olarak, başarısız olan izleme taahhütleri için bir hata günlüğüdür. Geçenlerde stacktrace.log dosyasının 65.5GB ve uygulamamın 66.7GB olduğunu öğrendim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.