Почему мой репозиторий git такой большой?

146

145M = .git / объекты / пакет /

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

Git принимает во внимание все эти вещи, поэтому я ожидал бы гораздо меньшего размера репозитория. Так почему же .git такой большой?

Я сделал:

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

Чтобы ответить на вопрос, сколько файлов / коммитов, у меня есть 19 веток по 40 файлов в каждой. 287 коммитов, найдено с использованием:

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

Информация об этом не должна занимать десятки мегабайт.

Ян Келлинг
источник
5
Линус рекомендует следующее по сравнению с агрессивным gc. Это имеет значение? git repack -a -d --depth = 250 --window = 250
Грег Бэкон,
спасибо gbacon, но без разницы.
Ян Келлинг
Это потому, что вам не хватает -f. metalinguist.wordpress.com/2007/12/06/...
spuder
git repack -a -dсократил мое репо с 956 МБ до 250 МБ . Большой успех! Благодарность!
xanderiel

Ответы:

69

Недавно я вставил неправильный удаленный репозиторий в локальный ( 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
pgs
источник
1
Вау. СПАСИБО. .git = 15M сейчас !! после клонирования вот небольшой вкладыш 1 для сохранения ваших предыдущих веток. d1 = # исходное репо; d2 = # новое репо; cd $ d1; для b в $ (git branch | cut -c 3-); выполнить git checkout $ b; х = $ (git rev-parse HEAD); cd $ d2; git checkout -b $ b $ x; cd $ d1; готово
Ян Келлинг
если вы отметите это, вы можете добавить 1 лайнер к своему ответу, чтобы он был отформатирован как код.
Ян Келлинг
1
Я по глупости добавил кучу видеофайлов в свое репо, и мне пришлось сбросить --soft HEAD ^ и повторить. После этого каталог .git / objects стал огромным, и это был единственный способ вернуть его обратно. Однако мне не понравилось, как один лайнер изменил имена моих веток (он показывал происхождение / название ветви вместо простого названия ветки). Поэтому я пошел еще дальше и провел некоторую схематичную операцию - удалил каталог .git / objects из оригинала и вставил каталог из клона. Это помогло, оставив все исходные ветки, ссылки и т. Д. Нетронутыми, и, похоже, все работает (скрещивание пальцев).
Джек Сенешал
1
спасибо за подсказку о файле: // clone, который
помог
3
@vonbrand, если вы жестко связываете файл и удаляете исходный файл, ничего не происходит, за исключением того, что счетчик ссылок уменьшается с 2 до 1. Только если этот счетчик уменьшается до 0, пространство на fs освобождается для других файлов. Так что нет, даже если файлы были жестко связаны, ничего не произойдет, если оригинал будет удален.
stefreak
159

Некоторые скрипты, которые я использую:

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-eradicate (для 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 (включая всю информацию из рефлогов). Используйте с осторожностью.

Vi.
источник
2
Наконец ... По иронии судьбы, я видел этот ответ раньше, но он выглядел слишком сложным ... после того, как попробовали другие вещи, этот начал обретать смысл и вуаля!
msanteler
@msanteler, Первый git-fatfilesскрипт ( ) появился, когда я задал вопрос по IRC (Freenode / # git). Я сохранил лучшую версию в файл, а затем разместил ее здесь в качестве ответа. (Хотя я не могу указать автора в логах IRC).
Vi.
Поначалу это работает очень хорошо. Но когда я снова получаю или получаю данные с пульта дистанционного управления, он просто копирует все большие файлы обратно в архив. Как мне это предотвратить?
пир
1
@felbo, тогда проблема наверное не только в вашем локальном репозитории, но и в других репозиториях. Может быть, вам нужно проделать процедуру повсюду или заставить всех отказаться от исходных веток и перейти на перезаписанные ветки. Это непросто в большой команде и требует сотрудничества разработчиков и / или вмешательства менеджера. Иногда лучше просто оставить камень внутри.
Vi.
1
Это отличная функция, но она невообразимо медленная. Он не может закончиться даже на моем компьютере, если я уберу ограничение в 40 строк. К вашему сведению, я просто добавил ответ с более эффективной версией этой функции. Проверьте это, если вы хотите использовать эту логику в большом репозитории, или если вы хотите увидеть размеры, суммированные для каждого файла или папки.
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команды, хотя невозможно точно увидеть, где в истории фиксации репозитория имеется ссылка на объект. Если вам нужно это сделать, попробуйте что-нибудь из этого вопроса .

CB Bailey
источник
1
Это нашло большие объекты великолепными. Принятый ответ избавил от них.
Ян Келлинг,
2
Разница между git gc и git repack по мнению Линуса Торвальдса. metalinguist.wordpress.com/2007/12/06/...
spuder
35

Просто к вашему сведению, самая большая причина, по которой вы можете остаться с нежелательными объектами, заключается в том, что git поддерживает журнал ссылок.

Reflog нужен для того, чтобы сохранить вашу задницу, когда вы случайно удалите свою основную ветку или каким-либо иным образом катастрофически повредите свой репозиторий.

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

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

Это отличается от git gc --prune=todayтого, что срок действия всего рефлога истекает немедленно.

Джон Гитцен
источник
1
Этот сделал это за меня! Я перешел с 5 ГБ до 32 МБ.
Hawkee
Этот ответ казался более простым, но, к сожалению, мне не помог. В моем случае я работал над только что клонированным репозиторием. Это причина?
Мерт
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».

piojo
источник
4

Вы уверены, что учитываете только файлы .pack, а не файлы .idx? Они находятся в том же каталоге, что и файлы .pack, но не имеют данных репозитория (как указывает расширение, они не более чем индексы для соответствующего пакета - на самом деле, если вы знаете правильную команду, вы можете легко воссоздать их из файла пакета, и git сам делает это при клонировании, поскольку только файл пакета передается с использованием собственного протокола git).

В качестве репрезентативного образца я взглянул на свой локальный клон репозитория linux-2.6:

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

Это означает, что расширение примерно на 7% должно быть обычным явлением.

Есть также файлы снаружи objects/; в моем личном опыте, из них indexи , как gitk.cacheправило, самые крупные из них ( на общую сумму 11M в моем клоне репозитория Linux-2.6).

CesarB
источник
3

Другие объекты git, хранящиеся в, .gitвключают деревья, коммиты и теги. Коммиты и теги небольшие, но деревья могут стать большими, особенно если у вас очень большое количество маленьких файлов в вашем репозитории. Сколько файлов и сколько коммитов у вас есть?

Грег Хьюгилл
источник
Хороший вопрос. 19 веток по 40 файлов в каждой. git count-objects -v говорит: «in-pack: 1570». Не уверен, что именно это означает и как посчитать, сколько у меня коммитов. Думаю, несколько сотен.
Ян Келлинг,
Хорошо, тогда не похоже, что это ответ. Несколько сотен будут незначительными по сравнению с 145 МБ.
Greg Hewgill
2

Вы пробовали использовать git repack ?

baudtack
источник
Хороший вопрос. Я сделал, у меня тоже создалось впечатление, что git gc делает то же самое?
Ян Келлинг
Это работает с git gc --auto Не уверен, что вы использовали.
baudtack
2

перед выполнением git filter-branch и git gc вы должны просмотреть теги, которые присутствуют в вашем репо. Любая реальная система, которая имеет автоматические теги для таких вещей, как непрерывная интеграция и развертывание, будет делать неотмеченные объекты по-прежнему ссылаться на эти теги, поэтому gc не может их удалить, и вы все равно будете задаваться вопросом, почему размер репо по-прежнему так велик.

Лучший способ избавиться от всего нежелательного - запустить git-filter и git gc, а затем отправить master в новое голое репо. Новое чистое репо будет иметь очищенное дерево.

v_abhi_v
источник
1

Это может произойти, если вы случайно добавили большой кусок файлов и разместили их, не обязательно фиксируя их. Это может произойти в railsприложении, когда вы запускаете, bundle install --deploymentа затем случайно git add .видите, что все файлы, добавленные под vendor/bundleвами, деактивируют их, но они уже попали в историю git, поэтому вам нужно применить ответ Ви и изменить video/parasite-intro.avi, а vendor/bundleзатем запустить вторую команду, которую он предоставляет.

Вы можете видеть разницу, с git count-objects -vкоторой в моем случае перед применением скрипта размер пакета был равен 52 КБ, а после применения - 3,8 КБ.

Юлиангонсалес
источник
1

Стоит проверить файл stacktrace.log. По сути, это журнал ошибок для отслеживания неудачных коммитов. Недавно я узнал, что мой stacktrace.log составляет 65,5 ГБ, а мое приложение - 66,7 ГБ.

Nes
источник