В этой статье я покажу одну из моделей организации разработки в GIT, которую я стал применять во всех моих проектах (и в рабочих, и в личных) около года назад. Этот GIT Flow оказался очень действенным. Я собирался написать статью об этой модели уже давно, но никак не находил времени. Я не буду углубляться в детали проектов. Обсудим только стратегию ветвления и управление релизами.
Почему именно GIT?
В интернете полно обсуждений о преимуществах и недостатках GIT по сравнению с централизованными системами контроля исходного кода. Там можно найти множество пламенных дискуссий. Будучи разработчиком, я предпочитаю GIT всем остальным инструментам, доступным сейчас. GIT действительно изменил то, как разработчики думают о ветвлении и слиянии. Я пришел из классического мира CVS/Subversion. В нем и слияние, и ветвление считались слегка пугающими («стерегитесь конфликтов при слиянии!»); там думают. Эти действия проводятся очень редко.
В GIT эти процессы стали легкими и мелочными. Они считаются одними из главных элементов повседневной работы. Например, в книгах по CVS/Subversion слияние и ветвление впервые вводятся в последних главах, предназначенных для опытных пользователей. В каждой книге по GIT все это обсуждается уже в 3 главе, то есть считается частью основ.
Вследствие своей простоты и повторяющейся природы, ветвление и слияние больше не являются чем-то, чего стоит бояться. Инструменты контроля версий должны помогать в ветвлении и слиянии больше всего на свете.
Достаточно об инструментах! Давайте рассмотрим непосредственно модель разработки GIT Flow. Модель, которую я собираюсь здесь показать, по сути является не более чем набором процедур, которым должен следовать каждый программист, чтобы прийти к управляемому процессу разработки ПО.
Одновременно децентрализованный и централизованный
Настройка репозитория, который мы используем, заключается в центральном «истинном» репозитории. Такая настройка отлично работает в данной модели. Обратите внимание, что этот репозиторий считается центральным (поскольку GIT является DVCS, такого понятия, как центральный репозиторий, на техническом уровне не существует). Мы будем называть этот репозиторий origin
, так как это название знакомо всем пользователям GIT.
Каждый разработчик использует команды pull
и push
, обращаясь к origin. Помимо централизованных отношений pull-push, каждый разработчик может также извлекать изменения от других членов команды. Например, это может быть полезно для совместной работы с 2 или более разработчиками над большой новой функциональностью. Они не смогут отправить origin команду push преждевременно. На рисунке выше представлены подгруппы Алисы и Боба, Алисы и Дэвида, Клэр и Дэвида.
Технически это означает не что иное, как то, что Алиса определила удаленную ветку GIT с именем bob, указывая на хранилище Боба, и наоборот.
Основные ветки
По сути, данная модель разработки, связанная с GIT Flow, в значительной степени вдохновлена существующими моделями. Центральный репозиторий содержит две основные ветви: master
и develop
. Продолжительность их «жизни» бесконечна.
Ветка master в origin должна быть знакома каждому пользователю GIT. Параллельно с основной веткой существует другая ветвь, которая называется develop.
Мы считаем origin/master
основной ветвью, где исходный код HEAD всегда отражает состояние на продакшене.
Мы считаем, что origin/develop
является основной ветвью, где исходный код HEAD всегда отражает состояние с последними изменениями, которые могут потребоваться для разработки следующего релиза. Некоторые называют ее «интеграционной ветвью». Это то, из чего строятся любые автоматические сборки.
Когда исходный код в ветви develop достигает стабильной точки и готов к релизу, все изменения должны быть как-то объединены обратно в master, а затем помечены номером релиза. Подробнее этот момент будет обсуждаться ниже.
Следовательно, каждый раз, когда изменения объединяются с master, по определению появляется новый релиз. Мы, как правило, очень строго воспринимает это правило. Теоретически мы могли бы использовать скрипт GIT Hook для автоматической сборки и развертывания ПО на серверах каждый раз, когда выполняется коммит на master.
Второстепенные ветки, в которых ведется основная работа
Наряду с основными ветвями master и develop данная модель разработки, основанная на GIT Flow, использует множество второстепенных веток. В них в основном и ведется работа. Это способствует параллельной разработке между членами команды, облегчению отслеживания изменения функционала, подготовке к релизу рабочей версии. Это быстро решает возникающие проблемы. В отличие от основных ветвей, эти ветки всегда имеют ограниченный срок «жизни», так как в конечном итоге они будут удалены после завершения в них работы.
Мы можем использовать следующие типы веток:
- Feature branches (Ветки c новым/разрабатываем функционалом);
- Release branches (Ветки релизов);
- Hotfix branches (Ветки с быстрыми исправлениями).
Каждая из этих ветвей имеет определенную цель. Они связаны строгими правилами относительно того, из каких ветвей они выходят, а в какие должны слиться. Скоро мы это обсудим.
Эти ветви ни в коем случае не являются «особыми» с технической точки зрения. Типы ветвей классифицируются по тому, как мы их используем.
Feature branches
Могут ответвляться от:
develop
Должны вливаться в:
develop
Могут быть названы как угодно, кроме:
master, develop, release-*, hotfix-*
Функциональные ветки используются для разработки нового функционала для предстоящего в скором или будущем релизе. При начале разработки функционала - целевой выпуск, в который будет включен этот код, может быть неизвестен в этот момент. Суть функциональной ветви заключается в том, что она существует до тех пор, пока код находится в стадии разработки. В конечном итоге ветка будет объединена с develop (для последующего релиза) или удалена (в случае неудачного эксперимента).
Эти ветви обычно существуют только в репозиториях разработчиков, а не в origin.
Когда разработчик начинает работу над новой задачей, ему необходимо сделать новую ветку. Она должна ответвляться от develop:
git checkout -b myfeature develop
Когда он закончит свою задачу, разработчику необходимо будет слить свою ветку в develop, что позволит в дальнейшем добавить его код в релиз на продакшен.
git checkout develop
git merge --no-ff myfeature
git branch -d myfeature
git push origin develop
Благодаря флагу --no-ff
при слиянии всегда образуется новый коммит, даже если оно выполняется в режиме fast-forward
. Это позволяет избежать потери информации о существовании ветки компонента и объединяет все коммиты, которые вместе добавляли новый функционал. Для сравнения:
В последнем случае в истории GIT невозможно увидеть, какие из коммитов вместе реализовали функционал. Вам придется вручную прочитать все сообщения log. Откатить все коммиты относящиеся к функционалу (т. е. группы коммитов) - настоящая головная боль в последней ситуации. Все было бы проще простого при использовании флага --no-ff
.
Да, это создаст еще несколько (пустых) коммитов, но выигрыш намного больше, чем стоимость.
Release branches
Могут отходить от:
develop
Должны слиться с develop в:
master
Название:
release-*
Ветки релизов необходимы для подготовки нового выпуска функционала продукта. В них обычно исправляют незначительные ошибки и подготавливают метаданные для релиза (номер версии, дата сборки и т. д.). Выполняя всю эту работу над веткой релиза, ветвь develop очищается для получения нового кода для следующего крупного релиза.
Ключевой момент, подходящий для отделения новой ветки выпуска от develop, - это то, когда разработка (почти) отражает желаемое состояние новой версии. На этот момент весь код, который должен быть выпущен, уже должен быть слит с develop. Все функции и исправления, нацеленные на будущие релизы, могут отсутствовать. Им придется подождать разветвлений ветви релиза.
Именно в начале ветки выпуска следующему релизу присваивается номер версии, не раньше. До этого момента ветвь develop отражала изменения для «следующего релиза». Вы не узнаете, станет ли этот «следующий релиз» 0.3 или 1.0, пока не будет запущена ветвь релиза. Это решение принимается в начале ветки релиза и выполняется в соответствии с правилами проекта по изменению номера версии.
Ветви выпусков создаются из ветки develop. Скажем, версия 1.1.5 является текущей версией, и у нас скоро будет большой выпуск. Состояние разработки готово к «следующему релизу», и мы решили, что он станет версией 1.2 (а не 1.1.6 или 2.0). Поэтому мы разветвляемся и даем ветке релиза имя, отражающее номер новой версии:
git checkout -b release-1.2 develop
./bump-version.sh 1.2
git commit -a -m "Bumped version number to 1.2"
После создания новой ветки и перехода на нее мы указываем номер версии. Здесь bump-version.sh
представляет собой вымышленный сценарий оболочки, который изменяет некоторые файлы в рабочей копии, чтобы отразить новую версию. (Конечно, это можно делать вручную. Главное, чтобы поменялись некоторые файлы.) Затем фиксированный номер версии попадает в коммит.
Эта новая ветка может существовать некоторое время, пока релиз не будет точно выпущен. В течение этого времени исправления ошибок могут применяться в этой ветви (а не в ветви develop). Добавление большого нового функционала здесь строго запрещено. Ветки должны слиться с develop и, следовательно, ждать следующего крупного релиза.
Когда состояние ветки выпуска готово стать реальным релизом, необходимо выполнить некоторые действия. Во-первых, ветвь релиза объединяется с master (т. к. каждый коммит на master по определению является новым выпуском). Затем этот коммит на master должен быть помечен, чтобы потом было легко на него ссылаться. Наконец, изменения, внесенные в ветку выпуска, необходимо объединить с develop, чтобы будущие выпуски также содержали эти исправления ошибок.
Первые 2 шага в GIT:
git checkout master
git merge --no-ff release-1.2
git tag -a 1.2
Релиз уже готов и помечен для дальнейшего использования.
Также можно использовать флажки -s или <key> для криптографической подписи тега.
Чтобы сохранить изменения, внесенные в ветку релиза, нужно слить их с develop. В GIT:
git checkout develop
git merge --no-ff release-1.2
Этот шаг вполне может привести к конфликту слияния. Это очень вероятно, так как мы сменили номер версии. Если такое произойдет, исправьте ошибку и создавайте коммит.
Теперь мы действительно закончили. Ветку релиза можно удалить, так как она нам больше не нужна:
git branch -d release-1.2
Hotfix branches
Могут отходить от:
master
Должны слиться с:
develop, master
Название:
hotfix-*
Ветви исправлений очень похожи на ветки выпусков в том смысле, что они тоже предназначены для подготовки к новому релизу, пусть и незапланированному. Они возникают из-за необходимости действовать немедленно, когда состояние версии нежелательно. Когда критическая ошибка в рабочей версии должна быть устранена немедленно, ветвь исправления может быть отделена от соответствующего тега в ветви master этой версии.
Суть в том, что работа членов команды (в ветви develop) может продолжаться, пока другой человек готовит быстрое исправление бага.
Ветки исправлений создаются из ветки master. Например, текущая рабочая версия 1.2. Она запущена, но в ней появляются проблемы из-за серьезной ошибки. Изменения в develop пока нестабильны. Тогда мы можем создать ветку исправлений и исправить баг:
git checkout -b hotfix-1.2.1 master
./bump-version.sh 1.2.1
git commit -a -m "Bumped version number to 1.2.1"
Не забывайте зафиксировать номер версии после создания ветки!
После исправления бага изменения фиксируются в 1 или нескольких коммитах:
git commit -m "Fixed severe production problem"
По окончании исправление необходимо слить с master. Также необходимо слить ветку и с develop, чтобы гарантировать, что исправление также будет включено в следующий выпуск.
Сначала обновите master и отметьте релиз:
git checkout master
git merge --no-ff hotfix-1.2.1
git tag -a 1.2.1
Также можно использовать флаги -s или <key> для криптографической подписи тега.
Далее включите исправление и в develop:
git checkout develop
git merge --no-ff hotfix-1.2.1
Единственное исключение из этого правила: если существует ветвь выпуска, исправления необходимо слить с ней, а не с develop. Обратное объединение исправления с веткой выпуска приведет к тому, что исправление также будет добавлено в develop, когда ветвь выпуска будет завершена. (Если работа в разработке требует немедленного исправления и не может ждать завершения ветки выпуска, можно смело вносить исправление в develop уже сейчас.)
Наконец, удалите временную ветку:
git branch -d hotfix-1.2.1
Заключение
В этой модели ветвления Git Flow нет ничего совершенно нового. «Общая картина», с которой начался этот пост, оказалась поразительно полезной в наших проектах. Она формирует великолепную ментальную модель, которую легко понять. Также между членами одной команды формируется взаимопонимание ветвлений и процессов выпусков.