Пошук уроків, статей та іншого контенту
Організовуємо монорепозиторій, ізолюємо зміни між компонентами та налаштовуємо зручну роботу команд.
Монорепозиторій — це один Git-репозиторій, у якому зберігаються кілька компонентів проєкту:
застосунки;
бібліотеки;
спільні конфігурації;
документація;
інструменти розробки.
Наприклад:
company-platform/
├── apps/
│ ├── web/
│ ├── admin/
│ └── api/
├── packages/
│ ├── ui/
│ ├── config/
│ └── types/
├── scripts/
├── .gitignore
├── package.json
└── README.mdУсі компоненти мають спільну історію Git, але кожен компонент залишається логічно відокремленим.
Git не розуміє, де закінчується один застосунок і починається інший. Для Git це просто набір файлів у єдиному робочому дереві. Тому структуру, правила комітів і процес роботи потрібно організувати самостійно.
На верхньому рівні варто розділити компоненти за призначенням:
apps/ # кінцеві застосунки
packages/ # бібліотеки та спільний код
scripts/ # скрипти для розробки й автоматизації
docs/ # документаціяНаприклад:
apps/
├── web/
├── admin/
└── api/
packages/
├── ui/
├── types/
└── eslint-config/Таку структуру легше читати та використовувати в командах Git:
git diff -- apps/web
git log -- packages/ui
git add -- apps/api packages/typesСимвол -- відокремлює параметри Git від шляхів до файлів. Це особливо корисно, коли назва файлу може збігатися з назвою гілки або іншого параметра.
Конфігурації, спільні для всього репозиторію, зазвичай зберігають у корені:
.gitignore
.gitattributes
README.md
package.jsonНалаштування конкретного компонента можна зберігати всередині його каталогу:
apps/web/
├── package.json
├── src/
└── README.mdНазви каталогів і структура мають бути передбачуваними. Якщо один компонент називається apps/web, не варто без потреби використовувати інші варіанти на кшталт frontend або client.
У монорепозиторії важливо робити атомарні коміти. Один коміт має описувати одну логічну зміну.
Хороші приклади:
feat(web): add profile page
fix(api): validate user identifier
refactor(ui): extract Button component
docs: update local setup instructionsНебажаний коміт:
update everythingУ ньому можуть одночасно бути:
зміни API;
форматування всього репозиторію;
оновлення компонента інтерфейсу;
виправлення документації.
Такі коміти складно перевіряти, скасовувати та переносити між гілками.
Команда git add . додає всі зміни з поточного каталогу та вкладених каталогів. У монорепозиторії це може випадково включити зміни кількох компонентів.
Безпечніше вказувати конкретні шляхи:
git add -- apps/web packages/uiПісля цього потрібно перевірити індекс:
git diff --cached --name-only
git diff --cachedЯкщо до індексу випадково потрапив зайвий файл:
git restore --staged -- apps/api/debug.logЦя команда прибере файл з індексу, але залишить його зміни у робочій директорії.
# Перейти до основної гілки та отримати актуальний стан
git switch main
git pull --ff-only
# Створити гілку для зміни компонента інтерфейсу
git switch -c feat/ui-profile-card
# Переглянути змінені файли
git status --short
# Додати лише потрібні компоненти
git add -- packages/ui apps/web
# Перевірити файли, які потраплять у коміт
git diff --cached --name-status
# Перевірити самі зміни
git diff --cached
# Створити атомарний коміт
git commit -m "feat(ui): add profile card"
# Переглянути локальну історію гілки
git log --oneline --decorate -5Якщо зміна стосується бібліотеки packages/ui і її використання в apps/web, додавання обох шляхів до одного коміту може бути правильним. Головне, щоб вони належали до однієї логічної задачі.
Ізоляція в монорепозиторії досягається не окремими Git-репозиторіями, а поєднанням кількох правил:
кожна задача має власну гілку;
коміти містять одну логічну зміну;
до індексу додаються лише потрібні шляхи;
зміни перевіряються перед комітом;
зміни компонентів переглядаються окремо.
Показати всі незбережені зміни лише в apps/api:
git diff -- apps/apiПоказати зміни, вже додані до індексу:
git diff --cached -- apps/apiПоказати імена змінених файлів:
git diff --name-only -- apps/apiПорівняти поточну гілку з main лише для одного каталогу:
git diff main...HEAD -- packages/uiІсторію змін компонента можна переглянути незалежно від решти репозиторію:
git log --oneline -- apps/webДля перегляду конкретного файлу:
git log --follow --oneline -- packages/ui/src/Button.tsxПараметр --follow дає змогу продовжити історію файлу після його перейменування.
Для переміщення файлу слід використовувати git mv:
git mv apps/web/src/OldHeader.tsx packages/ui/src/Header.tsx
git add -- packages/ui/src/Header.tsx
git commit -m "refactor(ui): move header to shared package"Git зазвичай сам визначає переміщення за схожістю вмісту. Переміщення не є окремим типом об’єкта в історії Git: воно визначається під час порівняння змін.
Гілку варто створювати для задачі, а не назавжди для кожного компонента:
feat/web-search
fix/api-authentication
refactor/ui-modal
chore(repo-update-dependencies)Необов’язково створювати окрему постійну гілку web або api. Постійні гілки компонентів ускладнюють синхронізацію та збільшують кількість конфліктів.
Якщо одна задача зачіпає кілька компонентів, це нормально:
feat/api-add-user-statusУ такій гілці можуть бути зміни в:
apps/api/
apps/web/
packages/types/Важливо, щоб усі зміни були частиною однієї задачі та проходили узгоджений процес перевірки.
У монорепозиторії є файли, які часто змінюють різні команди:
кореневий package.json;
lock-файл;
спільні конфігурації;
типи, які використовують кілька компонентів;
коренева документація.
Такі файли є потенційними точками конфліктів. Щоб зменшити кількість проблем:
не змінюйте форматування всього файлу разом із функціональною зміною;
не сортуйте великі конфігурації без потреби;
розділяйте великі зміни на невеликі коміти;
синхронізуйте гілку з main перед створенням pull request;
повідомляйте команду про зміни спільних контрактів.
Якщо зміни в різних компонентах не залежать одна від одної, їх краще оформити окремими комітами:
feat(types): add UserStatus type
feat(api): return user status
feat(web): display user statusЯкщо безпечний проміжний стан неможливий, зміни можна об’єднати в один атомарний коміт. Критерій — не кількість каталогів, а цілісність зміни.
У великому монорепозиторії не завжди потрібно мати всі каталоги у робочій директорії. Git підтримує часткове розгортання за допомогою sparse-checkout.
Клонування без початкового розгортання файлів:
git clone --no-checkout ssh://git.example.com/platform.git platform
cd platform
# Увімкнути режим вибіркового розгортання
git sparse-checkout init --cone
# Залишити в робочій директорії лише потрібні каталоги
git sparse-checkout set apps/web packages/ui
# Перейти на основну гілку
git switch mainПісля цього в робочій директорії будуть доступні вибрані каталоги та необхідні файли верхнього рівня.
Додати ще один компонент:
git sparse-checkout add packages/typesПереглянути поточні правила:
git sparse-checkout listПовернути всі файли репозиторію:
git sparse-checkout disableSparse-checkout змінює лише вміст робочої директорії. Історія Git усе одно належить усьому репозиторію.
Цей режим корисний, коли:
монорепозиторій містить багато незалежних застосунків;
локально потрібен лише один компонент;
зайві файли сповільнюють навігацію або інструменти розробки.
Не слід використовувати sparse-checkout як механізм доступу. Він не приховує файли від користувача, який має доступ до репозиторію.
Коли потрібно паралельно працювати над кількома задачами, можна використовувати git worktree. Він створює додаткову робочу директорію для іншої гілки, не вимагаючи нового клонування репозиторію.
# Створити робочу директорію для окремої задачі
git worktree add ../platform-api -b feat/api-health-check main
# Перейти до неї
cd ../platform-api
# Перевірити створену гілку
git statusПовернувшись до основної робочої директорії, список worktree можна переглянути так:
git worktree listПісля завершення роботи додаткову директорію можна видалити:
git worktree remove ../platform-apiОдна й та сама гілка не повинна одночасно бути активною у двох worktree. Для кожної робочої директорії використовуйте окрему гілку.
Git сам по собі не визначає, яка команда відповідає за каталог. У монорепозиторії це потрібно зафіксувати в правилах команди або в налаштуваннях платформи для code review.
Приклад логічного розподілу:
apps/web/ — команда Web
apps/api/ — команда Backend
packages/ui/ — команда Design System
packages/types/ — спільна відповідальністьПід час перегляду змін важливо звертати увагу не лише на власний компонент, а й на його публічні контракти:
експортовані типи;
API функцій;
компоненти, які використовують інші застосунки;
спільні конфігурації.
Зміна в packages/ui може вплинути на кілька застосунків, навіть якщо в них не було змінено жодного файлу.
Припустімо, потрібно додати властивість до спільного типу та використати її в API.
git switch main
git pull --ff-only
git switch -c feat/user-status
# Перевірити стан перед початком
git status --short
# Перший логічний коміт: зміна спільного типу
git add -- packages/types
git diff --cached --name-only
git commit -m "feat(types): add user status"
# Другий логічний коміт: використання типу в API
git add -- apps/api
git diff --cached --name-only
git commit -m "feat(api): return user status"
# Перевірити всі зміни гілки відносно main
git diff main...HEAD --stat
git diff main...HEAD -- packages/types apps/apiРозділення на коміти полегшує перевірку. Якщо зміна типу й зміна API завжди мають надходити разом, їх також можна об’єднати, але в коміті все одно має бути одна зрозуміла задача.
git add .git add .У монорепозиторії ця команда може додати тимчасові файли або зміни іншої команди.
Краще використовувати конкретні шляхи:
git add -- apps/webАбо додавати файли вибірково:
git add -- apps/web/src/Profile.tsx packages/ui/src/Card.tsxКоміт, який одночасно змінює API, вебзастосунок, документацію та форматування, складно перевірити й скасувати.
Краще створити кілька атомарних комітів або розділити роботу на окремі гілки.
Git не забороняє змінювати будь-який файл із будь-якої гілки. Ізоляція компонентів — це результат правил команди, структури гілок і перевірки staged-змін.
Гілки на кшталт web, api та ui, які живуть постійно, часто призводять до розходження історій.
Зазвичай краще використовувати короткоживучі гілки для конкретних задач:
feat/web-checkout
fix/api-timeout
refactor/ui-inputМасове форматування створює шум у diff і збільшує кількість конфліктів. Форматування слід виконувати окремим комітом або окремою задачею.
Зміна кореневого lock-файлу або спільного типу може вплинути на весь монорепозиторій. Перед таким комітом потрібно перевірити:
git diff --cached --name-status
git diff --cached -- package.json packages/typesЯкщо файл не відображається локально, це не обов’язково означає, що його видалено з Git. Спочатку перевірте налаштування:
git sparse-checkout listМонорепозиторій зберігає кілька компонентів в одному Git-репозиторії.
Зрозуміла структура apps/, packages/ і scripts/ спрощує навігацію та роботу з pathspec.
Для ізоляції змін використовуйте гілки задач, атомарні коміти та явне додавання шляхів через git add --.
Перевіряйте staged-зміни командами git diff --cached і git diff --cached --name-only.
Історію та diff можна переглядати окремо для конкретного компонента.
Для великих репозиторіїв корисні sparse-checkout і git worktree.
Git не ізолює компоненти автоматично: правила роботи та відповідальність команди мають бути визначені явно.