среда, 20 октября 2010 г.

Успешная модель ветвления на Git

Я конечно понимаю, что я редкостный slowpoke, но ничего с этим не поделаешь. Наткнулся на статью Vincent Driessen, в которой он рассказывает о своей модели процесса разработки с использованием Git.
Когда то, эту статью я кажется встречал, но по какой-то причине то ли не осилил, то ли пропустил мимо. Тут недавно натолкнулся на нее еще раз. И решил таки перевести, для себя, ну и может еще кому полезно будет.
В отношении перевода: перевод довольно вольный, ибо переводчик из меня плохой. Поэтому не пинать. Заметите ошибку - пишите в комментарии.
Статья первоисточник: http://nvie.com/posts/a-successful-git-branching-model/
Заинтересовавшихся, приглашаю под кат )
Предлагаемая модель управления исходным кодом, посредством Git успешно использовалась автором на всех его проектах (рабочих, и собственных). В статье не говорится о деталях проектов, статья содержит лишь описание модели, которую автор с успехом применяет в повседневной жизни разработчика.

Данная модель сосредотачивается вокруг Git, как инструмента для контроля версий.

Почему Git?

Данная статья не о системе контроля версий Git, не о ее преимуществах и недостатках. Это статья о одной из множества моделей ведения проекта, с использованием особенностей Git.
Модель предложенная автором статьи базируется на особенности работы Git с ветками. Для сравнения, если взять классические централизованные системы контроля версий, такие как CVS/SVN, то у этих систем весьма консервативное и осторожное использование веток, а также их объединения. Данные операции считаются сложными, и в любой книге по этим системам, такого рода операции описываются в разделе для опытных пользователей. Зачастую операции создания и объединения веток в хранилищах являются привилегированными операциями, и разработчики не имеют к ним доступа.
В противоположность, такие системы как Git, базируются на идее активного использования веток, их создания, и объединения, что позволило создать еще одну эффективную модель разработки, которая отлично подходит как для одиночного разработчика, так и для командной разработки.
Что касается самой модели, то это ни панацея, ни великое открытие, это всего лишь набор процедур, придерживаясь которых можно сделать процесс разработки ПО управляемым. Конечно, чтобы данная модель принесла свои плоды, требуется четкое соблюдение процедур не отдельными разработчиками, но каждым участником команды в целом. Как показала практика, и опыт применения этой модели автором на реальных проектах, модель является успешной.

Децентрализованные, но централизованные

Несмотря на то, что Git является децентрализованной системой, обычно при разработке и сопровождении проекта создается основное центральное хранилище. Конечно, с технической точки зрения, это хранилище не является центральным, такого понятия в Git просто не существует. Центральное хранилище с которым работает вся команда, обычно называется origin.

Каждый разработчик при помощи push и pull передает изменения между своим хранилищем и origin. Но кроме двунаправленного обмена между указанными хранилищами, возможен также двусторонний обмен и между хранилищами разработчиков. Чем это может быть полезно? Ну представим ситуацию, когда в проекте над внедрением какого-то нового функционала работают два или более разработчиков. Во время разработки, эти изменения не должны попадать в origin, но в тоже время, должен вестись обмен изменениями, произведенными каждым из разработчиков. В этом случае разработчики обмениваются изменениями между своими локальными хранилищами, в обход центрального хранилища origin. На рисунке выше, показан такой пример. Как видите, над проектом работает команда из четырех разработчиков. Каждый обменивается изменениями с origin. Но помимо этого, можно выделить три группы разработчиков, которые обмениваются изменениями только между собой: Алиса и Боб, Алиса и Дэвид, Клэр и Дэвид.
С технической стороны, это означает, что к примеру Алиса работает с двумя удаленными хранилищами: origin и bob(которое, принадлежит Бобу), и наоборот.

Главные ветки

Представляемая модель в значительной степени вдохновлена уже существующими моделями.
Центральное хранилище предоставляет две ветки с бесконечным жизненным циклом:
  • master
  • develop
Ветка master в хранилище origin знакома каждому пользователю Git. Параллельно с веткой master, существует другая ветка, называемая develop.
Ветвь origin/master мы рассматриваем как главную ветку, HEAD в которой, всегда указывает на production ready состояние.
Ветвь origin/develop рассматривается как главная ветка, в которой HEAD отражает состояние исходного кода, на момент последних изменений, подготовленных к следующему релизу. Некоторые называют эту ветвь "integration branch". Например, HEAD этой ветки используется для ночных сборок.
Когда исходный код в ветке develop достигает стабильности, и содержит уровень функциональности близкий к желаемому, все изменения из develop должны быть внесены обратно в master, и отмечены тегом соответствующего релиза.
Исходя из всего вышесказанного, можно заявить, что каждое внесение изменений в master, является очередным релизом по определению. Если строго подходить к этим рекомендациям, то теоретически, можно использовать хук(hook) к скрипту Git, для автоматической сборки и развертывания ПО на production-серверах, после каждого коммита в master.

Дополнительные ветки

