Устранение медленных операций Git: частые ошибки и решения

Диагностируйте медленные команды Git, разделяя причины: статус, клонирование, получение, отправка, хуки, файловая система, сеть и размер репозитория.

Устранение медленных операций Git: частые ошибки и решения

Медленный Git может иметь разные причины в зависимости от того, какая команда работает медленно. Медленный git status обычно связан с локальной файловой системой или индексом. Медленный git fetch часто вызван сетью, размером удалённого репозитория или переговорами. Медленный git checkout может быть следствием большого количества файлов, работы антивируса, проблем с разрежённым checkout или генерируемыми файлами. Медленный git push может быть вызван большими объектами, хуками, сжатием или удалённым сервером.

Поэтому первое решение — не git gc. Первое решение — измерить конкретную операцию.

На macOS или Linux:

time git status
time git fetch --prune
time git checkout main

На PowerShell:

Measure-Command { git status }

Запустите команду дважды. Первый запуск может быть медленнее, потому что кэш ОС холодный. Если первый git status занимает десять секунд, а второй — одну, возможно, дело в поведении дискового кэша. Если оба медленные, продолжайте исследование.

Git имеет встроенную трассировку, которая может показать, где тратится время:

GIT_TRACE=1 git status
GIT_TRACE_PERFORMANCE=1 git status
GIT_TRACE_PACKET=1 GIT_TRACE=1 git fetch

GIT_TRACE_PACKET очень шумный, но полезен, когда fetch или push зависают во время переговоров протокола. Не вставляйте вывод трассировки с URL частных репозиториев или токенами в публичные тикеты.

Когда git status медленный

git status проверяет индекс и рабочее дерево. Он становится медленным, когда в репозитории огромное количество файлов, рабочее дерево находится на медленной файловой системе, чтение метаданных файлов дорогое или другая программа сканирует каждый файл, к которому обращается Git.

Начните с основ:

git status --short
git config --show-origin --get core.fsmonitor
git config --show-origin --get core.untrackedCache
git config --show-origin --get core.preloadIndex

Для больших рабочих деревьев эти настройки могут помочь на многих системах:

git config core.untrackedCache true
git config core.preloadIndex true

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

Встроенный монитор файловой системы Git может ускорить status, избегая полного сканирования на поддерживаемых платформах и версиях Git:

git config core.fsmonitor true

Если после включения status стал некорректным или странным, отключите его и обновите Git перед повторной попыткой:

git config --unset core.fsmonitor

Неотслеживаемые файлы могут быть скрытой проблемой. Результаты сборки, каталоги зависимостей, сгенерированные отчёты и локальные логи обычно следует игнорировать. Проверьте, что сканирует Git:

git status --untracked-files=all --short | head -100

Если вы видите node_modules/, dist/, .venv/, target/ или подобные сгенерированные каталоги, добавьте правильные шаблоны в .gitignore. Не игнорируйте исходные файлы только для ускорения status. Игнорируйте файлы, которые действительно не должны быть под версионным контролем.

На Windows сканирование антивирусом в реальном времени — частая причина медленной работы Git. Git читает много маленьких файлов внутри .git и рабочего дерева, и защитное ПО может проверять каждый доступ. Если ваша организация позволяет, исключите доверенные рабочие пространства разработки из сканирования в реальном времени. Не исключайте каталоги, где вы запускаете непроверенный код.

Также избегайте размещения активных репозиториев в папках облачной синхронизации, таких как OneDrive, Dropbox или iCloud Drive. Инструменты синхронизации могут блокировать файлы, перезаписывать метаданные и конкурировать с собственными файловыми операциями Git.

Когда клонирование или fetch медленные

Медленное клонирование может означать большую историю, много больших blob-объектов, медленный удалённый репозиторий или сетевой путь с высокой задержкой. Измерьте размер репозитория после клонирования:

git count-objects -vH
du -sh .git 2>/dev/null

Для CI-задач и временных сред используйте shallow clone, когда история не нужна:

git clone --depth 1 <url>

Для сборки ветки:

git clone --depth 1 --branch main <url>

Shallow clone не идеален для каждого рабочего процесса. Команды, которым нужна история, теги, merge base или вычисления версий, могут завершиться ошибкой или дать неполные ответы. В CI это часто приемлемо. На машине разработчика это может быть неприятно.

