Demiazz Harbor

Путанные гиковские мысли о веб-разработке, и вообще.

A Successfull Git Branching Model

| Комментарии

Система управления версиями Git заняла прочные позиции на рынке. Как и любая система управления версиями, Git подразумевает поиск и использование более удобных и простых подходов к организации работы с исходным кодом, и его контролю.

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

Свою статью он опубликовал по в своем блоге, и ее можно почитать в оригинале: A successful Git branching model.

В данном посте, я приведу свой немного вольный перевод этой статьи.

Предлагаемая модель управления исходным кодом, с использованием Git, успешно использовалась автором на всех его проектах (рабочих, и собственных). В статье не говорится о деталях проектов, а статья содержит лишь описание модели, которую автор с успехом применяет в повседневной разработке программного обеспечения.

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

Почему Git?

Данная статья не о Git как таковом, не о его преимуществах и недостатках. Эта статья лишь описывает одну из моделей разработки проекта, с применением особенностей Git.

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

Распределенные системы контроля версий, такие как Git, напротив, базируются на идее активного использования веток, их создания и объединения. Это позволило создать еще одну эффективную модель разработки, которая отлично подходит как для разработчика-одиночки, так и для командной разработки.

Что касается самой модели, то это не панацей, не великое открытие. Это всего лишь набор процедур и подходов, придерживаясь которых можно сделать процесс разработки ПО управляемым. Конечно, чтобы данная модель принесла свои плоды, требуется четкое соблюдение процедуры не отдельными разработчиками, а каждым участником команды. Как показала практика, и опыт применения этой модели автором на реальных проектах, модель является весьма жизнеспособной и эффективной.

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

Несмотря на долгое существование Git на рынке, как и децентрализованных систем управления версиями, до cих пор имеет место путаница в связи с централизованностью.

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

Пример "централизованности" Git

Каждый разработчик используя 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
  • ветки release
  • ветки hotfix

(Оригинальные названия типов веток сохранены намерено, во измежание путаницы в переводе. Прим. перев.)

Каждый из этих типов веток имеет конкретные цели и правила работы с ними. Для них строго определены те ветки, от которых можно произвести ветвление, и с какими ветками должны быть объединены. И опять же, во избежание введения в заблуждение, стоит оговориться, что эти ветки никак не являются “специальными” с технической точки зрения. Это обычные ветки Git. Их типизация зависит от правил, которые придерживаются при работе с ними.

Ветки feature

Правила ветвления, слияния и именования:

  • Эти ветки наследуются только от develop;
  • Эти ветки должны сливаться обратно с develop;
  • Имя ветки может быть любым, кроме master, develop, release-*, hotfix-*.

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

Создание ветки feature

Для создания ветки feature:

Создание ветки `feature`
1
2
$ git checkout -b myfeature develop
Switched to a new branch "myfeature"

При создании ветки feature ветвление осуществляется от develop.

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

Законченный функционал может быть объединен обратно с веткой develop для добавления его в следующий релиз:

Слияние `feature` ветки в `develop`
1
2
3
4
5
6
7
8
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
$ git branch -d myfeature
Deleted branch myfeature (was 05e9557).
$ git push origin develop

Использование флага --no-ff создает новый коммит, который содержит все изменения сливаемой ветки. Это позволяет избежать потери информации о существовании ветки, в которой велась разработка добавленного функционала. Для сравнения, взгляните на изображение ниже. Слева слияние с использованием флага --no-ff, справа без него:

Как видно, если не указать флаг –no-ff, изменения которые делались в ветке feature, невозможно будет увидеть. Это чревато тем, что для того, чтобы увидеть все изменения сделанные в ветке feature, придется перечитать все сообщения журнала, и выбирать именно те коммиты, которые касаются интересующих изменений. Отмена изменений ветки feature также становится настоящей головной болью, тогда как --no-ff упрощает эту задачу, ведь откатить придется всего лишь один коммит. Хотя именно такое поведение более предпочтительною

Для установки такого поведения слияния по умолчанию, можно заставить Git использовать --no-ff по умолчанию, путем правки .gitconfig, и добавления следующих строк:

Включение –no-ff по умолчанию
1
2
[alias]
merge=merge --no-ff

Ветки release

Правила ветвления, слияния и именования:

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

Ветки release используются при подготовке к выпуску очередного релиза. Они позволяют в самую последнюю минуту расставить все точки над i. Кроме того, они позволяют вносить исправления мелких ошибок, и подготовить метаданные для релиза (номер версии, дата создания и т.д.). Делая все эти действия в ветках release, ветка develop останется чиста, и будет готова к добавлению нового кода, отвечающего непосредственно за расширение функционала разрабатываемого продукта.

Создание ветки release производится в тот момент, когда состояние кода в develop отражает желаемое состояние нового релиза. По крайней мере, весь запланированный функционал ожидаемый в релизе, уже должен быть в develop ветке. Весь оставшийся код, отвечающий за расширение функционала, и отложенный на будущие релизы, должен ожидать, пока не будет выпущен релиз.

