Mengapa repositori git saya begitu besar?

141

145M = .git / objek / pack /

Saya menulis sebuah skrip untuk menjumlahkan ukuran perbedaan masing-masing komit dan komit sebelum mundur dari ujung masing-masing cabang. Saya mendapatkan 129MB, yang tanpa kompresi dan tanpa akuntansi untuk file yang sama di cabang dan sejarah umum di antara cabang.

Git memperhitungkan semua hal itu jadi saya berharap repositori yang jauh lebih kecil. Jadi mengapa git begitu besar?

Saya sudah selesai:

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

Untuk menjawab tentang berapa banyak file / commit, saya memiliki 19 cabang masing-masing sekitar 40 file. 287 komit, ditemukan menggunakan:

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

Seharusnya tidak mengambil 10 megabita untuk menyimpan informasi tentang ini.

Ian Kelling
sumber
5
Linus merekomendasikan berikut ini untuk gc yang agresif. Apakah itu membuat perbedaan yang signifikan? git membungkus kembali -a -d --depth = 250 --window = 250
Greg Bacon
terima kasih gbacon, tetapi tidak ada perbedaan.
Ian Kelling
Itu karena kamu melewatkan -f. metalinguist.wordpress.com/2007/12/06/...
spuder
git repack -a -dmenyusut repo 956MB saya ke 250MB . Sukses besar! Terima kasih!
xanderiel

Jawaban:

68

Saya baru-baru ini menarik repositori jarak jauh yang salah ke yang lokal ( git remote add ...dan git remote update). Setelah menghapus referensi jarak jauh yang tidak diinginkan, cabang dan tag saya masih memiliki ruang kosong 1.4GB (!) Di repositori saya. Saya hanya bisa menyingkirkan ini dengan mengkloningnya git clone file:///path/to/repository. Perhatikan bahwa file://membuat perbedaan saat mengkloning repositori lokal - hanya objek yang direferensikan yang disalin, bukan seluruh struktur direktori.

Sunting: Inilah one liner Ian untuk membuat ulang semua cabang di repo baru:

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
pgs
sumber
1
Wow. TERIMA KASIH. .git = 15 juta sekarang !! setelah kloning, berikut adalah 1 liner kecil untuk melestarikan cabang Anda sebelumnya. d1 = # repo asli; d2 = # repo baru; cd $ d1; untuk b in $ (cabang git | cut -c 3-); lakukan checkout g $ $; x = $ (KEPALA git rev-parse); cd $ d2; git checkout -b $ b $ x; cd $ d1; selesai
Ian Kelling
jika Anda memeriksa ini, Anda bisa menambahkan 1 liner ke jawaban Anda sehingga diformat sebagai kode.
Ian Kelling
1
Saya dengan bodohnya menambahkan banyak file video ke repo saya, dan harus mengatur ulang --soft HEAD ^ dan berkomentar kembali. Dir .git / objek sangat besar setelah itu, dan ini adalah satu-satunya cara untuk mendapatkannya kembali. Namun saya tidak suka cara liner mengubah nama cabang saya di sekitar (ini menunjukkan asal / nama cabang bukan hanya nama cabang). Jadi saya melangkah lebih jauh dan menjalankan beberapa operasi samar - saya menghapus direktori .git / objek dari aslinya, dan memasukkan satu dari klon. Itu berhasil, meninggalkan semua cabang asli, referensi, dll tetap utuh, dan semuanya tampak bekerja (bersilangan).
Jack Senechal
1
terima kasih atas tip tentang file: // clone, itu berhasil bagi saya
adam.wulf
3
@vonbrand jika Anda menautkan ke file dan menghapus file asli, tidak ada yang terjadi kecuali bahwa penghitung referensi dikurangi dari 2 menjadi 1. Hanya jika penghitung itu dikurangi menjadi 0, ruang tersebut dibebaskan untuk file lain di fs. Jadi tidak, bahkan jika file itu ditautkan dengan keras, tidak ada yang akan terjadi jika dokumen asli terhapus.
stefreak
157

Beberapa skrip yang saya gunakan:

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

Jika Anda ingin lebih banyak baris, lihat juga versi Perl dalam jawaban yang berdekatan: https://stackoverflow.com/a/45366030/266720

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

Catatan: skrip kedua dirancang untuk menghapus info dari Git sepenuhnya (termasuk semua info dari reflog). Gunakan dengan hati-hati.

