open atlas
↑ К треку
Git: от нуля до сеньора GIT · 09 · 01

Работа с очень большими репозиториями

Полный клон скачивает каждый объект из каждого коммита. Поверхностный клон (--depth 1) обрезает историю для быстрого CI; частичный клон (--filter=blob:none) подтягивает блобы по требованию; sparse-checkout заполняет лишь часть рабочего дерева монорепо. У каждого свой компромисс.

GIT Senior ◷ 18 min
Уровень
ОсновыJuniorMiddleSenior

git clone корпоративного монорепо крутится уже одиннадцать минут. Он тянет пятнадцать лет истории и всё рабочее дерево, тогда как твоей CI-задаче нужен только последний коммит, а тебе на ноутбуке — лишь один сервис, который ты ведёшь. Обычный клон копирует каждый объект из каждого коммита — каждую старую версию каждого файла — независимо от того, посмотришь ты на неё когда-нибудь или нет.

К концу этого урока ты сможешь выбрать правильную стратегию дешёвого клона — поверхностную, частичную или sparse — для CI-раннера, разработчика в монорепо или скачивания при слабом канале, и объяснить, что каждая из них скачивает, а что нет.

Цель

После этого урока ты сможешь выбрать и выполнить правильный клон для большого репозитория — --depth, --filter или sparse-checkout — и объяснить, какой компромисс по истории, объектам и рабочему дереву делает каждый из них.

1

Полный клон скачивает каждый объект из всей истории — вот с какой ценой ты борешься. Git хранит проект как коммиты, указывающие на деревья, указывающие на блобы (содержимое файлов). Клон по умолчанию переносит все из них: каждый блоб каждого файла, каким он был в каждом коммите, плюс все деревья и коммиты, связывающие их. Для долгоживущего репо с большими или часто меняющимися файлами история затмевает текущий чекаут — можно скачать гигабайты, чтобы получить рабочее дерево на 200 МБ. Три приёма ниже отрезают разные куски этого переноса.

2

Поверхностный клон обрезает историю: скачать недавние коммиты, а не все. --depth N просит у сервера только последние N коммитов ветки, отбрасывая всё, что старше.

# Только верхний коммит — типично для CI
git clone --depth 1 https://example.com/big/repo.git

# Несколько недавних коммитов, только одна ветка
git clone --depth 50 --single-branch --branch main https://example.com/big/repo.git

--single-branch (подразумевается при --depth) избегает скачивания истории каждой другой ветки. Это стандартный клон для CI: сборке нужно только текущее дерево и, может быть, пара коммитов для changelog, но никогда не вся прошлая история. Подвох в том, что операции с историей оказываются искалечены — об этом ниже.

3

Частичный клон сохраняет полную историю, но откладывает тяжёлое содержимое файлов. --filter=blob:none скачивает все коммиты и деревья сразу, но никаких блобов — содержимое файлов подтягивается лениво с сервера в тот момент, когда оно тебе реально понадобится (чекаут, diff, git log -p).

# Все коммиты/деревья сейчас; блобы по требованию
git clone --filter=blob:none https://example.com/big/repo.git

# Пропустить только большие блобы, мелкие держать локально
git clone --filter=blob:limit=1m https://example.com/big/repo.git

В отличие от поверхностного клона, ты сохраняешь полный граф коммитов, поэтому git log, git blame и git bisect работают — они лишь запускают подтягивание по требованию, когда касаются старого содержимого файла. Цена: нужен сервер, поддерживающий протокол частичного клона (GitHub, GitLab, Gitea, свежие Git-серверы умеют), и он медленнее офлайн, потому что недостающие блобы требуют сети.

4

Sparse-checkout сжимает рабочее дерево: заполнить только нужные тебе каталоги. Поверхностный и частичный клоны сжимают скачивание; sparse-checkout сжимает то, что ложится на диск. В монорепо можно склонировать только метаданные, затем выгрузить несколько путей:

