Модель розгалуження та управління модулями git для великого проекту

Без малого два роки тому ми почали використовувати в розробці нашого флагманського проекту СУБД ЛІНТЕР нову модель розгалуження та управління подмодулями git-а. Десятки тисяч комітів, зроблені за цей час групою розробників, які дозволяють з певною часткою упевненості вважати нововведення успішними. Ця стаття — короткий огляд принципів організації сховища вихідних кодів у великому проекті на базі альтернативної реалізації модулів git, сформованої стратегії розгалуження та інструментарію linflow.



Монорепозиторий, git submodules, git subtree або...
Раніше вихідні коди ЛІНТЕР-а зберігалися в CVS. Незважаючи на моральне старіння цієї системи контролю версій, вона володіла певними особливостями, які ми активно використовували (частково це дозволило протриматися цього «динозавра» так довго в строю): для роботи над конкретним завданням можна було отримати лише необхідні модулі з його залежностями. Це зручно, оскільки модулі в нашому проекті мають переважно взаємно низьке і вільне сполучення.

Технічно процес отримання необхідних вихідних кодів був організований простіше простого: сервер зберігав сукупність модулів в директоріях, розробник ж мав інструментарій вилучення і файл-описувач дерева, необхідної для того чи іншого технологічного процесу. Для більшої наочності наведемо невеликий фрагмент такого описувача:

#RepositoryDir Unix 

RELAPI relapi 
LINDESKX lindeskx 
KERNEL5/SQL sql 
KERNEL5/TSP tsp

Неважко помітити, що правила визначали не тільки які модулі слід витягувати, але і куди їх витягати, тобто дерево в центральному сховищі і дерево в робочій копії відрізнялися. Ці файли-описатели змінювалися для різних цільових дистрибутивів, операційних систем і версій СУБД.

Але, як відомо, git не надає простого механізму клонування частини репозиторію. Тому, коли постало питання про міграції з CVS на git, в першу чергу ми розглядали два найбільш очевидних способу його організації: використовувати єдине сховище (монорепозиторий) для всього дерева проекту з неминучим внесенням змін у процес складання продукту або зберігати проект в сукупності незалежних модулів і використовувати git-івські submodule/subtree для роботи з ними.

