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