Система управления версиями 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
.
Каждый разработчик используя 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
:
1 2 |
|
При создании ветки feature
ветвление осуществляется от develop
.
Конец жизненного цикла ветки feature
Законченный функционал может быть объединен обратно с веткой develop
для добавления его в следующий релиз:
1 2 3 4 5 6 7 8 |
|
Использование флага --no-ff
создает новый коммит, который содержит все изменения сливаемой ветки. Это позволяет избежать потери информации о существовании ветки, в которой велась разработка добавленного функционала. Для сравнения, взгляните на изображение ниже. Слева слияние с использованием флага --no-ff
, справа без него:
Как видно, если не указать флаг –no-ff, изменения которые делались в ветке feature
, невозможно будет увидеть. Это чревато тем, что для того, чтобы увидеть все изменения сделанные в ветке feature
, придется перечитать все сообщения журнала, и выбирать именно те коммиты, которые касаются интересующих изменений. Отмена изменений ветки feature
также становится настоящей головной болью, тогда как --no-ff
упрощает эту задачу, ведь откатить придется всего лишь один коммит. Хотя именно такое поведение более предпочтительною
Для установки такого поведения слияния по умолчанию, можно заставить Git использовать --no-ff
по умолчанию, путем правки .gitconfig
, и добавления следующих строк:
1 2 |
|
Ветки 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
. Теперь, мы создаем ветку, и даем ей имя отображающее номер релиза:
1 2 3 4 5 6 7 |
|
После создания новой ветки, и переключения на нее, мы изменяем везде номер версии на новый. К примеру это могут быть номера версии в исходных файлах проекта, в параметрах сборки executable файла, или еще что-то. В примере выше, это автоматизировано, и для этой цели используется вымышленный скрипт bump-version.sh, который производит нужные изменения в файлах рабочей директории, для отображения состояния новой версии. Это конечно может быть произведено и в ручную. В нашем примере, мы используем некий абстрактный проект. После этого заносим изменения в RB.
После создания новой ветки, и переключения на нее, мы изменяем везде номер версии на новый. К примеру, это могут быть номера версии в исходных файлах проекта, в параметрах сборки выполняемого файла, или еще что-то. В примере выше, это автоматизировано, и для этой цели используется вымышленный скрипт bump-version.sh
, который производит нудные изменения в файлах рабочей директории, для отображения состояния новой версии. Это конечно может быть произведено и в ручную. В нашем примере, мы используем некий абстрактный проект. После этого заносим изменения в ветку release
.
Этот процесс очень похож на выпуск так называемой бета-версии, когда производится только тестирование продукта, поиск и исправление ошибок, но уже никакой новый функционал не добавляется.
Закрытие ветки release
и выпуск релиза
Когда состояние кода в ветке release
готово к выпуску релиза, необходимо провести некоторые действия. Во-первых, нужно влить ветку release
обратно в master
(напомним еще раз, что каждый коммит в master
- это новый релиз по определению). Далее, сделанный коммит следует отметить тегом, отображающим номер версии, что поможет в будущем легче ориентироваться в релизах. И наконец, изменения сделанные в ветке release
должны быть слиты обратно в develop
, так чтобы будущее релизы содержали все исправления, сделанные в процессе подготовки релиза.
Первые два шага в Git:
1 2 3 4 5 6 |
|
Релиз выпущен, и помечен для будущего использования.
Для того, чтобы сохранить изменения сделаные в ветке release
, нужно слить их обратно в develop
:
1 2 3 4 5 |
|
Этот шаг может привести к конфликтам во время слияния. Если такое произойдет, то следует исправить конфликты, и зафиксировать изменения.
Теперь релиз полностью выпущен, и release
ветка может быть удалена, так как она нам больше не нужна:
1 2 |
|
Ветки hotfix
Правила ветвления, слияния и именования:
- Ветвление этих веток может быть произведено только от
master
; - Эти ветки должны объединяться обратно с
develop
иmaster
; - Имя ветки должно быть формата
hotfix-*
.
Ветки hotfix
очень похожи на ветки release
в том, что они также предназначены для подготовки к новым релизам, хотя и не запланированным. Они возникают, из-за необходимости действовать сразу же после обнаружения серьезной ошибки в production коде. Когда критическая ошибка в production версии должна быть разрешена немедленно, hotfix
может быть создана ветвлением от коммита в ветке master
с меткой текущей production версии.
Использование такого перехода заключается в том, что члены команды смогут дальше продолжать решение поставленных задач (в ветке develop
), в то время как ответственный разработчик будет готовить исправление ошибки в production версии.
Создание ветки hotfix
Ветка hotfix
создается ветвлением от ветки master
. Рассмотрим небольшой пример. Допустим, версия 1.2
является текущей production версией, которая сейчас используется и вызывает проблемы из-за нескольких досадных и неприятных ошибок в коде. Изменения в ветке develop
пока нестабильные, чтобы делать новый релиз. В таком случае, мы ветвлением от master
создает ветку hotfix
и начинаем исправлять ошибку в рамках этой ветки:
1 2 3 4 5 6 7 |
|
Не забывайте применить изменения версии после ответвления!
После, исправляем ошибку и фиксируем исправление одним или несколькими коммитами.
1 2 3 |
|
Закрытие ветки hotfix
и выпуск внеочередного релиза
После исправления ошибки, нужно слить ветку hotfix
обратно в ветку master
, но также требуется объединить эту ветку обратно с develop
, с тем, чтобы гарантировать, что исправления будут доступны и в последующих релизах. Эти действия аналогичны закрытию ветки release
.
Обновим ветку master
и тег релиза.
1 2 3 4 5 6 |
|
Затем, занесем исправление в ветку develop
:
1 2 3 4 5 |
|
Из правила работы с ветками hotfix
есть одно исключение: если ветка release
уже существует, изменения ветки hotfix
должны быть слиты обратно в ветку release
, исключая при этом ветку develop
. Слияние с веткой release
приведет к тому, что после выпуска релиза, исправление будет также доступно и в ветке develop
. (Если работа в develop
требует немедленного исправления, и не может ждать пока будет выпущен релиз, можно безопасно занести исправление и в develop
).
В конце, удаляем ветку hotfix
:
1 2 |
|
Подведем итоги
Предложенная модель в чем-то может показаться знакомой, и может сложиться ощущение, что автор изобрел велосипед. На самом деле, автор объединил существующие подходы, очевидные вещи в единую логичную системы и подвел черту. Данная модель не является серебрянной пулей, но она проста, наглядна и легка в применении. Модель отлично подходит для тех проектов, где используется Git. Также, данная модель помогает развить понимание ветвления и процесса выпуска релизов.