Монорепозиторий
Від ідеї використання одного сховища для всього дерева проекту відмовилися практично відразу. І на те були вагомі причини:
  • Продуктивність. Ні, у нас не було 1.3 мільйона файлів, як у тестах від Facebook (http://habrahabr.ru/post/137615/), але і 114800 комітів експортованої історії для ~14000 файлів виявилося достатнім, щоб зафіксувати помітне падіння продуктивності при роботі з індексом.
  • Історія. Монорепозиторий має спільну історію редагувань: в одному ланцюжку логів можуть бути перемешены правки ядра, прикладів, утиліт, документації і т. п.
  • Підтримка. Уніфікація дерева вихідних кодів призвела б до зміни механізмів збірки для всіх версій продукту, випущених з кінця минулого століття. Розробка цих релізів, звичайно, уже не ведеться, але позбавлятися можливості «здути пил» з архівної версії зовсім не хотілося.


git submodules / git subtree
Якщо з точки зору зберігання групи модулів в головному репозиторії труднощів не виникало, то з їх клонуванням в правильне робоче дерево були складності. Звичайно, git підтримує нативні submodules і subtree, однак недоліків у їх використанні вистачає (див. http://habrahabr.ru/post/75964/), такі вже архітектурні особливості цієї системи контролю версій. Найбільш неприємним моментом виявилася необхідність стежити, щоб у головний репозиторій модуля-контейнера не потрапляли посилання на непублічні стану дочірніх модулів. Так, попрацювавши з експериментальним сховище, ми прийшли до того, що нам потрібен альтернативний механізм управління модулями на стороні клієнта.

linmodules
Для усунення недоліків нативних git submodules і git subtree ми розробили власний механізм управлінням модулями, який функціонує над рівнем git-а. Реалізація цього механізму стала частиною інструментарію, названого нами linflow.

Схема достатня проста: кожен з модулів проекту зберігається в окремому сховищі з експортованої історією, а один з модулів (в нашому випадку він має ім'я linter.git) є модулем-контейнером, який не містить жодних вихідних кодів і його основне завдання — ставити дерево глобальних гілок для всіх інших. На кожній з цих глобальних гілок в модулі-контейнері може знаходитися файл-описувач (з ім'ям .linmodules), необхідний для отримання коректного дерева проекту.

Як би дивно це не звучало для читача, якому пощастило не працюватиме щодня з нативними подмодулями git, але така схема виявилася простіше і зручніше штатної реалізації.


Ілюстрація 1: Порядок отримання варіанту дерева исходников. 1 — клонування модуля-контейнера з файлом-описателем, 2 — ініціалізація, 3 — клонування зареєстрованих модулів в цільові директорії.

Синтаксис опису шаблона (файл .linmodules) повністю повторює «рідний», який використовується в git-е файлу .gitmodules. Зроблено це було навмисно з метою сумісності.

Наведемо фрагмент шаблону розміщення з ілюстрації 1:

[submodule "tick"] 
path = lib/tick 
url = git@linter-git.common.relex.ru:TICK 
[submodule "odbc"] 
path = odbc 
url = git@linter-git.common.relex.ru:ODBC 
[submodule "inl"] 
path = app/inl 
url = git@linter-git.common.relex.ru:INL

Таким чином, нам вдалося зберегти можливість вилучення модулів в довільну структуру робочої копії. Первісне формування шаблонів і їх подальша зміна здійснюється засобами linflow.

Модель розгалуження
Не буде великою помилкою припустити, що багато розробників, які шукали оптимальну організацію своїх репозиторіїв на основі git, знайомі з роботою Vincent Driessen «A successful Git branching model» (ті ж, хто не встиг цього зробити завжди можуть ознайомитися з оригіналом http://nvie.com/posts/a successful-git-branching-model/ або переведенням на хабре http://habrahabr.ru/post/106912/). Ми не стали винятком і, почавши апробацію моделі, вносили в неї коригування, в результаті чого прийшли до власної, яка успадкувала деякі риси «батьківського». І хоч назва оригінальної статті не лукавить (модель дійсно вдала), але це вірно рівно до тих пір, поки не виникає необхідність застосувати її до справді великого проекту з довгою історією.

Причин, за якими «вдала модель від Vincent Driessen зажадала змін, було кілька. Наведемо лише найбільш важливі для нас в порядку їх виникнення і рішення:
  • Оригінальна модель не уточнює поведінки при декомпозиції вихідних проектів на подмодули і модулі з залежностями.
  • Гілки релізів не можуть бути закриті поки здійснюється супровід продукту, оскільки на момент написання цих рядків виправлення і частина нового функціоналу вносяться на всі версії випущені з початку 2009 року. З-за цього білди різних версій продукту не можуть бути представлені єдиної послідовністю комітів на який-небудь гілки.
  • Гілки виправлень і функціоналу можуть переноситися на старі версії, які не містять всіх змін гілки розробки, тому merge просто неможливий.
  • Переважна більшість виправлень містять один комміт (на момент написання цих рядків з 2598 гілок з виправленнями тільки 262 мали два і більше коміта), тому використання злиття зі стратегії no-ff, породжує кожен раз додатковий merge-комміт, не дуже зручно.
Підсумком роботи над модифікацією «вдалою» моделі стало створення власної стратегії, яка частково успадковує деякі терміни, угоди, іменування і робочі процеси оригінальною. Заради простоти виділимо ключові зміни, які будуть більш детально розглянуті нижче:
  • Обумовлені правила ведення гілок в подмодулях одного проекту;
  • Змінені правила роботи з develop гілкою;
  • Змінені правила виходу версій і, отже;
  • Змінені правила ведення релізних гілок;
  • Змінені правила переносу правок з гілки на гілку.



Ілюстрація 2: Варіант розгалуження і перенесення коду в модулі. Релізна гілка RELEASE#2 є новою і дозволяє переносити правки з допомогою merge, RELEASE#1 підтримує попередню версію і отримує зміни вибірково.

Головні гілки
Центральний репозиторій містить групу гілок, існуючу весь час і у всіх модулях:
  • release branches — група гілок проекту, що доповнюється в міру виходу версій;
  • develop(master) — головна гілка розробки.
Гілки origin/release вважаються головними продукційними, тобто вихідний код на них повинен дозволяти випустити версію або білд в будь-який момент часу. Гілка origin/master вважається головною виробничою, яка містить всі зміни проекту та служить джерелом для створення origin/release. Коли вихідний код origin/master готовий до релізу, зміни повинні бути певним чином перенесені на відповідні origin/release або породити нову версію, а отже — гілка в origin/release.

Допоміжні гілки
Крім головних гілок, структура репозиторіїв (як центрального, так і робочих копій) передбачає наявність допоміжних гілок наступних типів:
  • feature branches — гілки нових функціональностей;
  • fix branches — гілки виправлень.
У кожного з цих типів гілок є специфічне призначення і набір правил ведення, які будуть описані нижче.


Ілюстрація 3: Розподіл гілок по модулям: головні гілки присутні у всіх, допоміжні — тільки в необхідних.

Загальні правила
Загальні правила ведення гілок в центральному і локальних репозиторіях:
  • develop(master) містить стабільний код;
  • develop(master) існує у всіх модулях;
  • розробка на гілки develop(master) заборонена;
  • develop(master) зберігає код, необхідний для випуску нової версії або релізу;
  • за необхідності нового релізу від develop(master) гілкуються release branches;
  • release branches зберігають код для випуску нового білду;
  • випуски білдів відзначаються тегами на головних коммитах відповідних release branches;
  • створення гілки типу release branches в модулі-контейнері породжує створення однойменної гілки в кожному з подмодулей (див. рис. 2);
  • fix branches можуть галузиться від develop(master) або release branches і можуть вливатися як у develop(master) та release branches;
  • feature branches розгалужуються тільки від develop(master) і обов'язково вливаються в нього ж;
  • коміти, складові feature branches, у разі необхідності можуть бути перенесені на один або декілька release branches (але цей факт не скасовує попереднє правило про обов'язкове злиття з develop);
  • гілки feature branches та hotfix branches регулярно публікуються в центральному сховищі.


Гілки релізів/версії (release branches)
Гілки релізів (release branches) іменуються як release/Blinter_AB_C, A — мажорна версія, B — мінорна, а — номер релізу. Гілки релізів породжуються від develop і існують весь час підтримки версії ЛІНТЕР-а. Гілка є реципієнтом коду: яка-небудь розробка в ній не ведеться. Кожен факт випуску нового білду зазначається відповідним тегом виду Blinter_AB_C_D, D — номер складання. Гілки цього типу можуть бути посиланнями (з точки зору організації на origin) на іншу релізну гілку. У цьому випадку публікація в одну з таких гілок призведе до оновлення всіх пов'язаних. Релізна гілка є глобальною, тобто існує у всіх модулях, якщо створена в модулі-контейнері. Теги з мітками білду виставляються одноразово у всіх модулях.

Гілки виправлень (fix branches)
Гілки виправлень (fix branches) іменуються як hotfix/*, можуть породжуватися від develop (переважно) або release, можуть вливатися в develop(master) та release. Якщо виправлення містять один комміт, то злиття здійснюється без створення merge коміта. Підсумковий комміт в тілі коментаря містить відсилання до номеру відповідного тікета в багтрекері. Після перенесення правок гілка виправлення закривається.

Гілки функціональності (feature branches)
Гілки функціональності іменуються як feature/* і породжуються тільки від develop(master).
Гілки функціональностей (feature branches) використовуються для розробки нових функцій, які повинні з'явитися в поточному чи майбутньому релізах. Гілка існує так довго, скільки триває розробка функціональності. По мірі досягнення проміжних результатів гілка публікується в центральному сховищі. Коли робота гілки завершена, остання обов'язково вливається в головну гілку розробки (що означає, що функціональність буде додана в наступний реліз) і опціонально — в релизные гілки. Після перенесення коду гілка функціональності закривається.

linflow
Варто сказати кілька слів про інструментарій linflow, який згадувався кілька разів вище по тексту. Linflow призначений для операцій з модулями дерева вихідних кодів, а також для підтримки нашої моделі розгалуження. Клієнтська частина linflow — це форк проекту git-flow (https://github.com/nvie/gitflow), який був змінений для нашої стратегії і розширений для підтримки linmodules. Крім того, нами була розроблена і серверна частина, яка працює як розширення для gitolite (http://gitolite.com).

Функціонал керування модулями linflow дозволяє:
  • реєструвати/видаляти модулі;
  • редагувати джерело і цільову директорію існуючого модуля;
  • здійснювати первинне налаштування робочої копії;
  • відстежувати стан модуля-контейнера і своєчасно перемикати і оновлювати вкладені модулі;
  • виробляти упаковку модулів;
  • здійснювати перевірку на узгодженість всього дерева проекту.
Функціонал управління гілками в linflow дозволяє:
  • створювати/видаляти/публікувати гілки всіх дозволених типів;
  • здійснювати контроль над виконанням угоди про іменування гілок;
  • узгоджено перемикатися на гілки і теги у всіх модулях слідом за модулем-контейнером;
  • здійснювати масові операції над модулями;
  • переносити код з гілки на гілку з використанням різних стратегій;
  • переносити код на гілки із зміненим історією;
  • попереджати помилкове видалення гілок.
Функціонал серверної частини дозволяє:
  • здійснювати контроль над дотриманням правил іменування;
  • розмежовувати права користувачів за ролями;
  • керувати гілками-посиланнями;
  • здійснювати розсилку повідомлень про зміни динамічно формованому списку потенційно зацікавлених учасників;
  • проводити повне резервне копіювання.
Питання про можливість публікації повної технічної документації на модель розгалуження і засобів linflow зараз обговорюється. Не останню роль в цьому може зіграти відгуки (або їх відсутність) на цю публікацію.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.