...

пятница, 22 мая 2015 г.

Модель ветвления и управления модулями git для большого проекта

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


Раньше исходные коды ЛИНТЕР-а хранились в CVS. Несмотря на моральное устаревание этой системы контроля версий, она обладала определенными особенностями, которые мы активно использовали (отчасти это позволило продержаться этому «динозавру» так долго в строю): для работы над конкретной задачей можно было извлечь только необходимые модули с его зависимостями. Это удобно, поскольку модули в нашем проекте имеют преимущественно взаимно низкое и свободное сопряжение.

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

#RepositoryDir       Unix 
                                
RELAPI relapi 
LINDESKX lindeskx 
KERNEL5/SQL sql 
KERNEL5/TSP tsp

Нетрудно заметить, что правила определяли не только какие модули следует извлекать, но и куда их извлекать, т. е. дерево в центральном репозитории и дерево в рабочей копии отличались. Эти файлы-описатели менялись для разных целевых дистрибутивов, операционных систем и версий СУБД.

Но, как известно, git не предоставляет простого механизма клонирования части репозитория. Поэтому, когда стал вопрос о миграции с CVS на git, в первую очередь мы рассматривали два самых очевидных способа его организации: использовать единое хранилище (монорепозиторий) для всего дерева проекта с неизбежным внесением изменений в процесс сборки продукта или хранить проект в совокупности независимых модулей и использовать git-овские submodule/subtree для работы с ними.

Монорепозиторий


От идеи использования одного хранилища для всего дерева проекта отказались практически сразу. И на то были веские причины:
  • Производительность. Нет, у нас не было 1.3 миллиона файлов, как в тестах от Facebook (http://ift.tt/1HmgPb5), но и 114800 коммитов экспортированной истории для ~14000 файлов оказалось достаточным, чтобы зафиксировать заметное падение производительности при работе с индексом.
  • История. Монорепозиторий имеет общую историю правок: в одной цепочке логов могут быть перемешены правки ядра, примеров, утилит, документации и т. п.
  • Поддержка. Унификация дерева исходных кодов привела бы к изменению механизмов сборки для всех версий продукта, выпущенных с конца прошлого века. Разработка этих релизов, конечно, уже не ведется, но лишаться возможности «сдуть пыль» с архивной версии совсем не хотелось.

git submodules / git subtree


Если с точки зрения хранения группы модулей в главном репозитории трудностей не возникало, то с их клонированием в правильное рабочее дерево были сложности. Конечно, git поддерживает нативные submodules и subtree, однако недостатков в их использовании хватает (см. http://ift.tt/1Ajl22h), таковы уж архитектурные особенности этой системы контроля версий. Самым же неприятным моментом оказалась необходимость следить, чтобы в главный репозиторий модуля-контейнера не попадали ссылки на непубличные состояния дочерних модулей. Так, поработав с экспериментальным репозиторием, мы пришли к тому, что нам нужен альтернативный механизм управления модулями на стороне клиента.

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://ift.tt/mixx0f или переводом на хабре http://ift.tt/1f4reKx). Мы не стали исключением и, начав апробацию модели, вносили в нее корректировки, в результате чего пришли к собственной, которая унаследовала некоторые черты «родительской». И хоть заглавие оригинальной статьи не лукавит (модель действительно удачная), но это верно ровно до тех пор, пока не возникает необходимость применить ее к действительно большому проекту с длинной историей.

Причин, по которым «удачная» модель от 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 — это форк проекта git-flow (http://ift.tt/kXsWdp), который был изменен для нашей стратегии и расширен для поддержки linmodules. Кроме того, нами была разработана и серверная часть, которая работает как расширение для gitolite (http://gitolite.com).

Функционал управления модулями в linflow позволяет:

  • регистрировать/удалять модули;
  • редактировать источник и целевую директорию существующего модуля;
  • производить первоначальную настройку рабочей копии;
  • отслеживать состояние модуля-контейнера и своевременно переключать и обновлять вложенные модули;
  • производить упаковку модулей;
  • осуществлять проверку на согласованность всего дерева проекта.

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

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

Вопрос о возможности публикации полной технической документации на модель ветвления и средств linflow в настоящий момент обсуждается. Не последнюю роль в этом может сыграть отклики (или их отсутствие) на эту публикацию.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Комментариев нет:

Отправить комментарий