git · branching model

Git-flow: branching model из статьи nvie

Конспект «A successful Git branching model» Vincent Driessen (2010): master и develop живут вечно, feature / release / hotfix — временные, каждый merge — с --no-ff, каждый release получает tag.

Модель держится на двух вечных branch — master (всегда production-ready) и develop (интеграция изменений для следующего release) — плюс три типа временных branch: feature, release и hotfix, у каждого строго определено, от чего он branch-ится и куда merge-ится. Каждый merge в master — по определению новый production release и помечается tag. Все merges выполняются с --no-ff, чтобы история каждого feature оставалась сгруппированной и её можно было целиком прочитать или revert-нуть.

1 Два главных branch: master и develop

Оба существуют в центральном репозитории (origin) с бесконечным временем жизни:

origin/master HEAD всегда отражает production-ready состояние. Каждый merge в master — «новый production release по определению»: правило настолько строгое, что на commit в master можно повесить hook и автоматически deploy-ить в production.
origin/develop Отражает «последние доставленные изменения для следующего release» — integration branch, источник nightly builds. Когда develop достигает стабильного состояния, изменения merge-ятся в master и помечаются tag с номером release.

2 Три типа supporting branch

Все три — обычные git branches с ограниченным временем жизни; никакой магии в них нет, «особыми» их делают только конвенции использования: от чего branch-иться, куда merge-иться и как называться.

feature  Feature branches

Назначение: разработка новых features для ближайшего или будущего release. Живёт, пока feature в разработке; если эксперимент не удался — branch просто выбрасывается. Обычно существует только в репозиториях разработчиков, а не в origin.

Branch отdevelop
Merge вdevelop
Namingчто угодно, кроме master, develop, release-*, hotfix-*

release  Release branches

Назначение: подготовка production release — последняя полировка, мелкие bugfixes, метаданные (номер версии, build dates). Пока release готовится, develop свободен для features следующего release. Номер версии присваивается только в момент создания release branch — до этого develop отражает «следующий release», но неизвестно, станет ли он 0.3 или 1.0. Крупные новые features здесь запрещены.

Branch отdevelop
Merge вdevelop и master
Namingrelease-*

hotfix  Hotfix branches

Назначение: незапланированный production release — критичный bug в живой версии надо чинить немедленно. Branch создаётся от tag на master, помечающего сломанную production-версию. Смысл: пока один человек готовит быстрый production fix, работа остальной команды на develop продолжается.

Branch отmaster
Merge вdevelop и master
Naminghotfix-*

исключение  Если в этот момент существует release branch, hotfix merge-ится в него, а не в develop: fix попадёт в develop вместе с завершением release branch. Если fix нужен в develop срочно — можно merge-ить и туда сразу.

3 Сводная таблица

Branch typeBranches fromMerges intoNaming
featuredevelopdevelopвсё, кроме master, develop, release-*, hotfix-*
releasedevelopdevelop и masterrelease-*
hotfixmasterdevelop и masterhotfix-*

4 Команды из статьи

Feature: создать и закончить

# создать feature branch от develop
$ git checkout -b myfeature develop
Switched to a new branch "myfeature"

# закончить: merge в develop с --no-ff, удалить branch, push
$ 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

Release: создать, bump-нуть версию, выпустить с tag

# создать release branch от develop; здесь версия получает номер
$ git checkout -b release-1.2 develop
Switched to a new branch "release-1.2"
$ ./bump-version.sh 1.2          # bump-version.sh — вымышленный script; суть — файлы меняются
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(-)
# выпустить: merge в master + tag
$ 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 <key>

# и обязательно merge обратно в develop, потом удалить branch
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2   # скорее всего конфликт на номере версии — исправить и commit
Merge made by recursive.
(Summary of changes)
$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).

Hotfix: от tag на master, обратно в master и develop

# создать hotfix branch от master
$ 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(-)

# починить bug, commit-нуть fix
$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)

# выпустить fix: merge в master + tag, затем merge в develop
$ git checkout master
$ git merge --no-ff hotfix-1.2.1
$ git tag -a 1.2.1
$ git checkout develop
$ git merge --no-ff hotfix-1.2.1
$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).

5 Почему всегда --no-ff

--no-ff заставляет git создать merge commit даже там, где возможен fast-forward. Аргументы из статьи:

merge --no-ff: commits feature — группой, merge commit виден
fast-forward: история линейная, где кончается feature — не видно

6 Вся модель на одной схеме

master hotfixes release develop feature tag 0.1 tag 0.2 tag 1.0 fix критичного bug bump версии мелкий bugfix merge --no-ff merge + tag merge + tag время течёт вниз
Жизненный цикл: hotfix 0.2 branch-ится от tag 0.1 и merge-ится в master и develop; feature уходит из develop и возвращается в него; release готовит версию и даёт tag 1.0 на master. Все стрелки-merge — с --no-ff.

note of reflection (2020)

Спустя десять лет автор дописал к статье оговорку: модель родилась в 2010 году, git-flow стал «hugely popular», но, к сожалению, многие стали относиться к нему как к догме или panacea. За это время разработка сместилась к web-приложениям, которые «typically continuously delivered, not rolled back» и не существуют в нескольких поддерживаемых версиях одновременно — это не тот класс software, под который модель проектировалась.

Командам с continuous delivery автор сам советует не втискивать git-flow, а взять «a much simpler workflow (like GitHub flow)». Git-flow остаётся уместным для explicitly versioned software и продуктов, где надо поддерживать несколько версий «in the wild». Финальный совет: «panaceas don't exist. Consider your own context. … Decide for yourself».