GitLab Flow VS Git flow VS GitHub flow

Это перевод достаточно важной статьи про GitLab Flow, альтернативе Git flow и GitHub flow. Статья была написана в 2014, так что скриншоты успели устареть. Тем не менее сама статья более чем актуальна:

Ветвление и слияние веток в git устроено гораздо проще, чем в более ранних системах контроля версий, таких как SVN. Поэтому есть много способов организации командной работы над кодом, и большинство из них достаточно хороши. По крайней мере, они дают много преимуществ по сравнению с тем, что было до git. Но сам по себе git — не серебряная пуля, и во многих командах организация рабочего процесса с git имеет ряд проблем:

  • Не описан точным образом весь рабочий процесс,
  • Вносится ненужная сложность,
  • Нет связи с трекером задач (issue tracker).

Мы хотим представить вам GitLab flow — чётко определённый набор практик, решающий эти проблемы. Он объединяет в одну систему:

Эта статья описывает все аспекты GitLab flow, включая работу с ветками, интеграцию с задачами, непрерывную интеграцию и развёртывание. Её цель — помочь новым командам перейти на git и сразу внедрить простые, прозрачные и эффективные правила работы с ним.

Four stages (working copy, index, local repo, remote repo) and three steps between them

В большинстве систем контроля версий, чтобы поделиться кодом с коллегами, нужно сделать одно действие — коммит в репозиторий. В git то же самое происходит в три этапа:

  1. Добавить изменения из рабочей области проекта в индекс (область подготовленных файлов, staging area);
  2. Сделать коммит на основе индекса;
  3. Запушить коммит в удалённый репозиторий.

Освоение этих действий — первый шаг в изучении git. Следом идет работа с ветками.

Multiple long running branches and merging in all directions

Если не устанавливать никаких правил работы с ветками, в репозитории начинает расти энтропия:

  • Появляется много долгоживущих веток,
  • Вносимые изменения оказываются размазанными по разным веткам,
  • Непонятно, откуда начинать разработку новой фичи или откуда разворачивать код на production.

Для решения этих проблем обычно внедряется некая стандартная модель работы, например git flow или GitHub flow. Мы считаем, что у всех этих моделей есть потенциал для улучшения, поэтому мы разработали GitLab flow.

Git flow и его ограничения

Git Flow timeline by Vincent Driessen, used with permission

Модель рабочего процесса Git flow появилась одной из первых и стала довольно широко известной. Она предполагает наличие основной ветки master, ветки для накопленных изменений develop, а также отдельных веток для фич, релизов и хотфиксов. Разрабатываемые изменения мержатся в develop, оттуда в релизные ветки, и в итоге попадают в master. Git flow достаточно подробно и четко определяет рабочий процесс, но его сложность порождает две проблемы.

Во-первых, разработчики должны использовать ветку develop, а не master, потому что последняя зарезервирована под релизный код. Это противоречит привычной практике называть master основную ветку, от которой ответвляются прочие ветки, и в которую мержится результат. Нередко разработчики по ошибке мержат какие-то изменения только в master, забывая про develop. А большинство графических интерфейсов к git по умолчанию считают основной веткой именно master, поэтому в них приходится каждый раз что-то переключать или настраивать.

Во-вторых, лишняя сложность появляется из-за веток релизов и хотфиксов. Большинство команд, особенно небольших, может легко обойтись без них. Сегодня большинство организаций придерживается практики непрерывной доставки (continuous delivery), которая предполагает, что код из основной ветки можно развёртывать на продакшен (production, то, что предоставляется пользователям).

Следовательно, можно исключить ветки релизов и хотфиксов и всю лишнюю работу, которая для них требуется. Пример такой лишней работы — обратный мерж релизных веток в master. Для решения этой проблемы есть специальные инструменты, но они тоже требуют изучения документации и только добавляют сложности.

GitHub flow – более простой вариант

Master branch with feature branches merged in

В противовес сложной модели git flow был разработана модель GitHub flow. В ней есть только master и feature-ветки. Это упрощение привело к успешному внедрению GitHub flow множеством компаний. Компания Atlassian предложила похожую стратегию. Но, в отличие от GitHub, они предпочитают делать ребейз (rebase), а не мерж веток в master.

