Split монорепозитория: почему ломаются сборка и права
Пока в репозитории один продукт, единое дерево экономит время: общие библиотеки, единый стиль, один прогон проверок на все изменения. Как только рядом появляются несколько сервисов и платформ, рост кодовой базы начинает опережать возможности команды: правка в одном модуле запускает сборку и тесты соседей, а кэш инкрементальной сборки рассыпается от чужого изменения в общих зависимостях. На крупных деревьях полный прогон занимает часы, и это напрямую бьёт по частоте релизов.
Второе узкое место — выгрузка репозитория и разграничение доступа. Полная история за годы работы весит десятки гигабайт, и новая машина разработчика или агент сборки тратит на неё больше времени, чем на саму работу. Git ограничивает права на уровне репозитория, а не каталога, поэтому выбор невелик: либо все видят весь код, включая платёжные и административные модули, либо сборка идёт через сервисные учётные записи с вручную поддерживаемыми исключениями. Обе крайности стоят дорого — и в скорости, и в рисках.
Третья причина — история коммитов. При разделении она должна переехать вместе с кодом: разбор авторства строк и ссылки на задачи нужны, чтобы разбирать инциденты и находить причину регрессий, а не восстанавливать контекст по памяти. Дальше разбираем, как разделение монорепозитория закрывает все три точки роста — от подготовки дерева и прав до способа сохранить историю.
Что даёт разделение: скорость сборки и независимые релизы
В монорепозитории любое изменение запускает сборку всего дерева: правка в одном сервисе тянет установку зависимостей и проверки для всех остальных. После разделения конвейер собирает только репозиторий изменённого сервиса — время сборки падает с десятков минут до нескольких, а очередь сборок перестаёт быть узким местом, в котором ждут все команды сразу.
Независимые релизы — следствие той же границы. Тег в репозитории сервиса запускает только его конвейер, поэтому уходит класс отказов «одна команда сломала общий ствол и заблокировала выпуск всех». Каждая команда выпускает свою часть по своему графику, а совместимость версий фиксируется контрактами и версиями зависимостей, а не общей веткой, которую все обязаны держать зелёной.
Сокращение времени восстановления получается из того же свойства: откат затрагивает один репозиторий и один тег, а не пересборку всей кодовой базы. Область поражения при сбое ограничена сервисом — неудачный выпуск откатывается за минуты, без ожидания, пока остальные команды приведут общий ствол в рабочее состояние.
Сужается и периметр доступа: права выдаются на уровне репозитория, а не на всю кодовую базу, поэтому подрядчик или новая команда видит только свои каталоги, свои секреты и свои правила защиты веток. Зоны ответственности команд становятся явными — владелец репозитория отвечает за его сборку, выпуск и дежурство, а не растворяется в общем потоке чужих изменений.
Способы split монорепозитория: сравнение и ограничения
Выбор способа определяется двумя вопросами: нужен ли подпроекту обратный вклад в монорепозиторий и надолго ли разделяются линии разработки. Полное разделение, частичный вынос с зеркалированием и разовая выгрузка дают разный баланс между чистотой границ и стоимостью поддержки синхронизации.
История при любом подходе переносится с авторством и датами — она теряется только при явном «схлопывании» коммитов. Поэтому ограничения выбирают не по объёму истории, а по тому, как часто изменения пойдут в обе стороны.
| Подход к переносу подпроекта | Когда подходит | Ограничения подхода |
|---|---|---|
| Полное разделение на отдельные репозитории | команды и релизные циклы разъезжаются, общий код перестаёт быть общим | обратная сборка требует ручной настройки, сквозные ссылки и общие зависимости рвутся |
| Частичный вынос подпроекта с зеркалированием (git subtree) | подпроект нужен и внутри монорепозитория, и снаружи, ожидается обратный вклад | двойная история коммитов, синхронизация вручную, при правках с обеих сторон растут конфликты |
| Разовая выгрузка истории (splitsh, git filter-repo) | нужен снапшот для внешней команды или публикации | односторонний поток: изменения снаружи возвращаются патчами |
Перед выбором историю и ссылки проверяют на тестовом клоне: только так видно, что инструмент делает с коммитами слияния и подписями.
Порядок работ: от аудита истории до передачи схем
Прежде чем что-то переносить, мы восстанавливаем картину репозитория: какие каталоги развиваются обособленно, где история смешана правками соседних модулей, какие ветви и теги ещё в работе. Аудит истории сразу показывает, какая схема разделения выдержит, а какая породит дубли коммитов и битые ссылки на файлы.
- Аудит истории и активных ветвей. Проходим по каталогам и слияниям, отмечаем долгоживущие ветви и релизные теги — так определяются границы будущих репозиториев.
- Проектирование схемы репозиториев. Фиксируем, что выносится отдельно, какой код остаётся общим, как связаны версии и кто отвечает за каждый кусок.
- Перенос на копии репозитория. Прогоняем git subtree или splitsh на клоне: история переезжает с авторами и датами, а исходный репозиторий продолжает собираться.
- Проверка целостности. Сверяем число коммитов, ветви и теги, сравниваем содержимое по контрольным суммам и собираем проект из нового репозитория.
- Настройка сборки. Переводим сборочные задания на новые адреса, настраиваем триггеры и права доступа, чтобы релиз проходил без ручных шагов.
- Передача регламентов и обучение. Отдаём схему репозиториев, описание сборочных заданий и правила работы с ветвями, разбираем их с командой.
Если общий репозиторий продолжает жить, отдельно описываем, как правки возвращаются в него и как это отслеживается.
Комплект передачи: конфигурации, схемы, регламенты
Разделение монорепозитория заканчивается не первой удачной отправкой изменений, а моментом, когда ваша команда сама собирает любой выделенный репозиторий и при необходимости повторяет разбиение на новой ветке. Поэтому передаём не только результат, но и всё, чем он воспроизводится.
- Конфигурации разделения — набор фильтров для git subtree split и манифест для splitsh: префиксы, ветки, правила переноса путей. Повторный прогон запускается одной командой.
- Схемы зависимостей — карта модулей и общих библиотек: что переехало, что осталось в монорепозитории и где теперь источник истины для каждой общей части.
- Политики доступа — матрица прав по репозиториям и веткам: кто публикует изменения напрямую, кто только предлагает их, у кого есть право на принудительный откат.
- Проверочная обвязка — сценарий, который прогоняет разделение на тестовой ветке, сравнивает деревья файлов до и после и останавливается, если набор путей разошёлся.
- Документация — инструкция по повседневной работе: как подтянуть изменения из общего источника и как перенести правку из одного репозитория в другой.
- Регламент выпуска изменений — порядок версионирования и тегирования, момент, когда репозиторий становится самостоятельным, и процедура обратной сборки.
- Журнал миграции — таблица соответствия старых путей новым и перечень изменений, после которых история в репозиториях расходится.
Кейс: как изменились сборка и права после разделения
Исходное состояние
Продуктовая команда держала всё в одном дереве Git: сервисы платформы, общие библиотеки, фронтенд и файлы конфигурации. Полная сборка занимала около получаса — система сборки пересобирала все модули при любой правке, а общий файл зависимостей сбрасывал кэш. Доступ к коду отдавали целиком или не отдавали вовсе, поэтому внешние партнёры присылали патчи, которые приходилось применять вручную.
Что перенесли
Монорепозиторий оставили сборочной точкой, а наружу вынесли два репозитория через git subtree split с сохранением истории коммитов и авторства. Для деревьев большего объёма тот же перенос быстрее выполняет splitsh, но здесь хватило штатного механизма и одной ночи на подготовку. Обратный импорт сделали регулярным: ветка из выделенного репозитория подтягивается в основное дерево и проходит ревью, поэтому правки партнёров не теряются при следующем разделении.
Метрики до и после
Сборка сократилась с получаса до единиц минут: пересобирается только затронутый сервис, а не всё дерево и не общие модули. Конфликты при выпуске упали с пяти-шести до одного за релиз — правки в общие библиотеки больше не сталкиваются в одном коммите перед выкаткой. Доля отказов из-за чужого кода снизилась с пятой части до единичных случаев: у каждой команды своё дерево и свои проверки.
Чем git subtree отличается от splitsh и что выбрать
Оба пути переносят историю, но распоряжаются ей по-разному. git subtree — встроенный механизм: он выделяет подкаталог в отдельную ветку, не теряя коммиты, и умеет втягивать изменения обратно. Splitsh — отдельная утилита: проходит по дереву, отбирает нужные пути и формирует новый набор коммитов, после чего отправляет его в целевой репозиторий как зеркало.
Разница видна в том, что остаётся в целевом репозитории. Subtree дописывает служебные коммиты слияния при каждом обмене: история целостная, но ветка обрастает техническими точками склейки. Splitsh пересобирает коммиты, сохраняя автора, дату и сообщение, — на выходе линейная лента. Плюс жёсткое ограничение: subtree требует цельного подкаталога, а splitsh фильтрует по списку путей и переживает раскладку, где файлы модуля размазаны по дереву.
Обратный импорт — принципиальное расхождение. Subtree проектировался под двусторонний обмен: правки из выделенного репозитория возвращаются слиянием подкаталога, конфликты решаются как обычные. Splitsh работает в одну сторону: зеркало только принимает, и обратный поток строится отдельно — с явным правилом, какой источник считается главным.
Практический выбор такой. Немного репозиториев и живой двусторонний обмен — subtree закрывает задачу без внешних зависимостей. Десятки подкаталогов, линейная лента и воспроизводимость из конфигурации — splitsh предсказуемее. Промежуточный сценарий: splitsh на нарезку, subtree или обмен патчами на обратный импорт.
Технические сомнения: история, доступы, обратный импорт
Самое частое возражение — что при выделении подпроекта история коммитов схлопнется в один служебный коммит и работа команды обнулится. Это не так: git subtree split пересобирает историю только по указанному каталогу, сохраняя авторов, даты и сообщения, а splitsh-lite делает то же через кэш деревьев — повторное разбиение после новых коммитов считается секунды, потому что ранее просчитанные части переиспользуются.
Второе возражение — простой команд и потеря прав доступа. Каталог уезжает отдельной веткой, сборка в новом репозитории поднимается параллельно, и основной поток переключается только после зелёного прогона. Права выдаём по тем же группам, а прежний репозиторий на время сверки открыт только на чтение.
История коммитов точно сохранится?
Сверяем до переноса: число коммитов по пути, хеши последних изменений, авторство и даты. Расхождение видно на этой сверке, а не через неделю после переезда, когда историю уже не восстановить.
Что происходит со сборочными конвейерами?
Описания сборки переезжают вместе с кодом, триггеры переключаются на новый репозиторий. Старый конвейер продолжает работать до момента переключения — откат занимает минуты и не требует собирать схему сборки заново.
Как устроен обратный импорт изменений?
Правки из отдельного репозитория возвращаются в общий через отдельную ветку слияния: конфликты разрешаются на границе каталогов, а не по всему дереву, поэтому слияние не превращается в ручной разбор сотен файлов.
Обсудим задачу: что подготовить к первому разговору
Чтобы оценка разделения монорепозитория опиралась на факты, а не на предположения, к первому разговору достаточно собрать вводные. Разделение через git subtree и splitsh решает разные задачи: первый сохраняет историю и позволяет синхронизировать изменения обратно, второй вырезает каталог с полной историей и авторами одним проходом. Что выбрать — зависит от структуры модулей, связей между ними и того, как устроены сборка и доставка. Поэтому начинаем не с инструмента, а с вашего репозитория.
Что подготовить к обсуждению задачи:
- структуру репозитория: корневые каталоги, где лежат общие библиотеки и служебные файлы, как модули ссылаются друг на друга;
- список команд и зон ответственности: кто с какими каталогами работает и кому нужен отдельный репозиторий;
- требования к истории: сохранять ли коммиты, авторов, ветки и теги, важна ли непрерывность нумерации релизов;
- связи со сборкой и доставкой: какие задания сборки, окружения и артефакты завязаны на текущий репозиторий;
- зависимости между модулями: общие пакеты, подмодули, локальные пути — всё, что станет внешним после разделения;
- требования к доступам: кто и что должен видеть после выноса, нужна ли изоляция кода между командами;
- порядок и ограничения: какой модуль выносим первым, что не должно останавливаться в процессе.
С этим набором вводных мы вернёмся с оценкой работ и планом разделения: последовательность выноса модулей, точки отката, порядок правки сборки и прав доступа. Опишите задачу и пришлите то, что уже есть, — остальное уточним в разговоре.