Именно при создании release ветки, релиз получает номер версии, а не раньше. До этого момента, в ветке develop отражены изменения запланированные для выпуска в следующем релизе, но станет ли следующий релиз 0.3 или 0.1 неизвестно до создания ветки release. Присвоение релизу номера версии происходит в момент создания release. Присвоение релизу номера версии происходит в момент создания release ветки, и следует правилам нумерации, которые предложены в проекте.

Создание ветки release

Ветки release создаются ветвлением от develop. Создание такой ветки покажем на примере. Пускай версия 1.1.5 - это текущий production релиз, и мы приближаемся к следующему большому релизу. Состояние ветки develop говорит о готовности к следующему релизу, и мы решили, что это будет 1.2, а не 1.1.6 или 2.0. Теперь, мы создаем ветку, и даем ей имя отображающее номер релиза:

Создание ветки `release`
1
2
3
4
5
6
7
$ 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.

После создания новой ветки, и переключения на нее, мы изменяем везде номер версии на новый. К примеру, это могут быть номера версии в исходных файлах проекта, в параметрах сборки выполняемого файла, или еще что-то. В примере выше, это автоматизировано, и для этой цели используется вымышленный скрипт bump-version.sh, который производит нудные изменения в файлах рабочей директории, для отображения состояния новой версии. Это конечно может быть произведено и в ручную. В нашем примере, мы используем некий абстрактный проект. После этого заносим изменения в ветку release.

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

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

Когда состояние кода в ветке release готово к выпуску релиза, необходимо провести некоторые действия. Во-первых, нужно влить ветку release обратно в master (напомним еще раз, что каждый коммит в master - это новый релиз по определению). Далее, сделанный коммит следует отметить тегом, отображающим номер версии, что поможет в будущем легче ориентироваться в релизах. И наконец, изменения сделанные в ветке release должны быть слиты обратно в develop, так чтобы будущее релизы содержали все исправления, сделанные в процессе подготовки релиза.

Первые два шага в Git:

Выпуск релиза
1
2
3
4
5
6
$ 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

Релиз выпущен, и помечен для будущего использования.

Для того, чтобы сохранить изменения сделаные в ветке release, нужно слить их обратно в develop:

Слияние `release` ветки в `develop`
1
2
3
4
5
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)

Этот шаг может привести к конфликтам во время слияния. Если такое произойдет, то следует исправить конфликты, и зафиксировать изменения.

Теперь релиз полностью выпущен, и release ветка может быть удалена, так как она нам больше не нужна:

Удаление ветки `release`
1
2
$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).

Ветки hotfix

Правила ветвления, слияния и именования:

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

Ветки hotfix очень похожи на ветки release в том, что они также предназначены для подготовки к новым релизам, хотя и не запланированным. Они возникают, из-за необходимости действовать сразу же после обнаружения серьезной ошибки в production коде. Когда критическая ошибка в production версии должна быть разрешена немедленно, hotfix может быть создана ветвлением от коммита в ветке master с меткой текущей production версии.

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

Создание ветки hotfix

Ветка hotfix создается ветвлением от ветки master. Рассмотрим небольшой пример. Допустим, версия 1.2 является текущей production версией, которая сейчас используется и вызывает проблемы из-за нескольких досадных и неприятных ошибок в коде. Изменения в ветке develop пока нестабильные, чтобы делать новый релиз. В таком случае, мы ветвлением от master создает ветку hotfix и начинаем исправлять ошибку в рамках этой ветки:

Создание ветки `hotfix`
1
2
3
4
5
6
7
$ 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(-)

Не забывайте применить изменения версии после ответвления!

После, исправляем ошибку и фиксируем исправление одним или несколькими коммитами.

Фиксация изменений
1
2
3
$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)

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

После исправления ошибки, нужно слить ветку hotfix обратно в ветку master, но также требуется объединить эту ветку обратно с develop, с тем, чтобы гарантировать, что исправления будут доступны и в последующих релизах. Эти действия аналогичны закрытию ветки release.

Обновим ветку master и тег релиза.

Обновление `master` и тега релиза
1
2
3
4
5
6
$ 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:

Занесение изменений в ветку `develop`
1
2
3
4
5
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)

Из правила работы с ветками hotfix есть одно исключение: если ветка release уже существует, изменения ветки hotfix должны быть слиты обратно в ветку release, исключая при этом ветку develop. Слияние с веткой release приведет к тому, что после выпуска релиза, исправление будет также доступно и в ветке develop. (Если работа в develop требует немедленного исправления, и не может ждать пока будет выпущен релиз, можно безопасно занести исправление и в develop).

В конце, удаляем ветку hotfix:

Удаление ветки `hotfix`
1
2
$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).

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

Предложенная модель в чем-то может показаться знакомой, и может сложиться ощущение, что автор изобрел велосипед. На самом деле, автор объединил существующие подходы, очевидные вещи в единую логичную системы и подвел черту. Данная модель не является серебрянной пулей, но она проста, наглядна и легка в применении. Модель отлично подходит для тех проектов, где используется Git. Также, данная модель помогает развить понимание ветвления и процесса выпуска релизов.

Комментарии