Partial clone полезен, когда нужна история репозитория, но файловые blob-объекты можно загружать лениво:

git clone --filter=blob:none <url>

Это лучше всего работает с современными Git-серверами, которые хорошо поддерживают partial clone. Протестируйте с вашим хостом, прежде чем делать это официальной рекомендацией для команды.

Если вам нужна только одна часть монорепозитория, объедините sparse checkout с обычным или partial clone:

git clone --filter=blob:none --sparse <url>
cd repo
git sparse-checkout set services/api shared/lib

Sparse checkout уменьшает размер рабочего дерева. Он не делает магически каждую операцию Git дешёвой, но помогает, когда количество файлов — основная проблема.

Для fetch с множеством удалённых удалённых веток удалите устаревшие ссылки:

git fetch --prune

Чтобы сделать это поведением по умолчанию:

git config --global fetch.prune true

Когда push медленный

Скорость push зависит от того, сколько новых данных объектов вы отправляете, насколько дорога локальная упаковка, выполняются ли хуки и как быстро удалённый репозиторий принимает pack.

Проверьте, не закоммитили ли вы случайно большие файлы:

git rev-list --objects --all | sort -k 2 | tail

Эта команда грубая, потому что не показывает размеры. Для более глубокого анализа используйте такие инструменты, как git-sizer или команды анализа git filter-repo, если они доступны. Практический смысл прост: если видео, дамп базы данных, архив или артефакт сборки попали в историю, каждый клон может платить за это, пока история не будет переписана или проект не перейдёт на лучший шаблон хранения.

Git LFS — обычный ответ для больших бинарных активов, которые принадлежат проекту, но не должны жить как обычные blob-объекты Git:

git lfs install
git lfs track "*.psd"
git lfs track "*.mp4"
git add .gitattributes

Git LFS помогает больше всего, когда его принимают до того, как большие файлы попадут в историю. Миграция существующей истории возможна, но она переписывает коммиты и требует координации команды.

Будьте осторожны со старым советом увеличивать http.postBuffer. Его часто предлагают для проблем с push, но он редко исправляет общую медлительность в современном Git. Если push завершается ошибкой с конкретными HTTP-ошибками, проверьте точную ошибку, прокси, ограничения сервера и версию Git, прежде чем применять случайные настройки буфера.

Обслуживание репозитория: git gc, графы коммитов и переупаковка

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

Начните с безопасной команды обслуживания:

git maintenance run

Или более старая команда:

git gc

Избегайте делать git gc --prune=now вашим первым действием. Немедленная очистка удаляет недостижимые объекты, которые в противном случае могли бы быть восстановлены в течение некоторого времени. Это может быть нормально, когда вы знаете, что делаете, но это не безвредная кнопка ускорения.

Для репозиториев с большой историей графы коммитов могут ускорить обход истории, используемый такими командами, как log, merge-base и fetch negotiation:

git commit-graph write --reachable

Современное обслуживание Git может делать это за вас. Проверьте вашу версию:

git --version

Поддержание Git в актуальном состоянии — одно из наименее драматичных улучшений производительности. Новые версии регулярно улучшают sparse checkout, partial clone, мониторинг файловой системы и поведение обслуживания.

Большие репозитории и монорепозитории

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

Для репозиториев с большим количеством бинарных файлов переместите большие активы в Git LFS или хранилище артефактов. Для сгенерированных файлов прекратите коммитить результаты, которые можно пересобрать. Для монорепозиториев используйте sparse checkout и инструменты сборки, которые понимают границы проекта. Для CI избегайте полных клонов, если задаче не нужна полная история.

Полезная настройка монорепозитория для разработчика, работающего над одним сервисом, может быть такой:

git clone --filter=blob:none --sparse <url>
cd repo
git sparse-checkout set services/billing packages/common

Полезная настройка CI для простой задачи тестирования может быть такой:

git fetch --depth 50 origin main

Правильная глубина зависит от задачи. Если ваш инструмент версионирования использует теги из месяцев назад, глубина 1 сломает его.

Хуки и внешние инструменты

