$git rm git.tgz
rm 'git.tgz'
$git commit -m 'oops - removed large tarball'
[master dadf725] oops - removed large tarball
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 git.tgz
Теперь запустим сборщик мусора и посмотрим, сколько места мы занимаем:
$git gc
Counting objects: 17, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (13/13), done.
Writing objects: 100% (17/17), done.
Total 17 (delta 1), reused 10 (delta 0)
Чтобы быстро узнать, сколько места занято, можно воспользоваться командой count-objects:
$git count-objects -v
count: 7
size: 32
in-pack: 17
packs: 1
size-pack: 4868
prune-packable: 0
garbage: 0
size-garbage: 0
Строка size-pack — это размер pack-файлов в килобайтах, то есть всего занято почти 5 MБ. Перед последним коммитом использовалось около 2 КБ — очевидно, удаление файла не удалило его из истории. Всякий раз, когда кто-либо захочет склонировать этот репозиторий, ему придётся скачивать все 5 МБ для того, чтобы заполучить этот крошечный проектик, просто потому, что однажды вы имели неосторожность добавить большой блоб! Давайте же исправим это!
Для начала найдём проблемный файл. В данном случае, мы заранее знали, что это за файл. Но если бы не знали, как можно было бы определить, какие файлы занимают много места? При вызове git gc все объекты упаковываются в один pack-файл, но, несмотря на это, определить самые крупные файлы можно, запустив служебную команду git verify-pack, и отсортировав её вывод по третьей колонке, в которой записан размер файла. Так как нас интересуют самые крупный файлы, оставим три последние строки с помощью tail:
$git verify-pack -v .git/objects/pack/pack-29…69.idx \
| sort -k 3 -n \
| tail -3
dadf7258d699da2c8d89b09ef6670edb7d5f91b4 commit 229 159 12
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob 22044 5792 4977696
82c99a3e86bb1267b236a4b6eff7868d97489af1 blob 4975916 4976258 1438
Большой объект в самом внизу, его размер — 5 МБ. Для того чтобы узнать, что это за файл, воспользуемся командой rev-list, которая уже упоминалась в главе Enforcing a Specific Commit-Message Format. Если передать ей ключ --objects, она выдаст хеши всех коммитов, а также хеши объектов и соответствующие им имена файлов. Воспользуемся этим для определения имени выбранного объекта:
$git rev-list --objects --all | grep 82c99a3
82c99a3e86bb1267b236a4b6eff7868d97489af1 git.tgz
Теперь необходимо удалить данный файл из всех деревьев в прошлом. Легко получить все коммиты, которые меняли данный файл:
$git log --oneline --branches -- git.tgz
dadf725 oops - removed large tarball
7b30847 add git tarball
Необходимо переписать все коммиты, начиная с 7b30847 для полного удаления этого файла из истории. Воспользуемся командой filter-branch, о которой мы писали в Исправление истории:
$git filter-branch --index-filter \
'git rm --ignore-unmatch --cached git.tgz' -- 7b30847^..
Rewrite 7b30847d080183a1ab7d18fb202473b3096e9f34 (1/2)rm 'git.tgz'
Rewrite dadf7258d699da2c8d89b09ef6670edb7d5f91b4 (2/2)
Ref 'refs/heads/master' was rewritten
Опция --index-filter похожа на --tree-filter, использовавшуюся в главе Исправление истории, за исключением того, что вместо передачи команды, модифицирующей файлы на диске, мы используем команду, изменяющую файлы в индексе.
Вместо удаления файла чем-то вроде rm file, мы используем git rm --cached, так как нам надо удалить файл из индекса, а не с диска. Причина, по которой мы делаем именно так — скорость: нет необходимости извлекать каждую ревизию на диск, чтобы применить фильтр, а это может очень сильно ускорить процесс. Если хотите, можете использовать и tree-filter для получения аналогичного результата. Опция --ignore-unmatch команды git rm отключает вывод сообщения об ошибке в случае отсутствия файлов, соответствующих шаблону. Ещё один момент: мы указали команде filter-branch переписывать историю, начиная с коммита 7b30847, потому что мы знаем, что именно в этом изменении впервые появилась проблема. По умолчанию перезапись начинается с самого первого состояния, что потребовало бы гораздо больше времени.
Теперь история не содержит ссылок на данный файл. Однако, в reflog и в новом наборе ссылок, добавленном Git в .git/refs/original после выполнения filter-branch, ссылки на него всё ещё присутствуют, поэтому необходимо их удалить, а потом переупаковать базу. Необходимо избавиться от всех возможных ссылок на старые коммиты перед переупаковкой:
$rm -Rf .git/refs/original
$rm -Rf .git/logs/
$git gc
Counting objects: 15, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (15/15), done.
Total 15 (delta 1), reused 12 (delta 0)
Проверим, сколько места удалось освободить:
$git count-objects -v
count: 11
size: 4904
in-pack: 15
packs: 1
size-pack: 8
prune-packable: 0
garbage: 0
size-garbage: 0
Размер упакованного репозитория сократился до 8 КБ, что намного лучше, чем 5 МБ. Из значения поля size видно, что большой объект всё ещё хранится в одном из ваших "рыхлых" объектов, но, что самое главное, при любой последующей отправке данных наружу (а значит и при последующих клонированиях репозитория) он передаваться не будет. Если очень хочется, можно удалить его навсегда локально, выполнив git prune --expire:
Читать дальше