Олег Марков
Сбор мусора в Git с помощью git gc
Введение
Сбор мусора в Git — это процесс очистки репозитория от ненужных данных и оптимизации хранения объектов. Команда git gc (gc — garbage collection) отвечает именно за это. Без нее репозиторий со временем начинает разрастаться, операции замедляются, а в .git копятся старые ненужные объекты.
Git умеет запускать сборщика мусора автоматически, но в некоторых случаях вам нужно понимать, как он работает, когда его вызывать вручную, и как не потерять важные данные из-за слишком агрессивной очистки.
В этой статье мы разберем:
- какие объекты очищает git gc и какие сжимает;
- как именно устроена работа git gc внутри;
- когда стоит запускать сборку мусора вручную, а когда — не трогать;
- как настроить параметры, чтобы не сломать историю;
- как диагностировать проблемы с производительностью и размером репозитория.
Смотрите, я покажу вам на реальных примерах, что делает git gc, и как это влияет на ваш репозиторий.
Что такое сбор мусора в Git и зачем нужен git gc
Объектная модель Git в контексте git gc
Чтобы понять, что именно “убирает” git gc, полезно освежить, как Git хранит данные.
Внутри каталога .git/objects находятся объекты нескольких типов:
- commit — коммиты;
- tree — состояния каталогов (деревья);
- blob — содержимое файлов;
- tag — аннотированные теги.
Каждый объект имеет:
- хеш-идентификатор (SHA-1 или SHA-256 в новых конфигурациях);
- тип;
- размер;
- содержимое.
Git различает:
- Loose-объекты — хранятся по отдельности в виде отдельных файлов в
.git/objects/<2 символа>/<38 символов>; - Пакеты объектов (pack files) — сжатые коллекции объектов в
.git/objects/pack.
git gc:
- удаляет недостижимые объекты (сбор мусора в прямом смысле);
- сжимает большое количество loose-объектов в pack-файлы;
- перепаковывает уже существующие pack-файлы для лучшего сжатия;
- чистит вспомогательные файлы вроде старых reflog.
По сути, git gc — это:
- уборщик (удаление мусора);
- архиватор (упаковка и перепаковка объектов).
Как git gc ищет “мусор” и что он удаляет
Понятие достижимого и недостижимого объекта
Git считает объект достижимым, если до него можно “дойти” от одного из корневых указателей:
- ссылок в
.git/refs(ветки, теги,HEAD); - объектов, упомянутых в reflog;
- специального файла
.git/ORIG_HEADи некоторых временных ссылок.
Недостижимый объект — это объект, до которого нельзя дойти по этим ссылкам. Например:
- вы сделали коммит, а затем сделали
git reset --hard HEAD~1— старый коммит перестал быть привязан к веткам; - вы переписали историю через
git rebase, и старые коммиты заменились новыми; - вы сделали
git commit --amend, и старый коммит оказался “висящим”.
Вот таких “висящих” коммитов, деревьев и blob-объектов со временем скапливается довольно много, особенно в активных репозиториях с частыми rebases и amend.
git gc:
- проходит от всех корней (refs, reflog) и помечает достижимые объекты;
- все остальные объекты считает кандидатами на удаление;
- удаляет недостижимые объекты, которые “протухли” (старше определенного срока).
Гарантии “безопасности” — grace period
Git по умолчанию не удаляет недостижимые объекты мгновенно. Он использует время жизни (grace period). Это нужно, чтобы вы могли “откатить” неудачный reset или rebase, пока старые объекты еще доступны через reflog.
Основные параметры:
- gc.pruneExpire — по умолчанию “2 weeks ago”
- недостижимый объект моложе двух недель — сохраняется;
- старше — удаляется при следующем git gc (или git prune);
- gc.reflogExpire и gc.reflogExpireUnreachable — регулируют, сколько хранятся записи reflog.
Важно: пока запись о коммите есть в reflog, он считается достижимым, и git gc его не тронет.
Основные режимы работы git gc
Обычный запуск git gc
Базовая команда:
git gc
# Запуск сборщика мусора с настройками по умолчанию
При обычном запуске git gc:
- собирает loose-объекты в pack-файлы;
- может перепаковать существующие пакеты;
- чистит старые записи reflog;
- удаляет недостижимые объекты старше gc.pruneExpire.
В Git обычно настроен авто-запуск сборки мусора после определенного количества коммитов, fetch или изменений. Но на больших проектах разработчики иногда отключают авто-gc и запускают его вручную в “окно обслуживания”.
Принудительный запуск git gc --aggressive
Режим --aggressive делает более глубинную перепаковку объектов:
git gc --aggressive
# Более агрессивная оптимизация репозитория
Что он делает:
- использует более сложные и дорогие алгоритмы поиска дельт между объектами;
- может существенно уменьшить размер pack-файлов;
- может занять намного больше времени и ресурсов (особенно CPU).
Когда использовать:
- огромный репозиторий, который давно не чистили;
- вы только что импортировали большой монорепозиторий;
- вы сделали массовую миграцию истории (переписали много коммитов).
Когда лучше не использовать:
- на CI при каждом билде;
- на слабых машинах;
- на активных прод-репозиториях в рабочее время (процесс может “съесть” CPU).
Просушка объектов — git gc --prune и git prune
Иногда вам нужно более строго удалить мусор, чем это делает обычный git gc. Для этого есть:
git gc --prune=now
# Удалить все недостижимые объекты сразу, без grace period
Или отдельная команда:
git prune
# Низкоуровневая команда для удаления недостижимых объектов
Разница:
- git gc — высокоуровневая обертка, делает prune, repack и уборку;
- git prune — только удаление недостижимых объектов, без перепаковки.
Используйте --prune=now аккуратно. Обратите внимание: если вы удалили недостижимые объекты сразу, шанс вернуть состояние через reflog резко снижается.
Что именно делает git gc внутри
Сжатие loose-объектов в pack-файлы
При активной работе с репозиторием появляются тысячи loose-объектов. Каждый такой объект — отдельный файл. Это плохо по нескольким причинам:
- файловая система не любит огромное число маленьких файлов;
- чтение и запись по одному объекту замедляет операции;
- резервное копирование и перенос репозитория становятся тяжелее.
git gc объединяет loose-объекты в pack-файл:
git gc
# Внутри будет, по сути, вызвано что-то вроде git repack
Результат:
- в
.git/objects/packпоявляется один или несколько файлов:pack-XXXX.pack— сами данные;pack-XXXX.idx— индекс для быстрого поиска объекта по хешу;
- в
.git/objectsстановится намного меньше отдельных файлов.
Git использует дельта-сжатие: он не хранит каждый blob целиком, а строит отличия (дельты) от похожего содержимого. Например, между версиями одного файла.
Перепаковка уже существующих pack-файлов
Со временем pack-файлов может стать несколько, особенно в долгоживущих репозиториях. Это тоже не очень удобно: поиск может работать медленнее, хранение становится менее оптимальным.
git gc умеет:
- перепаковывать несколько pack-файлов в один крупный;
- избавляться от дубликатов;
- строить более выгодные цепочки дельт.
Пример ручной перепаковки (обычно не нужно, git gc сделает сам):
git repack -Ad
# -A перепаковать все объекты
# -d удалить старые pack-файлы
Очистка reflog и влияние на сбор мусора
Reflog — это журнал изменений ссылок, например HEAD и веток. Он сохраняет историю:
- перемещений веток;
- ребейзов;
- hard reset;
- merge и cherry-pick.
Пока запись о коммите есть в reflog, объект считается достижимым, даже если его больше нет в ветках и тегах.
git gc:
- удаляет старые записи reflog;
- настраивается параметрами:
- gc.reflogExpire — по умолчанию хранит записи 90 дней;
- gc.reflogExpireUnreachable — для недостижимых коммитов, по умолчанию 30 дней.
Как только запись из reflog исчезает и объект становится недостижимым, он может быть собран (удален) при следующем git gc.
Практические сценарии использования git gc
Сценарий 1. Репозиторий стал слишком большим
Вы замечаете, что .git занимает слишком много места:
du -sh .git
# Показывает общий размер каталога .git
Давайте разберемся, что можно сделать:
Посмотреть статистику пакетов:
git count-objects -vH # -v подробный вывод # -H человекочитаемые единицы (MB, GB)Пример вывода:
count: 12034 # количество loose-объектов size: 59.54 MiB # общий размер loose-объектов in-pack: 450123 # объектов в pack-файлах packs: 5 # число pack-файлов size-pack: 1.23 GiB # размер всех pack-файлов prune-packable: 234 # объекты, которые можно упаковать garbage: 0 # поврежденные или странные объектыОбратите внимание: если count и size большие — есть смысл вызвать git gc.
Запустить обычную сборку мусора:
git gcЕсли репозиторий очень большой и давно не чистился — попробовать агрессивный режим (желательно на отдельной машине или в “тихое время”):
git gc --aggressiveЕще раз проверить размер:
git count-objects -vH
Здесь вы увидите, насколько уменьшился размер loose-объектов, и изменился ли размер pack-файлов.
Сценарий 2. После rebase и reset вы хотите убрать старые версии
Вы сделали много экспериментов с историей:
- несколько rebase на разных ветках;
- пару раз делали
git reset --hard; - переписали историю через
git filter-repoилиBFG Repo-Cleaner.
В результате:
- история в ветках выглядит аккуратно;
- но внутри
.gitвсе еще лежат старые коммиты и blobs.
Покажу вам пример типичной последовательности:
# После успешной переписи истории
git reflog expire --expire=now --all
# Сразу же удалить все записи reflog
git gc --prune=now --aggressive
# Удалить все недостижимые объекты и глубоко перепаковать репозиторий
Комментарии к командам:
- reflog expire — принудительно очищает reflog, чтобы старые коммиты не считались достижимыми;
- git gc --prune=now — удаляет то, что теперь стало недостижимым, без grace period.
Используйте этот сценарий только тогда, когда вы уверены, что переписывание истории завершено, и вам не нужно будет “доставать” старые версии.
Сценарий 3. Репозиторий стал медленным — долгие fetch, status и log
Если операции Git начали работать заметно медленнее:
- долгий
git status; - медленный
git log; - ощутимые задержки при
git fetchиgit pull.
Частая причина — огромное количество исторических объектов и плохая упаковка.
Шаги:
Посмотреть количество loose-объектов:
git count-objects -vHЕсли count большой — запустить git gc:
git gcДля долговременной оптимизации настроить автоматический gc (если отключен):
git config --global gc.auto 256 # Запускать авто-gc после появления 256 loose-объектовПри очень больших репозиториях — периодически, например раз в неделю, запускать:
git gc --aggressiveЛучше делать это на серверном зеркале или в maintenance-режиме.
Настройка и управление поведением git gc
Автоматический git gc и параметры gc.auto
Git запускает автоматический сборщик мусора после некоторых операций, когда количество loose-объектов превышает порог gc.auto.
Посмотреть текущие настройки:
git config --show-origin --get-all gc.auto
# Показывает значение gc.auto и откуда оно взято
Установить значение:
git config --global gc.auto 256
# Устанавливаем порог в 256 loose-объектов
Особенности:
gc.auto = 0— отключает авто-gc;- слишком маленькое значение может приводить к частым запускам gc и задержкам;
- слишком большое — к накоплению мусора и деградации производительности.
Если вы работаете с большими репозиториями, имеет смысл:
- на локальных машинах оставить авто-gc включенным;
- на CI и серверах часто авто-gc отключают и запускают его по расписанию.
Пример отключения авто-gc:
git config --global gc.auto 0
# Полное отключение автоматического git gc
Настройка сроков хранения reflog и недостижимых объектов
Параметры:
- gc.reflogExpire — сколько хранить записи reflog для достижимых объектов;
- gc.reflogExpireUnreachable — для недостижимых;
- gc.pruneExpire — сколько хранить недостижимые объекты перед окончательным удалением.
Посмотрим пример настройки, если вы хотите более долго хранить историю:
git config --global gc.reflogExpire "180 days"
# Хранить reflog 180 дней
git config --global gc.reflogExpireUnreachable "60 days"
# Недостижимые коммиты держать 60 дней
git config --global gc.pruneExpire "60 days"
# Не удалять недостижимые объекты младше 60 дней
Если же, наоборот, вы хотите ускорить очистку:
git config --global gc.reflogExpire "30 days"
git config --global gc.reflogExpireUnreachable "7 days"
git config --global gc.pruneExpire "7 days"
Обратите внимание: слишком агрессивные значения уменьшают вашу способность “спасти” историю после ошибок.
Особенности и подводные камни git gc
Можно ли потерять данные из-за git gc
С точки зрения Git, данные “теряются” тогда, когда:
- объект становится недостижимым (ни в ветках, ни в тегах, ни в reflog);
- и выходит за пределы grace period (gc.pruneExpire);
- и был запущен git gc (или git prune).
До этого момента вы еще можете:
- найти объект через reflog;
- восстановить ветку (
git branchна нужный хеш); - отменить неудачный reset или rebase.
Поэтому основное правило:
- git gc сам по себе редко становится причиной "потери" данных;
- главная опасность — агрессивная очистка reflog и использование
--prune=now.
Если вы сомневаетесь, лучше:
- сначала выполнить обычный
git gcбез--prune=now; - не менять слишком резко gc.reflogExpire и gc.pruneExpire.
Почему git gc иногда запускается в самый неподходящий момент
Иногда, после commit или fetch, вы замечаете паузу — и в терминале видите:
Auto packing the repository in background for optimum performance
Это авто-gc:
- он запускается, когда число loose-объектов превышает gc.auto;
- может заметно нагружать процессор и диск.
Если это мешает во время CI или при сборке:
git config --global gc.auto 0
# Отключение авто-gc, чтобы запускать его вручную
Но для обычных локальных репозиториев авто-gc лучше оставить включенным.
Как диагностировать и контролировать работу git gc
Команда git count-objects
Это основной инструмент диагностики состояния хранилища:
git count-objects -vH
Покажу вам, на что смотреть:
- count — количество loose-объектов. Если значение больше нескольких тысяч — есть смысл запускать git gc;
- size — размер loose-объектов;
- in-pack — сколько объектов уже упаковано;
- size-pack — суммарный размер pack-файлов;
- garbage — наличие “мусорных” объектов, возможно поврежденных.
Если garbage больше нуля, стоит проверить целостность:
git fsck
# Проверка целостности репозитория
Проверка и восстановление после ошибок
Если после git gc вы подозреваете проблемы:
Проверить репозиторий:
git fsck --full # Глубокая проверка, ищет недостающие объекты и поврежденияЕсли git fsck сообщает о недостающих объектах:
- попробуйте найти хеши в reflog:
bash git reflog # Просмотр истории перемещений ссылок - если коммиты еще где-то есть (на удаленном сервере) — попробуйте сделать
git fetch.
- попробуйте найти хеши в reflog:
Если у вас есть клон или зеркало, где проблема не проявляется:
- можно пере-клонировать;
- или восстановить поврежденные объекты из зеркала.
Рекомендации по использованию git gc в разных сценариях
Локальная разработка
Для обычных локальных репозиториев:
- оставьте авто-gc включенным (значение gc.auto по умолчанию обычно разумно);
- иногда (раз в несколько недель) можете вручную запускать:
bash git gc - агрессивный режим используйте редко, например после больших чисток истории.
CI и билд-сервера
На CI часто используются временные клоны, которые живут недолго. Там можно:
- отключить авто-gc:
bash git config --global gc.auto 0 - периодически чистить кеши или зеркала репозиториев, если они используются;
Если есть shared репозитории, которые кэшируются внутри CI, имеет смысл настроить плановый git gc в расписании (cron или встроенные механизмы CI).
Центральные репозитории и зеркала
На серверных зеркалах:
- авто-gc нередко отключают, чтобы он не запускался в рабочее время;
- делают плановый git gc (в том числе с
--aggressive) по расписанию ночью или в выходные; - более аккуратно настраивают gc.reflogExpire и gc.pruneExpire, чтобы не удалять данные слишком рано.
Заключение
git gc — это ключевой механизм в Git, отвечающий за:
- удаление недостижимых и устаревших объектов;
- упаковку loose-объектов в эффективные pack-файлы;
- оптимизацию размера и производительности репозитория.
Вы увидели, как:
- Git отличает достижимые и недостижимые объекты;
- reflog влияет на сбор мусора;
- настраиваются параметры срока хранения объектов;
- использовать обычный и агрессивный режимы git gc;
- проверять состояние репозитория через git count-objects и git fsck.
Если вы понимаете, что именно делает git gc и как его настраивать, вы можете:
- поддерживать репозитории компактными и быстрыми;
- планировать обслуживание больших репозиториев;
- уменьшать риск потери данных при переписывании истории.
Частозадаваемые технические вопросы по теме статьи и ответы на них
1. Как предотвратить запуск git gc в конкретной операции (например, при git fetch на CI)
Вы можете временно отключить авто-gc только для одной команды, переопределив конфиг через переменную окружения:
GIT_CONFIG_COUNT=1 \
GIT_CONFIG_KEY_0=gc.auto \
GIT_CONFIG_VALUE_0=0 \
git fetch origin
# Здесь авто-gc не будет запущен
Так вы не меняете глобальную конфигурацию, но подавляете авто-gc именно для этого вызова.
2. Как посмотреть, запускался ли git gc и какие действия он выполнял
Логи git gc обычно попадают в общий лог Git, если вы включите трассировку:
GIT_TRACE=1 GIT_TRACE_PERFORMANCE=1 git gc
# В вывод попадет информация о действиях, времени и шагах
Также посмотрите файлы в .git/logs/ (это reflog) и системные лог-файлы вашей системы, если git gc запускается как часть серверного обслуживания.
3. Как полностью переупаковать репозиторий без удаления недостижимых объектов
Если вы хотите только оптимизировать pack-файлы, но не трогать недостижимые объекты:
git repack -Adf
# -A перепаковать все объекты
# -d удалить старые пакеты
# -f пересоздать дельты даже для уже упакованных объектов
Эта команда не запускает prune и не удаляет недостижимые объекты.
4. Что делать, если git gc завершается с ошибкой из-за недостатка места на диске
Действия по шагам:
- Освободите немного места (хотя бы несколько процентов диска).
- Временное решение — отключить авто-gc:
bash git config --global gc.auto 0 - Если репозиторий очень большой, перенесите его на диск с большим объемом и затем запустите:
bash git gc --aggressive - После успешного завершения gc можно снова включить авто-gc с разумным порогом.
5. Как ограничить использование памяти git gc на машине с небольшим объемом RAM
Git предоставляет параметры для ограничения памяти при упаковке:
git config --global pack.windowMemory "256m"
git config --global pack.packSizeLimit "512m"
Эти настройки подскажут Git не выходить за указанные пределы при создании pack-файлов. После этого можно запустить git gc с меньшим риском “съесть” всю доступную память.
Постройте личный план изучения Git до уровня Middle — бесплатно!
Git — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Git
Лучшие курсы по теме

Основы Git
Антон Ларичев
TypeScript с нуля
Антон Ларичев