Git может быть не медленной частью. Хук pre-commit может запускать форматтеры, линтеры, тесты, сканеры секретов или проверки зависимостей. Хук post-checkout может пересобирать файлы. Помощник с учётными данными может приостанавливаться, пытаясь разблокировать связку ключей.

Проверьте хуки:

git config --get core.hooksPath
ls -l .git/hooks .githooks 2>/dev/null

Временно сравните с отключёнными хуками, только если вы понимаете риск:

git commit --no-verify

Для команд, не связанных с коммитом, переместите или отключите хук в тестовой копии репозитория, а не удаляйте командные хуки из вашего основного checkout.

Если IDE замедляет Git, но терминал быстрый, проверьте интеграции Git в IDE. Некоторые инструменты запускают git status повторно, сканируют неотслеживаемые файлы или обновляют состояние ветки в фоне.

Сетевые и удалённые проверки

Для удалённых операций отделите Git от сетевого пути. Попробуйте:

GIT_TRACE_PERFORMANCE=1 git ls-remote <url>
GIT_TRACE_PERFORMANCE=1 git fetch

Если git ls-remote медленный, задержка происходит до передачи большей части данных репозитория. Подумайте о DNS, прокси, VPN, SSH-аутентификации, доступности удалённого репозитория или запросах учётных данных. Если ls-remote быстрый, но fetch медленный, более вероятны размер данных репозитория и переговоры.

Для SSH-удалённых репозиториев протестируйте SSH напрямую:

ssh -T [email protected]

Используйте ваш фактический Git-хост. Для HTTPS-удалённых репозиториев запросы менеджера учётных данных могут быть скрыты за окнами GUI. Зависший fetch может ждать аутентификации.

Краткое дерево решений

Если git status медленный, проверьте неотслеживаемые файлы, сгенерированные каталоги, антивирус, папки облачной синхронизации, монитор файловой системы и настройки индекса.

Если клонирование медленное, рассмотрите shallow clone, partial clone, sparse checkout, Git LFS и проверьте, содержит ли история репозитория большие blob-объекты.

Если fetch медленный, удалите устаревшие ссылки, обновите Git, проверьте сетевые трассировки и проверьте, есть ли на удалённом репозитории много веток или тегов.

Если push медленный, ищите большие новые объекты, медленные хуки, проверки на стороне сервера и проблемы с сетью или прокси.

Если все команды Git медленные, проверьте состояние диска, свободное место, защитное ПО, версию Git и то, находится ли репозиторий на сетевом монтировании.

Лучшее решение — то, которое соответствует измеренному узкому месту. Работа с производительностью Git становится запутанной, когда применяются все предложения сразу. Измените одно, измерьте снова и оставьте изменение, только если оно действительно помогает.

Исправления на уровне команды лучше личных настроек

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

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

Для репозиториев с законными большими активами определите политику вместо того, чтобы полагаться на память. Например: исходный код в Git, экспорт дизайна в Git LFS, артефакты сборки в репозитории артефактов, дампы баз данных в контролируемом хранилище, а локальные временные файлы игнорируются. Поместите эти правила в .gitattributes и .gitignore, чтобы Git мог обеспечивать форму репозитория.

CI заслуживает собственного обзора. Многие конвейеры клонируют полную историю, потому что это было значением по умолчанию, скопированным годы назад. Если задача запускает только модульные тесты, ей могут не понадобиться все теги и все ветки. Если задача собирает релиз, ей могут понадобиться теги, но не каждый blob в монорепозитории. Измеряйте время клонирования отдельно от времени сборки, чтобы стоимость репозитория была видна.

Простой аудит CI задаёт вопросы:

Нужна ли этой задаче полная история?
Нужны ли ей теги?
Нужен ли ей каждый подмодуль?
Нужна ли ей каждая директория в монорепозитории?
Загружает ли она файлы LFS, которые никогда не читает?

Честные ответы на эти вопросы часто экономят больше времени, чем настройка obscure-опций Git.

Наконец, задокументируйте рекомендуемую команду клонирования для проекта. Если новые разработчики должны использовать partial clone и sparse checkout, скажите об этом в README. Если им нужен Git LFS перед checkout, скажите и это. Рекомендации по производительности, которые живут только в истории оболочки одного старшего разработчика, не помогают следующему человеку.