Мерж всех изменений в master и частое развёртывание позволяют не писать код «в стол», а сразу выпускать изменения. Это соответствует идеям бережливого (lean) производства и непрерывной доставки. Но множество вопросов остаются без ответа: когда именно нужно развёртывать и в каких окружениях, как выпускать релизы, как связать всё это с трекером задач. GitLab flow отвечает на все эти вопросы.

GitLab flow: ветка production

Master branch and production branch with arrow that indicate deployments

GitHub flow строится на предположении, что вы можете развернуть ваш код на продакшен в любой момент, сразу после мержа feature-ветки в master. Это верно для SaaS-приложений, но неверно в множестве других случаев. Бывает, что вы не можете влиять на точное время релиза. Например, вы выпускаете приложение под iOS и каждое обновление должно пройти валидацию в AppStore. Другой пример — когда релизить можно в строго определённое время (например, с 10 до 16 в будние дни, когда все сотрудники находятся на рабочем месте), но замержить ветку в master можно в любое время.

Для управления выпуском кода в продакшен GitLab flow предлагает использовать специальную ветку production. Настройте автоматическое развёртывание кода из этой ветки при каждом изменении в ней. Теперь для релиза достаточно сделать мерж из ветки master в production. Состояние ветки даст вам точную информацию о том, какая версия кода сейчас выпущена, а приблизительное время выпуска можно будет определить по времени создания мерж-коммита. Если вам нужна абсолютная точность, можно в процессе развёртывания создавать новый тег с timestamp'ом в описании.

GitLab flow: ветки для нескольких сред

Multiple branches with the code cascading from one to another

Может быть полезно иметь отдельную среду (environment), в которую происходит развёртывание из ветки master. В этом единственном случае название среды может отличаться от названия ветки.

Предположим, что у вас есть несколько сред: стейджинг (staging), пре-продакшен (pre-production) и продакшен (production). Код из master автоматически развёртывается на стейджинг. Как только вы готовы развернуть его на пре-продакшен, вы создаете мерж-реквест из master в pre-production. Соответственно, мерж из pre-production в production означает окончательный релиз. Такой процесс, когда все коммиты проходят через ветки в строго определенном порядке, гарантирует, что изменения прошли тестирование во всех средах.

Если вам нужно быстро «протащить» хотфикс на продакшен, то можно реализовать его в обычной feature-ветке, а потом открыть мерж-реквест в master, не удаляя ветку. Теперь, если код в master проходит тесты и жизнеспособен (правильно настроенная непрерывная доставка должна гарантировать это), вы можете замержить ветку хотфикса последовательно в pre-production и production. Если же изменения требуют дополнительного тестирования, то вместо немедленного мержа нужно открыть мерж-реквесты в те же ветки. В «экстремальном» случае отдельная среда может создаваться для каждой ветки. Так делается, например, в Teatro.

GitLab flow: релизные ветки

Master and multiple release branches that vary in length with cherry-picks from master

Ветки релизов понадобятся вам только если вы выпускаете ПО для внешних клиентов. В таком случае каждая минорная версия будет храниться в отдельной ветке (2.3-stable, 2.4-stable и т.п.).

Стабильные (stable) ветки должны создаваться от ветки master. Их нужно создавать как можно позже, чтобы минимизировать добавление хотфиксов в несколько веток. После того, как релизная ветка создана, в неё можно включать только исправления серьёзных багов. Следуйте правилу "upstream first": всегда, когда это возможно, сначала делайте мерж исправлений в master, и только оттуда — cherry-pick в релизную ветку. Благодаря этому правилу вы не забудете сделать cherry-pick исправлений в master и не встретите тот же самый баг в следующем релизе. Правило "upstream first" применяется в том числе в Google и Red Hat. Каждый раз, когда в релизную ветку добавляется исправление бага, нужно повысить третье число в номере версии (по правилам семантического версионирования). Обозначьте эту версию новым тегом в git. В некоторых проектах используется ветка stable, которая всегда указывает на тот же коммит, что и последний релиз. Ветка production (или master в правилах git flow) в таком случае не нужна.