В предлагаемой модели, помимо двух главных веток (master и develop), также используются вспомогательные ветки, которые позволяют облегчить ведение параллельной разработки членами команды, слежение за спектром разрабатываемого функционала проекта, подготовку к релизам, а также помогают в решении проблем, которые возникают в production коде. Но в отличие от главных веток, эти ветки имеют ограниченное время жизни, так как рано или поздно будут удалены.
В качестве основных вспомогательных типов веток могут быть выделены:
  • Feature branches
  • Release branches
  • Hotfix branches
(Оригинальные названия типов веток сохранены намерено, во избежание путаницы в переводе. Прим. перев.)
Каждый из этих типов веток имеет конкретные цели и политику работы с ними. Для них строго определены те ветки, от которых можно произвести ветвление, и с какими ветками должны быть объединены. И опять же во избежание введения в заблуждение, стоит оговориться, что эти ветки никоим образом не являются "специальными" с технической точки зрения. Это обычные ветки Git. Их типизация зависит от правил, которых придерживаются при работе с ними.

Feature branches

Правила ветвления, объединения и именования:
  • Ветвление этих веток может быть произведено только от develop
  • Эти ветки должны объединяться обратно с develop
  • Имя ветки может быть любым, кроме master, develop, release-*, hotfix-*

В Feature branches (далее FB) ведется разработка функционала для будущих релизов проекта. На момент начала работы над новым функционалом, релиз в котором он появится может быть и неизвестен. Суть таких веток в том, что разработка новых функциональных возможностей может вестись параллельно разработке основной части проекта, а по завершении реализации нового функционала он может быть внедрен в одном из следующих версий, либо отклонен (в случае, если функционал не удовлетворяет целям проекта, либо по каким-либо другим причинам). Такие ветки должны создаваться в локальных хранилищах разработчиков, но ни в коем случае не в origin.

Создание feature branch


Для создания FB:

$ git checkout -b myfeature develop
Switched to a new branch "myfeature"
При создании FB ветвление осуществляется от develop.

Конец жизненного цикла feature branch


Законченный функционал может быть объединен обратно с веткой develop для добавления его в следующий релиз:
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
Использование флага --no-ff создает новый коммит, который содержит все изменения объединяемой ветки. Это позволяет избежать потери информации о существовании ветки, в которой велась разработка добавленного функционала. Для сравнения, взгляните на изображение ниже. Слева с использованием флага --no-ff, справа без его использования:

Как видно, в случае не использования флага --no-ff, изменения которые делались в FB, невозможно будет увидеть. Это чревато тем, что для того, чтобы увидеть все изменения сделанные в FB, придется перечитать все сообщения журнала, и выбирать именно те коммиты, которые касаются интересующих изменений. Отмена изменений FB также станет настоящей головной болью, тогда как --no--ff упрощает эту задачу, ведь откатить придется всего лишь один коммит. Хотя именно такое поведение более предпочтительное.
В оригинальной статье автор указал, что не нашел способа, как по умолчанию заставить Git использовать --no-ff по умолчанию. Но вот тут LySeVi в комментариях подсказывает, что это решается благодаря правке .gitconfig:
[alias]
merge=merge --no-ff

Release Branches

Правила ветвления, объединения и именования:
  • Ветвление этих веток может быть произведено только от develop
  • Эти ветки должны объединяться обратно с develop и master
  • Имя ветки должно быть формата release-*

Release branches (далее RB) используются при подготовке к выпуску очередного релиза. Они позволяют в самую последнюю минуту расставить все точки над i. Кроме того, они позволяют вносить исправления мелких ошибок, и подготовить мета данные для релиза (номер версии, дата создания и т.д.). Делая все эти вещи в RB, ветка develop останется чиста, и будет готова к добавлению нового кода, отвечающего непосредственно за расширение функционала разрабатываемого продукта.
Создание RB производится в тот момент, когда состояние кода в develop отражает желаемое состояние нового релиза. По крайней мере, весь запланированный функционал ожидаемый в релизе, уже должен быть в develop. Весь оставшийся код, отвечающий за расширение функционала и отложенный на будущие релизы, должен ждать, пока не будет выпущен релиз.
Именно при создании RB, релиз получает номер версии, а не раньше. До этого момента, в ветке develop отражены изменения запланированые для выпуска в "следующем релизе", но станет ли следующий релиз 0.3 или 1.0 неизвестно до создания RB. Присвоение релизу номера версии происходит в момент создания RB, и следует правилам нумерации, которые предложены в проекте.

Создание release branch


