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 розв’язує різні завдання: перший зберігає історію й дає змогу синхронізувати зміни назад, другий вирізає каталог із повною історією та авторами одним проходом.
Що вибрати — залежить від структури модулів, зв’язків між ними та того, як влаштовані збірка й доставка. Тому починаємо не з інструмента, а з вашого репозиторію.
Що підготувати до обговорення задачі:
- структуру репозиторію: кореневі каталоги, де лежать спільні бібліотеки та службові файли, як модулі посилаються один на одного;
- список команд і зон відповідальності: хто з якими каталогами працює та кому потрібен окремий репозиторій;
- вимоги до історії: чи зберігати коміти, авторів, гілки й теги, чи важлива безперервність нумерації релізів;
- зв’язки зі збіркою та доставкою: які завдання збірки, середовища й артефакти зав’язані на поточний репозиторій;
- залежності між модулями: спільні пакети, підмодулі, локальні шляхи — усе, що стане зовнішнім після розділення;
- вимоги до доступів: хто і що має бачити після винесення, чи потрібна ізоляція коду між командами;
- порядок і обмеження: який модуль виносимо першим, що не повинно зупинятися в процесі.
Із цим набором вхідних даних ми повернемося з оцінкою робіт і планом розділення: послідовність винесення модулів, точки відкату, порядок правки збірки та прав доступу. Опишіть задачу й надішліть те, що вже є, — решту уточнимо в розмові.