GitLab flow: мерж/пулл-реквесты

Merge request with line comments

Мерж-реквест или пулл-реквест создаётся в системе управления git-репозиториями. Это запрос на мерж одной ветки в другую, подобно задаче, назначаемый на какого-либо исполнителя. GitHub и Bitbucket используют термин «пулл-реквевст», потому что первое необходимое действие — сделать пулл предлагаемой ветки. GitLab и Gitorious используют термин «мерж-реквест», потому что заключительное действие — собственно, мерж ветки. Далее в этой статье мы будем называть это мерж-реквестом.

Если вы работаете над веткой больше, чем пару часов, имеет смысл поделиться промежуточным результатом с коллегами через мерж-реквест. Не назначайте его на кого-либо, а просто упомяните (командой /cc @имя) ваших коллег в описании реквеста или в комментарии — они получат уведомление. Это будет означать, что реквест не готов к мержу, но по нему уже можно давать обратную связь. Вы можете явным образом обозначить, что работа над реквестом не завершена. Для этого начните заголовок реквеста с [WIP] или WIP:, то есть "Work in progress". Такой мерж-реквест даже нельзя будет замержить через интерфейс GitLab (хотя по-прежнему можно вручную через git).

Интерфейс GitLab позволяет оставлять комментарии как к реквесту в целом, так и к конкретным строкам кода. Таким образом, мерж-реквест уже включает в себя инструментарий для ревью кода, и какие-то дополнительные инструменты вам не понадобятся. По результатам ревью кто угодно может внести правки следующим коммитом в ту же ветку (обычно это делает автор реквеста). Все последующие коммиты, запушенные в эту же ветку, включаются в мерж-реквест, а дифф обновляется автоматически и корректно работает даже с push -f.

Когда фича готова, и ветку можно мержить, назначьте реквест на того, кто хорошо знает код проекта (и у кого есть права на мерж в master). Этот человек несёт ответственность за окончательное ревью и принимает решение: замержить результат или закрыть реквест без мержа.

В GitLab есть стандартная практика — «защищать» долгоживущие ветки (такие как master или production). Защита ветки не позволяет участникам с уровнем доступа "Developer" пушить в неё любые изменения.

Поэтому для мержа в защищённую ветку нужно открывать мерж-реквест, назначаемый на участника с более высоким уровнем доступа.

GitLab flow: интеграция с задачами (issues)

Merge request with the branch name 15-require-a-password-to-change-it and assignee field shown

GitLab flow позволяет вам явным образом связывать код и задачи из трекера.

Любые значимые изменения в коде должны сопровождаться задачей, в которой сформулированы требования и смысл изменений. Это помогает оставаться в рамках задачи, а также даёт команде представление о том, чем вы заняты. В GitLab каждое изменение кодовой базы начинается c оформления задачи в трекере. Если предполагаемые изменения хоть сколько-нибудь серьёзны (например, требуют более часа работы), то работу нужно начинать с оформления задачи. Многие команды уже следуют этому правилу, потому что всегда оценивают время выполнения задачи, прежде чем взять её в спринт.

Заголовки задач желательно формулировать так, чтобы они описывали желаемое состояние системы.

Хороший пример: «Как администратор, я хочу иметь возможность удалить пользователя без ошибок».
Плохой пример: «Админ не может удалять пользователей».

Приступая к работе над задачей, создайте новую ветку от ветки master. Её название должно начинаться с номера тикета, например 42-admin-can-remove-users.

Когда вы завершили работу над задачей или хотите получить промежуточную обратную связь, открывайте мерж-реквест. Помните о возможности отправить оповещение (/cc @имя) коллегам и отметке WIP:.

В момент, когда вы считаете, что работа завершена, назначьте реквест на ревьюера и уберите WIP:.