git clone --filter=blob:none --no-checkout https://example.com/monorepo.git
cd monorepo
git sparse-checkout init --cone
git sparse-checkout set services/payments libs/shared
git checkout main

В cone-режиме ты перечисляешь каталоги, и Git заполняет только их (плюс файлы в корне репо); другая тысяча сервисов никогда не появляется в твоём дереве, но репо по-прежнему знает о них. В сочетании с --filter=blob:none ты скачиваешь блобы лишь для тех путей, которые реально заполняешь, — золотая середина для работы в одном углу гигантского монорепо.

Граничные случаи

Поверхностные клоны ломают операции, зависящие от истории. При --depth 1 нет базы слияния до верхнего коммита, поэтому git log останавливается на одном коммите, git blame не может добраться до старых авторов, а git merge или git rebase против старой ветки могут упасть или вести себя странно. CI, которому нужен настоящий diff против базовой ветки, должен сначала углубиться — git fetch --deepen=50 или git fetch --unshallow, чтобы восстановить полную историю. Считай поверхностный клон одноразовым, а не местом, где делаешь работу с историей.

Разбор примера

Одно монорепо на 8 ГБ, три человека, три клона.

CI-раннер собирает сервис payments на каждый push. Ему никогда не нужна история и никогда не нужны другие сервисы. Он запускает git clone --depth 1 --single-branch --branch "$CI_COMMIT_BRANCH" $REPO — несколько сотен мегабайт, секунды, а не минуты. Когда шагу нужен diff против main, он только тогда добавляет git fetch --deepen=100.

Разработчик payments весь день живёт в services/payments, но иногда запускает git blame, чтобы понять, зачем существует строка. Ему нужна полная история, но не 8 ГБ блобов каждой другой команды. Он клонирует --filter=blob:none --no-checkout, затем sparse-checkout set services/payments libs/shared и checkout main. На диске два каталога; git log и git blame работают, подтягивая редкий старый блоб по требованию.

Аналитик на отельном Wi-Fi всего лишь раз хочет прочитать последние конфиги. Он клонирует --depth 1 --filter=blob:limit=100k, чтобы даже верхушка скачивала только мелкие файлы, а немногие крупные подгрузились бы, если он их откроет. То же репо, три переноса — от мегабайтов до гигабайтов — выбранные, а не случайные.

Почему это работает

Ничто из этого не помогает с большими бинарными файлами, которые часто меняются: дизайн-ассет на 50 МБ, коммитимый еженедельно, раздувает историю как ни клонируй, потому что каждая версия — это полный новый блоб. Для этого есть Git LFS (Large File Storage): отслеживаемые шаблоны (git lfs track "*.psd") заменяются в репо крошечными текстовыми указателями, а настоящие байты живут на отдельном LFS-сервере и подтягиваются только для тех версий, которые ты выгружаешь. Частичный клон откладывает блобы, уже имеющиеся в истории; LFS вообще не пускает гигантские бинарники в хранилище объектов Git. Они сочетаются: многие большие монорепо используют LFS и частичные/sparse-клоны вместе.

Проверь себя
Викторина

Разработчику нужна полная история (для git blame и bisect), но он не хочет заранее скачивать каждую старую версию каждого файла. Что подходит лучше всего?

Итог

Клон по умолчанию копирует каждый объект из каждого коммита, что расточительно на больших репо. Поверхностный клон (--depth N, обычно --depth 1 --single-branch) обрезает историю для быстрых одноразовых CI-чекаутов — но ломает blame, bisect и diff против базовой ветки, пока не сделаешь --deepen или --unshallow. Частичный клон (--filter=blob:none) сохраняет полный граф коммитов и подтягивает содержимое файлов лениво, требуя поддерживающего сервера. Sparse-checkout (git sparse-checkout set <dirs>, cone-режим) заполняет лишь выбранные каталоги монорепо и естественно сочетается с частичным клоном. Для часто меняющихся больших бинарников Git LFS вообще держит байты вне истории. Дальше ты увидишь, как встроить целый другой репозиторий внутрь своего — через сабмодули и subtree.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.