Чому моє сховище git таке велике?


141

145M = .git / об'єкти / pack /

Я написав сценарій, щоб додати розміри різниць кожного коміту та комітету, перш ніж він повернеться назад від кінця кожної гілки. Я отримую 129 Мб, що без стиснення і без обліку однакових файлів у гілках та загальної історії між гілками.

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
Грег Бекон

спасибі gbacon, але різниці немає.
Ян Келінг

Це тому, що ви пропускаєте -f. metalinguist.wordpress.com/2007/12/06 / ...
spuder

git repack -a -dусадка мого 956MB репо до 250МБ . Великий успіх! Дякую!
xanderiel

Відповіді:


68

Нещодавно я витягнув неправильне віддалене сховище у локальне ( git remote add ...і git remote update). Після видалення небажаного віддаленого списку, гілок і тегів у мене все ще було 1,4 ГБ (!) Марного місця в моєму сховищі. Я зміг позбутися цього лише клонуючи його git clone file:///path/to/repository. Зауважте, що file://створює світ різниці при клонуванні локального сховища - копіюються лише посилання, що посилаються, а не вся структура каталогів.

Редагувати: Ось один лайнер Яна для відтворення всіх гілок у новому репо:

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М зараз !! після клонування, ось трохи 1 лайнера для збереження попередніх гілок. d1 = # оригінальний репо; d2 = # нове репо; cd $ d1; для b в $ (гіт гіт | cut -c 3-); робити git checkout $ b; x = $ (git rev-parse HEAD); cd $ d2; git checkout -b $ b $ x; cd $ d1; виконано
Ієн Келінг

якщо ви перевірите це, ви можете додати у відповідь 1 вкладиш, щоб його було відформатовано як код.
Ієн Келінг