Ревьюер может принять (замержить) реквест как через командную строку, так и через кнопку в интерфейсе реквеста. Нажатие на кнопку автоматически создаёт мерж-коммит, описание которого формируется на основе описания реквеста. Мерж-коммит полезен тем, что он сохраняет в истории время и обстоятельства мержа. Поэтому, по умолчанию, коммит создаётся всегда, даже если был возможен "fast-forward merge", когда master просто переключается на последний коммит вашей ветки. В git эта стратегия называется "no fast-forward" и используется с командой git merge --no-ff. GitLab EE и .com предлагают выбор поведения при мерже, подробности далее в статье.

Feature-ветка обычно больше не нужна после мержа, поэтому интерфейс реквеста позволяет удалить её. Предположим, что ветка была замержена, после чего вы обнаружили какие-то недоработки и переоткрыли задачу. Если старая ветка удалена, можно создать новую ветку с тем же именем и продолжить разработку в ней. Как правило, одной задаче соответствует не более одной ветки, но в одной ветке может решаться несколько задач.

Связывание задач и мерж-реквестов

Merge request showing the linked issues that will be closed

В сообщении коммита, либо в описании мерж-реквеста можно упомянуть задачу по её номеру, используя слово-триггер, например: fixes #14, closes #67. При этом GitLab публикует в упомянутой задаче комментарий с обратной ссылкой на коммит или реквест. А в мерж-реквесте появляется список связанных задач. Когда вы замержите код в основную ветку, связанные задачи будут отмечены как выполненные. Обратите внимание: триггеры распознаются только на английском, то есть fixes #14 сработает, а исправляет #14 — нет.

Если вы хотите создать ссылку на задачу, но не закрывать её, напишите просто её номер: "Duck typing is preferred. #12".

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

Rebase и объединение коммитов

Vim screen showing the rebase view

Git позволяет объединить (squash) несколько коммитов в один или поменять их порядок с помощью команды rebase -i. В GitLab EE и .com вы можете сделать это непосредственно перед мержем через веб-интерфейс. Это имеет смысл, если в процессе работы вы сделали несколько небольших коммитов, но хотите чтобы в master попал один, или если хотите выстроить коммиты в логическом порядке.

Помните, что коммиты, которые уже попали в удалённый репозиторий и, тем более, в стабильную ветку, ребейзить нельзя. Причина этого в том, что-нибудь мог оставить ссылку на них или вытащить (cherry-pick) в свою ветку. Ребейз меняет идентификаторы (SHA-1) коммитов, потому что фактически создаёт из них новые коммиты. В результате ваши изменения появляются в истории git с несколькими разными идентификаторами, что приводит к путанице и ошибкам. Ребейз также затрудняет ревью кода, так как теряется информация о том, какие изменения были внесены после ревью. Если объединяются коммиты разных авторов, то информация об авторстве тоже будет потеряна. Это лишает авторов указания на их авторство, а ещё мешает работе git blame (показывает, в каком коммите и кем изменялась каждая строка).

Регулярно делать коммиты и пушить их в удалённый репозиторий — хорошая практика, позволяющая коллегам видеть, над чем вы работаете. Но при таком подходе одна задача размазывается на много коммитов, так что историю разработки становится довольно сложно просматривать. Эти небольшие коммиты можно было бы объединить в один, но это приведёт к потере идентификаторов. Вместо этого можно просматривать историю по мерж-коммитам: они всегда объясняют суть изменения и обозначают момент мержа целой ветки.

Изменения, которые уже попали в master, нельзя стирать из истории и не так просто отменить через git revert. Если все коммиты были объединены в один с помощью rebase, можно применить revert к этому единственному коммиту. Однако мы убеждены, что в объединении коммитов больше вреда, чем пользы. К счастью, git умеет отменять мерж-коммиты. Если вы передумали и хотите вернуть отменённый мерж-коммит, то применяйте revert к коммиту, созданному в результате первого revert. Git всё равно не позволит вам замержить один и тот же коммит дважды.

Чтобы это стало возможным, необходимо сначала создать этот мерж-коммит. Поэтому, если вы мержите вручную, добавляйте опцию --no-ff. Система управления репозиториями сделает это за вас в момент принятия мерж-реквеста.

