Данная статья
опубликована автором Vincent Driessen на английском языке. Я перевёл её в качестве упражнения для собственного удовольствия и предлагаю к прочтению. Далее - текст автора.
Успешная модель ветвления в Git
Опубликовано: 5 января 2010
В этом сообщении я представляю модель разработки, которую я внедрил во всех моих проектах (рабочих и личных) около года назад, и которая оказалась очень успешной. Я в течение некоторого времени намеревался написать об этом, но до этого момента никогда не находил времени, чтобы сделать это исчерпывающе. Я не буду говорить о каких-либо деталях проектов, только о стратегии ветвления и управлении релизами.
Модель сосредоточена на
Git как на средстве управления версиями всего исходного кода.
Почему Git?
За исчерпывающим обсуждением «за» и «против» Git в сравнении с централизованными системами управления кодом,
обращайтесь в интернет. Там происходит множество флеймовых войн. Как разработчик сегодня я предпочитаю Git всем другим средствам. Git реально изменил то, как разработчики думают о слиянии и ветвлении. В классическом мире CVS/Subversion, откуда я пришёл, слияние/ветвление всегда казалось немного пугающим («берегись конфликтов слияния, они кусаются!») и тем, что вы делаете только изредка.
Но с Git эти действия крайне дешевы и просты и реально рассматриваются как одна из центральных частей вашего ежедневного рабочего процесса. Например, в
книгах о CVS/Subversion ветвление и слияние впервые обсуждается в последних главах (для продвинутых пользователей), в то время как в
каждой книге о Git это описывается уже в главе 3 (основы).
Вследствие простоты и повторяющейся природы ветвление и слияние больше не являются тем, чего следует бояться. Средства контроля версий предназначены для помощи в ветвлении/слиянии более, чем что бы то ни было.
Теперь хватит об инструментах, давайте направимся к модели разработки. Модель, которую я собираюсь представить, по существу является не более чем набором процедур, которым должен следовать каждый член команды, чтобы достичь управляемого процесса разработки программного обеспечения.
Децентрализованная, но централизованная
Структура репозитория, которую мы используем и которая хорошо работает, имеет центральный «истинный» репозиторий. Заметьте, что этот репозиторий только считается центральным (так как Git является распределенной системой управления версиями, в нём нет такой сущности, как центральный репозиторий на техническом уровне). Мы будем называть этот репозиторий origin, так как это имя знакомо всем пользователям Git.
Каждый разработчик делает pull и push в origin. Но кроме централизованных взаимоотношений типа pull/push, каждый разработчик может также брать изменения от других равноправных членов, чтобы формировать подкоманды. Например, это может быть полезно, чтобы работать вместе с двумя или более разработчиками над большой функцией, чтобы преждевременно не отправлять текущую работу в origin. На рисунке сверху есть подкоманды из Алисы и Боба, Алисы и Дэвида и Клэр и Дэвида.
Технически это означает не более чем, что Алиса определила remote, названный bob и указывающий на репозиторий Боба, и наоборот.
Главные ветки
Наша модель разработки в своей основе сильно вдохновлена существующими моделями. Центральный репозиторий содержит две главные ветки с бесконечным временем жизни:
Ветка master в origin должна быть знакома каждому пользователю Git. Параллельно ветке master существует ещё одна - develop.
Мы рассматриваем origin/master как главную ветку, в которой исходный код в HEAD-ревизии всегда отражает какое-то готовое состояние.
Мы рассматриваем origin/develop как главную ветку, в которой исходный код в HEAD-ревизии отражает состояние с последними изменениями разработки, сделанными для следующего релиза. Её можно называть «веткой интеграции». Отсюда делаются все автоматические ночные сборки.
Когда исходный код в ветке develop достигает стабильной точки и готов к релизу, все изменения должны быть каким-то образом слиты обратно в master и отмечены тэгом с номером релиза. Как это делается, мы подробно обсудим далее.
Таким образом, каждый раз, когда изменения сливаются обратно в master, это означает новый готовый релиз по определению. Мы склонны быть очень строгими с этим, так что каждый раз, когда происходит коммит в master, теоретически мы могли бы использовать скрипт, реагирующий на это событие в Git, чтобы автоматически собирать и развёртывать программное обеспечение на рабочие сервера.
Вспомогательные ветки
Следом за главными ветками master и develop наша модель разработки использует разнообразные вспомогательные ветки, которые способствуют параллельной разработке между членами команды, упрощают отслеживание фич, подготавливают релизы и помогают в быстром исправлении проблем в выпущенном коде. В отличие от главных веток, эти ветки всегда имеют ограниченное время жизни, так так со временем их удалят.
Мы используем следующие типы веток:
- Ветки для фич
- Ветки для релизов
- Ветки для хотфиксов
Каждая из таких веток имеет определённое предназначение и связана строгими правилами о том, какие ветки могут быть их источниками, и какие ветки для них могут быть целями слияния. Через минуту мы пройдёмся по ним.
Эти ветки ни в коем случае не являются «особыми» в техническом плане. Эти типы веток объединяются по способу использования. Конечно, это обычные ветки Git.
Ветки для фич
Могут ответвляться от: develop
Должны сливаться обратно в: develop
Соглашение о именах веток: что угодно, кроме master, develop, release-* или hotfix-*
Ветки для фич используются, чтобы разрабатывать новые фичи для предстоящего релиза или для релиза в отдалённом будущем. Когда начинается разработка фичи, целевой релиз, в который она будет включена, может быть неизвестным. Суть ветки для фичи в том, что она существует столько, сколько фича находится в разработке, но в конечном счёте она будет слита обратно в develop (чтобы явно добавить новую фичу в предстоящий релиз) или отвергнута (в случае неудачного эксперимента).
Обычно ветки для фич существуют только в репозиториях разработчиков, а не в origin.
Создание ветки для фич
Начиная работу над новой фичей, ответвитесь от ветки develop.
$ git checkout -b myfeature develop
Switched to a new branch "myfeature"
Включение законченной фичи в develop
Законченные фичи могут быть слиты в ветку develop. В явном виде добавьте их в предстоящий релиз:
$ 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 заставляет merge всегда создавать новый объект коммита, даже если слияние может быть выполнено с fast forward. Это исключает потерю информации об историческом существовании ветки для фичи и группирует вместе все коммиты, которые добавили эту фичи. Сравните:
В последнем случае в истории Git невозможно увидеть, какие из объектов коммитов вместе реализовали фичу - вам, возможно, придётся вручную прочитать все сообщения журнала. Откат целой фичи (т.е. группы коммитов) в последней ситуации - это реальная головная боль, в то время как это легко сделать, если использовать флаг --no-ff.
Да, это создаст немного больше (пустых) объектов коммитов, но выгода гораздо больше стоимости.
К сожалению, я не нашёл способа сделать --no-ff поведением git merge по умолчанию, но это должно было бы быть именно так.
Релизные ветки
Могут ответвляться от: develop
Могут сливаться обратно в: develop и master
Соглашение о наименовании веток: release-*
Релизные ветки обеспечивают подготовку нового готового релиза. Они позволяют расставить все точки над i в последнюю минуту. Более того, они позволяют делать небольшие багфиксы и подготавливать мета-данные для релиза (номер версии, даты сборки, и т.д.). Делая всю эту работу в релизной ветке, вы оставляете ветку develop для сбора фич, предназначенных для следующего большого релиза.
Ключевой момент для создания новой релизной ветки - когда develop (почти) отражает желаемое состояние нового релиза. По крайней мере, все фичи, предназначенные для собираемого релиза, должны быть слиты в develop к этому моменту. В этом отличие от фич, предназначенных для будущих релизов, - их можно включать в develop уже после создания текущей релизной ветки.
Предстоящему релизу номер версии назначается именно тогда, когда стартует релизная ветка, и не ранее. До момента, пока релизная ветка не стартовала, ветка develop отражала изменения для «следующего релиза», но было неясно, станет ли этот «следующий релиз» в конце концов 0.3 или 1.0. Это решение принимается при старте релизной ветки и диктуется правилами проекта по назначению номеров версий.
Создание релизной ветки
Релизные ветки создаются из ветки develop. Скажем, версия 1.1.5 является текущим готовым релизом, и у нас на подходе новый большой релиз. Состояние 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 "Прописан номер версии 1.2"
[release-1.2 74d9424] Прописан номер версии 1.2
1 files changed, 1 insertions(+), 1 deletions(-)
После создания новой ветки и переключения на неё, мы прописали номер версии. Здесь, bump-version.sh - это вымышленный скрипт, который изменяет некоторые файлы в рабочей копии, чтобы отразить новую версию. (Это, конечно, может быть и ручным изменением - главное, чтобы некоторые файлы изменились.) Затем коммитится прописанный номер версии.
Эта новая ветка может существовать некоторое время, пока релиз нельзя будет окончательно опубликовать. В это время к этой ветке можно будет применять багфиксы (лучше к ней, а не к develop). Здесь строго запрещено добавление больших новых фич. Их следует сливать в develop и, таким образом, ждать следующего большого релиза.
Завершение релизной ветки
Когда состояние релизной ветки готово стать реальным релизом, необходимо произвести некоторые действия. Во-первых, релизная ветка сливается в master (так как каждый коммит в master является новым релизом по определению, помним об этом). Далее, этот коммит в master нужно пометить тэгом для простоты будущих ссылок на эту историческую ревизию. И наконец, изменения, сделанные в релизной ветке, должны быть слиты обратно в 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
Теперь релиз сделан и помечен для ссылок в будущем.
Дополнительно: вы можете также захотеть использовать флаги -s и -u <ключ>, чтобы криптографически подписать свой тэг.
Чтобы сохранить изменения, сделанные в релизной ветке, нам нужно слить их обратно в develop. В Git:
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
Этот шаг может привести к конфликту слияния (это даже вероятно, так как мы изменили номер версии). Если так, то исправьте его и делайте коммит.
Теперь мы действительно закончили, и можно удалить релизную ветку, раз мы в ней больше не нуждаемся:
$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).
Ветки для хотфиксов
Могут ответвляться от: master
Могут сливаться обратно в: develop and master
Соглашение о наименовании веток: hotfix-*
Ветки для хотфиксов очень похожи на релизные ветки в том, что они также предназначены для подготовки нового готового релиза, хотя и не являются запланированными. Они возникают из необходимости отреагировать на нежелательное состояние действующего релиза. Когда требуется немедленно исправить критическую ошибку в готовой версии, ветку для хотфиков можно ответвить от соответствующего тэга в ветке master.
Суть в том, что работа членов команды (над веткой develop) может продолжаться, пока другой человек готовит быстрое исправление для текущей версии.
Создание ветки для хотфикса
Ветки для хотфиксов создаются из ветки master. Например, допустим, что версия 1.2 является текущим production-релизом и вызывает проблемы из-за серьёзной ошибки, но изменения в develop ещё нестабильны. Тогда мы можем сделать ветку для хотфикса и начать исправлять проблему:
$ 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 "Прописан номер версии 1.2.1"
[hotfix-1.2.1 41e61bb] Прописан номер версии 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)
Не забудьте прописать номер версии после ветвления!
Затем, исправьте ошибку и зафиксируйте изменения в одном или нескольких отдельных коммитах.
$ git commit -m "Исправлена серьёзная проблема"
[hotfix-1.2.1 abbe5d6] Исправлена серьёзная проблема
5 files changed, 32 insertions(+), 17 deletions(-)
Завершение ветки для хотфикса
Когда багфикс закончен, его нужно слить обратно в master. Но его нужно также слить в develop, чтобы убедиться, что хотфикс будет включён в следующий релиз. Это полностью повторяет то, как завершаются релизные ветки.
Сначала, обновите 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
Дополнительно: вы можете также захотеть использовать флаги -s и -u <ключ>, чтобы криптографически подписать свой тэг.
Затем, включите багфикс также в develop:
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
Одно исключение из этого правила состоит в том, что если в данный момент существует релизная ветка, изменения хотфикса должен быть слит в эту ветку, а не в develop. Включение багфикса в релизную ветку в конце концов приведёт к тому, что багфикс также будет слит в develop, когда релизная ветка будет закончена. (Если для работы в develop требуется это исправление, и нельзя ждать до того, как релизная ветка будет закончена, вы можете заодно безопасно слить багфикс в develop.)
В конце, удалите временную ветку:
$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).
Резюме
Хотя в этой модели нет ничего действительно потрясающего, «большая схема», с которой началось это сообщение, оказалась жутко полезной в наших проектах. Она даёт элегантную мнемоническую модель, которую легко понять и которая позволяет членам команды укрепить совместное понимание процессов ветвления и подготовки релизов.
Здесь можно скачать высококачественную версию схемы в формате PDF. Возьмите и повесьте её на стену для быстрого напоминания в любое время.
Update: Для всех, кто просил: исходник главной схемы в формате Apple Keynote (
gitflow-model.src.key).
Git-branching-model.pdf