RB создаются ветвлением от develop. Создание такой ветки покажем на примере. Пускай версия 1.1.5 - это текущий production релиз, и мы приближаемся к следующему большому релизу. Состояние ветки develop говорит о готовности к следующему релизу, и мы решили что это будет 1.2, а не 1.1.6 или 2.0. Теперь, мы создаем ветку, и даем ей имя отображающее номер релиза:
$ git checkout -b release-1.2 develop
Switched to a new branch "release-1.2"
$ ./bump-version.sh 1.2
Files modified successfully, version bumped to 1.2.
$ git commit -a -m "Bumped version number to 1.2"
[release-1.2 74d9424] Bumped version number to 1.2
1 files changed, 1 insertions(+), 1 deletions(-)
После создания новой ветки, и переключения на нее, мы изменяем везде номер версии на новый. К примеру это могут быть номера версии в исходных файлах проекта, в параметрах сборки executable файла, или еще что-то. В примере выше, это автоматизировано, и для этой цели используется вымышленный скрипт bump-version.sh, который производит нужные изменения в файлах рабочей директории, для отображения состояния новой версии. Это конечно может быть произведено и в ручную. В нашем примере, мы используем некий абстрактный проект. После этого заносим изменения в RB.
Эта новая ветка будет существовать какое-то время, пока релиз не будет полностью готов. В течении этого времени исправляются мелкие ошибки которые могут быть отражены в этой ветке (а не в ветке develop). Добавление больших изменений в функционал строго запрещены во время существования RB. Они должны быть объединены обратно с develop и ожидать следующего большого релиза.
По сути весь этот процесс очень похож на выпуск так называемой бета версии, когда производится только тестирование продукта, поиск и исправление ошибок, но уже никакой новый функционал и прочее не добавляется.

Закрытие release branch и выпуск релиза


Когда состояние кода в RB готово к реальному релизу, необходимо провести некоторые действия. Во первых, нужно объединить RB обратно с master (напомню еще раз, что каждый коммит в master - это новый релиз по определению). Далее, сделанный коммит следует отметить тегом, отображающим номер версии, что поможет в будущем легче ориентироваться в релизах. И наконец, изменения сделанные в RB должны быть объединены обратно с develop, так чтобы будущие релизы содержали все исправления, сделанные в процессе подготовки релиза.
Первые два шага в Git:
$ git checkout master
Switched to branch 'master'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2
Релиз выпущен, и помечен для будущего использования.
Для того, чтобы сохранить изменения сделаные в RB, нужно объединить их обратно в develop. В Git:
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
Этот шаг может привести к конфликту во время объединения. Если такое случилось, то следует исправить конфликты и зафиксировать изменения.
Теперь мы действительно все сделали, и RB может быть удалена, так как она нам больше не нужна:
$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).

Hotfix branches

Правила ветвления, объединения и именования:
  • Ветвление этих веток может быть произведено только от master
  • Эти ветки должны объединяться обратно с develop и master
  • Имя ветки должно быть формата hotfix-*

Hotfix branches (далее HB) очень похожи на ветки релизов в том, что они также предназначены для подготовки к новым релизам, хотя и незапланированным. Они возникают, из-за необходимости действовать сразу же после обнаружения серьезной ошибки в production коде. Когда критическая ошибка в production версии должна быть разрешена немедленно, HB может быть создана ветвлением от коммита в ветке master с меткой текущей production версии.
Использование такого подхода заключается в том, что члены команды смогут дальше продолжать решение поставленных задач (в ветке develop), в то время как ответственный разработчик будет готовить исправление ошибки в production версии.

Создание hotfix branch


HB создается ветвлением от ветки master. Рассмотрим небольшой пример. Допустим, версия 1.2 является текущей production версией, которая сейчас используется и вызывает проблемы из-за нескольких досадных и неприятных ошибок в коде. Изменения в ветке develop пока нестабильные, чтобы делать новый релиз. В таком случае, мы ветвлением от master создаем HB и начинаем исправлять ошибку в рамках этой ветки:
$ git checkout -b hotfix-1.2.1 master
Switched to a new branch "hotfix-1.2.1"
$ ./bump-version.sh 1.2.1
Files modified successfully, version bumped to 1.2.1.
$ git commit -a -m "Bumped version number to 1.2.1"
[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)
Не забывайте применить изменения версии после ответвления!
После, исправляем ошибку и фиксируем исправление одним или несколькими коммитами.
$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)

Закрытие hotfix branch и выпуск внеочередного релиза


После исправления ошибки, нужно объединить HB обратно с master, но также требуется объединить эту ветку обратно с develop, с тем, чтобы гарантировать, что исправления будут доступны и в последующих релизах. Эти действия аналогичны закрытию RB.
Теперь обновим master и тег релиза.
$ git checkout master
Switched to branch 'master'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2.1
Далее, занесем исправление в ветку develop:
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
Из правила работы с HB есть одно исключение: если RB уже существует, изменения HB должны быть объединены обратно в RB, исключая при этом develop. Объединение с RB приведет к тому, что после выпуска релиза, исправление будет также доступно и в ветке develop. (Если работа в develop требует немедленного исправления, и не может ждать пока будет выпущен релиз, можно безопасно занести исправление и в develop).
И в конце, удаляем временную ветку:
$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).

Подведем итоги

Если вы дочитали до этого момента, то скорее всего ознакомились с моделью предлагаемой Vincent Driessen. В чем то она может показаться вам знакомой, и вам может показаться, что он изобрел велосипед, но тем не менее, часто самые простые и логичные решения люди пропускают мимо себя. Смотря на предложенную модель можно сказать, что она очень элегантна, проста, понятна и легка в использовании. Модель очень хорошо сочетается с Git. Также при использовании этой модели, члены команды постепенно все больще будут развивать понимание ветвления и процесса выпуска релизов.