Почему мой репозиторий 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

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


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команды, хотя невозможно точно увидеть, где в истории фиксации репозитория имеется ссылка на объект. Если вам нужно это сделать, попробуйте что-нибудь из этого вопроса .


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


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


3

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


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

Хорошо, тогда не похоже, что это ответ. Несколько сотен будут незначительными по сравнению с 145 МБ.
— Greg Hewgill

2

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


Хороший вопрос. Я сделал, у меня тоже создалось впечатление, что git gc делает то же самое?
— Ян Келлинг

Это работает с git gc --auto Не уверен, что вы использовали.
— baudtack

2

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

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


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 ГБ.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.