Vi.
sumber
2
Akhirnya ... Ironisnya saya melihat jawaban ini sebelumnya dalam pencarian saya tetapi terlihat terlalu rumit ... setelah mencoba hal-hal lain, yang ini mulai masuk akal dan voila!
msanteler
@ msanteler, Mantan ( git-fatfiles) script telah muncul ketika saya mengajukan pertanyaan pada IRC (Freenode / # git). Saya menyimpan versi terbaik ke file, lalu mempostingnya sebagai jawaban di sini. (Saya tidak bisa membuat penulis asli di log IRC).
Vi.
Ini bekerja dengan sangat baik pada awalnya. Tetapi ketika saya mengambil atau menarik dari remote lagi, itu hanya menyalin semua file besar kembali ke arsip. Bagaimana saya mencegahnya?
pir
1
@felbo, Maka masalahnya mungkin bukan hanya di repositori lokal Anda, tetapi di repositori lain juga. Mungkin Anda perlu melakukan prosedur di mana-mana, atau memaksa semua orang meninggalkan cabang asli dan beralih ke cabang yang ditulis ulang. Tidak mudah dalam tim besar dan membutuhkan kerjasama antara pengembang dan / atau intervensi manajer. Kadang-kadang hanya meninggalkan loadstone di dalam bisa menjadi pilihan yang lebih baik.
Vi.
1
Fungsi ini hebat, tetapi sangat lambat. Bahkan tidak bisa selesai di komputer saya jika saya menghapus batas 40 baris. FYI, saya baru saja menambahkan jawaban dengan versi yang lebih efisien dari fungsi ini. Periksa apakah Anda ingin menggunakan logika ini di repositori besar, atau jika Anda ingin melihat ukurannya dijumlahkan per file atau per folder.
piojo
66

git gcsudah melakukan git repacksehingga tidak ada gunanya mengemas ulang secara manual kecuali Anda akan melewati beberapa opsi khusus untuk itu.

Langkah pertama adalah untuk melihat apakah sebagian besar ruang (seperti biasanya) database objek Anda.

git count-objects -v

Ini harus memberikan laporan tentang berapa banyak objek yang dibongkar yang ada di repositori Anda, berapa banyak ruang yang digunakan, berapa banyak paket file yang Anda miliki dan berapa banyak ruang yang mereka ambil.

Idealnya, setelah pengemasan ulang, Anda tidak akan memiliki objek yang dibongkar dan satu file paket, tetapi sangat normal untuk memiliki beberapa objek yang tidak secara langsung dirujuk oleh cabang saat ini yang masih ada dan belum dibongkar.

Jika Anda memiliki satu paket besar dan Anda ingin tahu apa yang mengambil ruang maka Anda dapat membuat daftar objek yang membentuk paket bersama dengan bagaimana mereka disimpan.

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

Catatan yang verify-packmengambil file indeks dan bukan file paket itu sendiri. Ini memberikan laporan dari setiap objek dalam paket, ukuran sebenarnya dan ukurannya yang dikemas serta informasi tentang apakah itu telah 'dihapus' dan jika demikian asal mula rantai delta.

Untuk melihat apakah ada objek besar yang tidak biasa dalam repositori Anda, Anda dapat mengurutkan output secara numerik pada ketiga kolom keempat (mis | sort -k3n.).

Dari output ini Anda akan dapat melihat konten dari objek apa pun menggunakan git showperintah, meskipun tidak mungkin untuk melihat secara tepat di mana dalam komit sejarah repositori objek direferensikan. Jika Anda perlu melakukan ini, coba sesuatu dari pertanyaan ini .

CB Bailey
sumber
1
Ini menemukan benda-benda besar hebat. Jawaban yang diterima menyingkirkan mereka.
Ian Kelling
2
Perbedaan antara git gc dan git repack menurut linus torvalds. metalinguist.wordpress.com/2007/12/06/...
spuder
31

Hanya FYI, alasan terbesar mengapa Anda mungkin berakhir dengan benda-benda yang tidak diinginkan disimpan adalah bahwa git memelihara sebuah reflog.

Reflog ada untuk menyimpan pantat Anda ketika Anda secara tidak sengaja menghapus cabang master Anda atau dengan cara lain merusak repositori Anda.

Cara termudah untuk memperbaikinya adalah dengan memotong reflog Anda sebelum mengompres (pastikan bahwa Anda tidak ingin kembali ke salah satu commit di reflog).

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

Ini berbeda git gc --prune=todaydengan karena semua reflog segera kedaluwarsa.

John Gietzen
sumber
1
Yang ini melakukannya untuk saya! Saya beralih dari sekitar 5GB ke 32MB.
Hawkee
Jawaban ini sepertinya lebih mudah dilakukan tetapi sayangnya tidak berhasil untuk saya. Dalam kasus saya, saya sedang mengerjakan repositori yang baru dikloning. Apakah itu alasannya?
Mert
13

Jika Anda ingin menemukan file apa saja yang mengambil ruang di repositori git Anda, jalankan

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

Kemudian, ekstrak referensi gumpalan yang memakan banyak ruang (baris terakhir), dan periksa nama file yang mengambil begitu banyak ruang

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

Ini bahkan mungkin file yang Anda hapus git rm, tetapi git mengingatnya karena masih ada referensi untuk itu, seperti tag, remote dan reflog.

Setelah Anda tahu file apa yang ingin Anda singkirkan, saya sarankan menggunakan git forget-blob

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

Mudah digunakan, lakukan saja

git forget-blob file-to-forget

Ini akan menghapus setiap referensi dari git, menghapus gumpalan dari setiap komit dalam sejarah, dan menjalankan pengumpulan sampah untuk membebaskan ruang.

nachoparker
sumber
7

Skrip git-fatfiles dari jawaban Vi bagus jika Anda ingin melihat ukuran semua gumpalan Anda, tetapi sangat lambat sehingga tidak dapat digunakan. Saya menghapus batas output 40-line, dan mencoba menggunakan semua RAM komputer saya daripada menyelesaikan. Jadi saya menulis ulang: ini ribuan kali lebih cepat, telah menambahkan fitur (opsional), dan beberapa bug aneh telah dihapus - versi lama akan memberikan jumlah yang tidak akurat jika Anda menjumlahkan output untuk melihat total ruang yang digunakan oleh file.

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

Beri nama git-fatfiles.pl ini dan jalankan. Untuk melihat ruang disk yang digunakan oleh semua revisi file, gunakan --sumopsi. Untuk melihat hal yang sama, tetapi untuk file dalam setiap direktori, gunakan --directoriesopsi. Jika Anda menginstal Number :: Bytes :: Human cpan module (jalankan "cpan Number :: Bytes :: Human"), ukurannya akan diformat: "21M /path/to/file.mp4".

piojo
sumber
4

Apakah Anda yakin Anda hanya menghitung file .pack dan bukan file .idx? Mereka berada di direktori yang sama dengan file .pack, tetapi tidak memiliki data repositori (seperti yang ditunjukkan ekstensi, mereka tidak lebih dari indeks untuk paket yang sesuai - pada kenyataannya, jika Anda tahu perintah yang benar, Anda dapat mudah membuat ulang mereka dari file paket, dan git sendiri melakukannya saat kloning, karena hanya file paket yang ditransfer menggunakan protokol asli git).

Sebagai sampel yang representatif, saya melihat klon lokal saya dari repositori linux-2.6:

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

Yang menunjukkan ekspansi sekitar 7% harus umum.

Ada juga file di luar objects/; dalam pengalaman pribadi saya, dari mereka indexdan gitk.cachecenderung menjadi yang terbesar (berjumlah 11M di klon repositori linux-2.6).

CesarB
sumber
3

Objek git lain yang disimpan di dalamnya .gittermasuk pohon, komit, dan tag. Komit dan tag berukuran kecil, tetapi pohon bisa menjadi besar terutama jika Anda memiliki jumlah file kecil yang sangat besar di repositori Anda. Berapa banyak file dan berapa banyak komitmen yang Anda miliki?

Greg Hewgill
sumber
Pertanyaan bagus. 19 cabang dengan sekitar 40 file di masing-masing. git count-objects -v mengatakan "in-pack: 1570". Tidak yakin persis apa artinya atau bagaimana cara menghitung berapa banyak komit yang saya miliki. Beberapa ratus kurasa.
Ian Kelling
Ok, sepertinya itu bukan jawabannya. Beberapa ratus akan menjadi tidak signifikan dibandingkan dengan 145 MB.
Greg Hewgill
2

Apakah Anda mencoba menggunakan git repack ?

baudtack
sumber
Pertanyaan bagus. Ya, saya juga mendapat kesan bahwa git gc juga melakukannya?
Ian Kelling
Itu tidak dengan git gc --auto Tidak yakin tentang apa yang Anda gunakan.
baudtack
2

sebelum melakukan git filter-branch & git gc Anda harus meninjau tag yang ada di repo Anda. Setiap sistem nyata yang memiliki penandaan otomatis untuk hal-hal seperti integrasi berkelanjutan dan penyebaran akan membuat objek yang tidak disunting masih direfrensikan oleh tag ini, maka gc tidak dapat menghapusnya dan Anda masih akan bertanya-tanya mengapa ukuran repo masih sangat besar.

Cara terbaik untuk menghilangkan semua hal yang tidak diinginkan adalah dengan menjalankan git-filter & git gc dan kemudian mendorong master ke repo kosong yang baru. Repo telanjang baru akan membersihkan pohon.

v_abhi_v
sumber
1

Ini bisa terjadi jika Anda menambahkan banyak file secara tidak sengaja dan mementaskannya, belum tentu melakukannya. Ini bisa terjadi di railsaplikasi ketika Anda menjalankan bundle install --deploymentdan kemudian secara tidak sengaja git add .Anda melihat semua file yang ditambahkan di bawah vendor/bundleAnda unstage mereka tetapi mereka sudah masuk ke git history, jadi Anda harus menerapkan jawaban Vi dan mengubahnya video/parasite-intro.avidengan vendor/bundlekemudian jalankan perintah kedua yang dia sediakan.

Anda dapat melihat perbedaan git count-objects -vyang dalam kasus saya sebelum menerapkan skrip memiliki paket ukuran: dari 52K dan setelah menerapkannya adalah 3,8K.

juliangonzalez
sumber
1

Perlu memeriksa stacktrace.log. Ini pada dasarnya adalah log kesalahan untuk melacak komit yang gagal. Saya baru-baru ini menemukan bahwa stacktrace.log saya 65.5GB dan aplikasi saya 66.7GB.

Nes
sumber