Не меняйте порядок коммитов с помощью rebase

List of sequential merge commits

Git позволяет вам сделать ребейз feature-ветки на master, в результате чего коммиты этой ветки оказываются в истории после коммитов в master. Это позволяет сделать мерж без мерж-коммита и в результате у вас получается простая линейная история. Но здесь действует то же правило, что и с объединением коммитов: не трогайте то, что уже попало в удалённый репозиторий. Мы рекомендуем не ребейзить даже промежуточные результаты вашей работы, отданные на ревью через мерж-реквест.

Использование rebase вынуждает вас многократно разрешать одни и те же конфликты. В некоторых случах это можно сделать командой git rerere (reuse recorded resolutions). Но ещё проще — вовсе не ребейзить и разрешать конфликты всего один раз, при мерже. Чем с меньшим количеством мерж-конфликтов вы сталкиваетесь — тем лучше.

Чтобы избежать лишних конфликтов, нужно не слишком часто мержить master в feature-ветки. Давайте разберём три возможных причины мержа master куда-либо ещё: «подтягивание кода» (leveraging code), мерж-конфликты и долгоживущие ветки.

Если вам нужно «подтянуть» изменения из master в feature-ветку — обычно можно обойтись вытаскиванием (cherry-pick) одного нужного коммита.

Конфликт при мерже feature-ветки обычно разрешается с помощью создания мерж-коммита. Если строки вашего файла могут находиться в произвольном порядке, то можно избежать некоторых конфликтов с помощью настройки gitattributes. Например, в файле .gitattributes репозитория GitLab есть строка CHANGELOG merge=union, и это позволяет мержить список изменений автоматически.

Последняя ситуация, когда необходимо мержить master куда-то ещё — это использование долгоживущих веток, которые периодически нужно обновлять до актуального состояния. Мартин Фаулер в своей статье о feature-ветках рассуждает о практике непрерывной интеграции (continuous integration, CI). Мы в GitLab немного путаем CI с тестированием веток.

Цитируя Фаулера: "Я знаю людей, которые утверждают, что практикуют CI, потому что выполняют сборку каждой ветки и каждого коммита, и даже могут при этом использовать CI-сервер. То, что они делают, называется непрерывной сборкой (continuous building). Это тоже благородное дело, но интеграции-то нет, а значит, нет и «непрерывной интеграции»."

Решение заключается в том, что feature-ветки должны существовать недолго и быстро мержиться. Можно ориентироваться на срок в один рабочий день. Если разработчик держит ветку для реализации задачи более одного дня, подумайте о том, чтобы раздробить задачу на более мелкие части. В качестве альтернативы можно использовать «переключатели фич» (feature toggles).

Для работы с долгоживущими ветками есть две стратегии:

  • Стратегия непрерывной интеграции предполагает, что вы мержите master в долгоживущую ветку в начале каждого дня,
    чтобы предотвратить более сложные мержи в будущем.
  • Стратегия «точки синхронизации» (synchronization point strategy) разрешает мержить только строго определённые коммиты,
    например отмеченые тегом релизы. Линус Торвальдс рекомендует именно такой способ, потому что код релизных версий лучше изучен.

GitLab EE предлагает возможность делать rebase непосредственно перед принятием мерж-реквеста. Вы можете включить эту возможность в настройках проекта, выбрав Merge Requests Rebase.

Перед принятием мерж-реквеста выберите опцию rebase before merge.

GitLab попытается сделать rebase перед мержем. Если rebase без конфликтов невозможен, будет выполнен обычный мерж.

В заключение хотелось бы сказать следующее: старайтесь делать меньше мерж-коммитов, но не исключайте их вовсе. Ваш код должен быть чистым, но его история должна быть достоверной. Разработка ПО происходит небольшими и не всегда красивыми шагами. То, что они сохранятся в истории кода — нормально. А ребейз делает историю недостоверной, после чего никакие инструменты не покажут вам действительную историю, потому что они не могут узнать идентификаторы коммитов, которые были до ребейза.

Используйте эмодзи в задачах и мерж-реквестах

