Split-репозитории из монорепозитория: git subtree и splitsh

Монорепозиторий ускоряет старт, но со временем сборки растут, а права доступа становятся слишком широкими. Мы настраиваем split монорепозитория под ключ: выделяем сервисы в отдельные репозитории, сохраняя историю и связи. Сборка сервиса перестаёт зависеть от всей кодовой базы: проверки идут в разы быстрее, а команды выпускают релизы независимо друг от друга.
Часто задаваемые вопросы

Split монорепозитория: почему ломаются сборка и права

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

Второе узкое место — выгрузка репозитория и разграничение доступа. Полная история за годы работы весит десятки гигабайт, и новая машина разработчика или агент сборки тратит на неё больше времени, чем на саму работу. Git ограничивает права на уровне репозитория, а не каталога, поэтому выбор невелик: либо все видят весь код, включая платёжные и административные модули, либо сборка идёт через сервисные учётные записи с вручную поддерживаемыми исключениями. Обе крайности стоят дорого — и в скорости, и в рисках.

Третья причина — история коммитов. При разделении она должна переехать вместе с кодом: разбор авторства строк и ссылки на задачи нужны, чтобы разбирать инциденты и находить причину регрессий, а не восстанавливать контекст по памяти. Дальше разбираем, как разделение монорепозитория закрывает все три точки роста — от подготовки дерева и прав до способа сохранить историю.

Что даёт разделение: скорость сборки и независимые релизы

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

Независимые релизы — следствие той же границы. Тег в репозитории сервиса запускает только его конвейер, поэтому уходит класс отказов «одна команда сломала общий ствол и заблокировала выпуск всех». Каждая команда выпускает свою часть по своему графику, а совместимость версий фиксируется контрактами и версиями зависимостей, а не общей веткой, которую все обязаны держать зелёной.

Сокращение времени восстановления получается из того же свойства: откат затрагивает один репозиторий и один тег, а не пересборку всей кодовой базы. Область поражения при сбое ограничена сервисом — неудачный выпуск откатывается за минуты, без ожидания, пока остальные команды приведут общий ствол в рабочее состояние.

Сужается и периметр доступа: права выдаются на уровне репозитория, а не на всю кодовую базу, поэтому подрядчик или новая команда видит только свои каталоги, свои секреты и свои правила защиты веток. Зоны ответственности команд становятся явными — владелец репозитория отвечает за его сборку, выпуск и дежурство, а не растворяется в общем потоке чужих изменений.

Способы split монорепозитория: сравнение и ограничения

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

История при любом подходе переносится с авторством и датами — она теряется только при явном «схлопывании» коммитов. Поэтому ограничения выбирают не по объёму истории, а по тому, как часто изменения пойдут в обе стороны.

Подход к переносу подпроекта Когда подходит Ограничения подхода
Полное разделение на отдельные репозитории команды и релизные циклы разъезжаются, общий код перестаёт быть общим обратная сборка требует ручной настройки, сквозные ссылки и общие зависимости рвутся
Частичный вынос подпроекта с зеркалированием (git subtree) подпроект нужен и внутри монорепозитория, и снаружи, ожидается обратный вклад двойная история коммитов, синхронизация вручную, при правках с обеих сторон растут конфликты
Разовая выгрузка истории (splitsh, git filter-repo) нужен снапшот для внешней команды или публикации односторонний поток: изменения снаружи возвращаются патчами

Перед выбором историю и ссылки проверяют на тестовом клоне: только так видно, что инструмент делает с коммитами слияния и подписями.

Порядок работ: от аудита истории до передачи схем

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

  1. Аудит истории и активных ветвей. Проходим по каталогам и слияниям, отмечаем долгоживущие ветви и релизные теги — так определяются границы будущих репозиториев.
  2. Проектирование схемы репозиториев. Фиксируем, что выносится отдельно, какой код остаётся общим, как связаны версии и кто отвечает за каждый кусок.
  3. Перенос на копии репозитория. Прогоняем git subtree или splitsh на клоне: история переезжает с авторами и датами, а исходный репозиторий продолжает собираться.
  4. Проверка целостности. Сверяем число коммитов, ветви и теги, сравниваем содержимое по контрольным суммам и собираем проект из нового репозитория.
  5. Настройка сборки. Переводим сборочные задания на новые адреса, настраиваем триггеры и права доступа, чтобы релиз проходил без ручных шагов.
  6. Передача регламентов и обучение. Отдаём схему репозиториев, описание сборочных заданий и правила работы с ветвями, разбираем их с командой.

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

Комплект передачи: конфигурации, схемы, регламенты

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

  • Конфигурации разделения — набор фильтров для 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 решает разные задачи: первый сохраняет историю и позволяет синхронизировать изменения обратно, второй вырезает каталог с полной историей и авторами одним проходом. Что выбрать — зависит от структуры модулей, связей между ними и того, как устроены сборка и доставка. Поэтому начинаем не с инструмента, а с вашего репозитория.

Что подготовить к обсуждению задачи:

  • структуру репозитория: корневые каталоги, где лежат общие библиотеки и служебные файлы, как модули ссылаются друг на друга;
  • список команд и зон ответственности: кто с какими каталогами работает и кому нужен отдельный репозиторий;
  • требования к истории: сохранять ли коммиты, авторов, ветки и теги, важна ли непрерывность нумерации релизов;
  • связи со сборкой и доставкой: какие задания сборки, окружения и артефакты завязаны на текущий репозиторий;
  • зависимости между модулями: общие пакеты, подмодули, локальные пути — всё, что станет внешним после разделения;
  • требования к доступам: кто и что должен видеть после выноса, нужна ли изоляция кода между командами;
  • порядок и ограничения: какой модуль выносим первым, что не должно останавливаться в процессе.

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

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1368
  • Разработка логотипа компании B2B Advance
    Разработка логотипа компании B2B Advance
    815
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1140