1
Я нерозумно додав купу відеофайлів до мого репо, і довелося скинути --soft HEAD ^ та повторно. Режим .git / objects був величезним після цього, і це був єдиний спосіб, який повернув його назад. Однак мені не сподобалося, як один лайнер міняв назви моїх гілок (він показував походження / ім'я гілки замість просто імені гілки). Тож я пішов на крок далі і здійснив деяку схематичну операцію - я видалив каталог .git / objects з оригіналу, і помістив у клоні. Це зробило трюк, залишивши цілі оригінальні гілки, реф. Тощо, і все, здається, працює (схрещуючи пальці).
Джек Сенечал

1
дякую за пораду щодо файлу: // клон, що зробив для мене трюк
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 (включаючи всю інформацію з відхилень). Використовуйте обережно.


2
Нарешті ... Як не дивно, я відповів раніше, коли шукав цю відповідь, але вона виглядала занадто складною ... після спроб інших речей це стало сенсом і голосом!
msanteler

@msanteler, колишній ( git-fatfiles) сценарій з’явився, коли я задав питання про IRC (Freenode / # git). Я зберег найкращу версію у файл, а потім розмістив її як відповідь тут. (Я не можу оригінального автора в журналах IRC, хоча).
Ві.

Спочатку це працює дуже добре. Але коли я знову забираю або витягую з пульта, він просто копіює всі великі файли назад в архів. Як мені це запобігти?
бенкет

1
@felbo, тоді проблема, ймовірно, не лише у вашому локальному сховищі, а й у інших сховищах. Можливо, вам потрібно зробити процедуру скрізь, або змусити всіх відмовитися від оригінальних гілок і перейти на переписані гілки. У великій команді це непросто і потребує співпраці між розробниками та / або втручанням менеджера. Іноді просто залишити вантажний камінь всередині може бути кращим варіантом.
Ві.

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
Це визнало великі об’єкти великими. Прийнята відповідь позбулася їх.
Ян Келінг

2
Різниця між git gc та git repack відповідно до linus torvalds. metalinguist.wordpress.com/2007/12/06 / ...
spuder

31

Тільки FYI, найбільшою причиною, чому ви можете виявити непотрібні об'єкти, є те, що git підтримує рефлог.

Рефлог є, щоб зберегти ваш приклад, коли ви випадково видалили свою головну гілку або якимось чином катастрофічно пошкодите ваше сховище.

Найпростіший спосіб виправити це - урізати ваші рефлоги перед стисканням (просто переконайтеся, що ви ніколи не хочете повертатися до будь-якого з комітетів у записі).

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

Це відрізняється від того, git gc --prune=todayщо у нього закінчується термін дії всього рефлогу відразу.


1
Цей зробив це для мене! Я пішов приблизно від 5gb до 32mb.
Хокі

Ця відповідь здавалася простішою, але, на жаль, не працювала для мене. У моєму випадку я працював над щойно клонованим сховищем. Це причина?
Мерт

13

Якщо ви хочете знайти, які файли займають місце у вашому сховищі git, запустіть

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

Потім витягніть посилання на крапку, яка займає найбільше місця (останній рядок), і перевірте ім'я файлу, яке займає стільки місця

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

Це може бути навіть файл, який ви видалили git rm, але 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, видалить крапку з кожного комітету в історії та запустить збір сміття, щоб звільнити місце.


7

Сценарій git-fatfiles з відповіді Ві прекрасний, якщо ви хочете побачити розмір усіх своїх крапок, але це так повільно, що бути непридатним. Я зняв обмеження на 40 рядків виходу, і він намагався використовувати всю оперативну пам’ять мого комп'ютера, а не закінчувати. Тому я переписав це: це в тисячі разів швидше, додав функції (необов’язково), і якийсь дивний помилка був видалений - стара версія дала би неточні підрахунки, якщо підсумувати вихід, щоб побачити загальний простір, який використовується файлом.

#!/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, але не мають жодної з даних репозиторію (як вказує розширення, вони є не що інше, як індекси для відповідного пакету - адже, якщо ви знаєте правильну команду, ви можете легко відтворити їх з пакувального файлу, а сам git робить це під час клонування, оскільки лише пакетний файл передається за допомогою нативного протоколу 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, що зберігаються, .gitвключають дерева, коміти та теги. Коміти та теги невеликі, але дерева можуть бути великими, особливо якщо у вашому сховищі є дуже велика кількість невеликих файлів. Скільки файлів і скільки комітетів у вас є?


Гарне питання. 19 гілок з приблизно 40 файлами в кожному. git count-objects -v каже "in-pack: 1570". Не точно знаю, що це означає чи як порахувати, скільки у мене є комітетів. Я б здогадався кілька сотень.
Іен Келінг

Гаразд, це не здається, що це відповідь. Кілька сотень будуть незначними порівняно із 145 МБ.
Грег Х'югілл

2

Ви спробували використовувати git repack ?


Гарне питання. Я зробив, я також склав враження, що git gc робить це також?
Ян Келінг

Це стосується git gc --auto Не впевнений у тому, що ви використовували.
бодотака

2

перш ніж робити фільтр git-гілки та git gc, слід переглянути теги, які є у вашому репо-репо. Будь-яка реальна система, яка має автоматичне тегування для таких речей, як безперервна інтеграція та розгортання, зробить безперешкодні об’єкти такими тегами, отже, gc cant їх видалить, і ви все одно будете цікавитись, чому розмір репо все ще такий великий.

Найкращий спосіб позбутися від усіх непотрібних речей - запустити git-filter & git gc, а потім підштовхнути майстра до нового голого репо. Нове голе репо матиме очищене дерево.


1

Це може статися, якщо ви випадково додали великий фрагмент файлів і інсценізували їх, не обов'язково їх фіксуючи. Це може статися в railsдодатку, коли ви запускаєтесь, bundle install --deploymentа потім випадково git add .ви бачите всі додані під vendor/bundleвами файли, але їх уже не ввійшло в історію git, тому вам доведеться застосувати відповідь і змінити, video/parasite-intro.aviдо vendor/bundleцього запустіть другу команду, яку він надає.

Ви бачите різницю, з git count-objects -vякою в моєму випадку перед застосуванням скрипту був розмірний пакет: 52K, а після застосування - 3,8K.


1

Варто перевірити stacktrace.log. Це в основному журнал помилок для відстеження комітетів, які не вдалися. Нещодавно я дізнався, що мій stacktrace.log - 65,5 ГБ, а мій додаток - 66,7 ГБ.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.