Общепринятая практика — выражать одобрение или неодобрение с помощью кнопок +1 и -1.
В GitLab вы можете использовать эмодзи, чтобы, например, «дать пять» автору хорошей задачи или мерж-реквеста.

Пуш и удаление веток

Remove checkbox for branch in merge requests

Мы рекомендуем регулярно пушить локальные ветки в удалённый репозиторий, даже если код ещё не готов к ревью. Таким образом вы страхуетесь от ситуации, в которой кто-то другой начал работу над той же задачей. Разумеется, более правильный способ — назначить этой задаче исполнителя с помощью трекера задач. Но иногда этот способ даёт сбой, просто потому что никто об этом не вспомнил.

Когда ветка замержена в master, её можно удалить из репозитория. В GitLab и подобных ему системах это можно сделать непосредственно во время мержа. Это гарантирует, что при обзоре веток в системе управления репозиториями, вы увидите только те, над которыми действительно идёт работа. А ещё это освобождает имя и позволяет назвать им новую ветку. Это необходимо, если вы переоткрыли задачу и вам нужна новая ветка и новый мерж-реквест.

Делайте коммиты часто и пишите к ним корректные сообщения

Good and bad commit message

Мы рекомендуем начать коммитить код как можно раньше и делать это регулярно. Каждый раз, когда у вас есть работающий набор из кода и тестов к нему, можно сделать коммит. Преимущество этого способа в том, что если следующий этап работы зайдёт в тупик, вы всегда сможете вернуться к рабочей версии кода. Это кардинально отличается от работы с SVN, где код можно коммитить только тогда, когда он полностью готов. Когда ваша работа завершена, используйте мерж/пулл-реквест, чтобы поделиться ей.

Сообщение коммита должно описывать ваши намерения, а не пересказывать содержимое кода — его и так несложно посмотреть. Важно то, зачем вы сделали этот коммит.

Пример хорошего сообщения: «Скобминировать шаблоны, чтобы разгрузить интерфейс пользователя».

Некоторые слова портят сообщение, потому что ничего конкретного не значат: «поменять», «улучшить», «отрефакторить» и т.п. Слова «чинит», «исправляет» тоже лучше не использовать, только если вы не пишете "fix" (только на английском) в конце сообщения и вместе с номером задачи. Если вы хотите больше подробностей, рекомендуем прочитать отличную статью из блога Tim Pope.

Тестирование перед мержем

Merge requests showing the test states, red, yellow and green

В старых моделях рабочего процесса сервер непрерывной интеграции (CI server), как правило, запускал тесты только на ветке master. Поэтому разработчикам приходилось нести ответственность за то, чтобы не сломать master. В GitLab flow разработчики создают свои ветки от master, поэтому её всегда нужно поддерживать «зелёной». Поэтому каждый мерж-реквест нужно тестировать, прежде чем мержить. Инструменты CI, такие как GitLab CI или Travis, умеют показывать результаты сборки (build) непосредственно в мерж-реквесте.

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

Если ветки мержатся быстро и конфликтов при мерже нет, то обычно можно рискнуть и замержить, не тестируя результат. Если конфликты всё-таки есть, то можно замержить master в feature-ветку (т.е. наоборот), после чего ваш сервер CI запустит тесты на полученном коммите. Если feature-ветки живут дольше, чем несколько дней, стоит подумать об уменьшении масштаба ваших фич.

Мерж чужого кода в ваш код

Shell output showing git pull output

Когда начинаете работу над задачей, всегда создавайте feature-ветку от последнего коммита в master. Только если ваша работа требует изменений из определённой ветки, начните с этой ветки. Если впоследствии вам понадобилось замержить другую ветку, обязательно объясните необходимость этого в сообщении мерж-коммита. Пока вы не запушили вашу ветку в общий репозиторий, можно ребейзить её на master или другую ветку. Не нужно мержить стабильные ветки в свои feature-ветки, если в этом нет строгой необходимости. Линус Торвальдс вообще запрещает мержить стабильные ветки в feature-ветки, за исключением крупных релизов.

Источники: 1 - 2 - 3


25.03.2011 17:52