Git стал таким же привычным явлением, как электричество в розетке. Его можно использовать совершенно по-разному — он либо сделает вашу жизнь удобнее, либо причинит боль и доставит кучу проблем.

Привет, меня зовут Макс Мартынов и я ведущий разработчик в Атвинте. В этой статье расскажу про наш подход к Git workflow, в котором баги одной фичи не блокируют деплой другой. Существует множество подходов и наш, конечно, не единственно верный.
Содержание
Выбрать workflow — значит регламентировать путь фичи
То, как вы работаете с Git, касается не только кода, но и коммуникаций, и процессов. Workflow — это договорённость между разработчиками, тестировщиками, девопсами и менеджментом о том, как именно вы будете обращаться с кодом. По сути, ваш внутренний регламент. И если его нет, рано или поздно это обернётся головной болью.
Но если Git — это стандарт, с которым работают все, то значит и этот регламент уже давно придуман? Отчасти так и есть. Ещё в 2010 году Vincent Driessen описал в своём блоге, каким, по его мнению, должен быть Git Flow. Со временем появились и другие подходы — например, GitHub Flow, GitLab Flow и Trunk-Based Development.
Универсального Git workflow, который подходит всем, не существует. То, что отлично работает в стартапе с двумя разработчиками, умрёт под грузом бюрократии в enterprise-продукте с распределённой командой. А процесс, идеальный для мобильного приложения, где релизы проходят модерацию в сторах, может оказаться избыточным и медленным для веб-сервиса, который регулярно обновляется незаметно для пользователя.
Когда мы говорим «выбрать workflow», мы на самом деле подразумеваем «регламентировать наш путь фичи». И здесь внутри команды стоит обсудить несколько вопросов:
Как часто мы хотим/можем/должны выкатывать изменения — раз в месяц, раз в день или сразу по готовности?
Сколько человек работает над одной кодовой базой?
Кто отвечает за релиз?
Как мы поступаем со срочными фиксами — они идут в обход workflow или подчиняются тем же правилам?
Ваш workflow — это всегда компромисс между скоростью релиза, удобством разработчиков и контролем качества. Готовые фичи не должны ждать релиза по три недели, но и пользователи не обязаны страдать от багов на продакшене.
Путь к workflow: почему изначальная модель нам не подошла
Наша небольшая команда (от 2 до 4 разработчиков, 1 QA и 1 менеджер) занимается доработкой активно развивающегося веб-сервиса. У нас есть несколько требований к процессам:
любая фича или багфикс должны быть на продакшене сразу, как только будут готовы;
если фичи и/или багфиксы не связаны логически, то они не должны зависеть друг от друга;
если бага нет на тестовом окружении, то его не должно быть и на продакшене. Это действует и в обратную сторону — баги с продакшена, если их причина в коде, всегда должны воспроизводиться на тестовом окружении.
Давайте разберём, чем же нам не угодила модель Винсента Дриссена, о которой я упоминал выше. Вот та самая схема, которую вы наверняка уже много раз видели:

Что мы тут видим:
функциональные ветки (фича-ветки) начинаются от develop;
в master фичи попадают через релизные ветки, основанные на develop;
срочные багфиксы идут в develop и в master, минуя релизные ветки.
К чему это может привести, покажу на примерах, но перед этим обозначу вводные:
сервис уже работает в продакшене;
есть два окружения — продакшен (ветка master) и дев (ветка develop);
от бизнеса есть запрос на три доработки (далее: фича1, фича2 и фича3) в бизнес-логике сервиса;
в команде два разработчика (далее: разраб1 и разраб2) и один QA;
разраб1 работает над фичей1, разраб2 — над фичей2, первый из них, кто освободится, работает над фичей3 (так решил бизнес и/или менеджмент).
Оба разработчика создали свои ветки от develop и ведут в них разработку:

Разраб1 закончил фичу1 и залил в develop:

QA её проверил, нашёл кучу багов и вернул на доработку разрабу1. В это время разраб2 закончил фичу2 и готов залить её в develop. А как дальше — ему можно это сделать или же нет? Оба варианта нам не подходят — и вот почему.
Например, мы разрешили ему это сделать:

QA проверяет и не находит багов. Казалось бы, фича готова, надо бы её в master. Однако по workflow Винсента мы должны создать релизную ветку от develop, а там у нас фича1 и её баги. Не можем же мы их отправить на продакшен. Получается требование №1 не соблюдается — фича готова, но заливать её нельзя.
Аналогичную проблему получим, если всё же не разрешим ему заливать:

В этом случае ещё добавится простой в работе QA. Ведь он не может проверять фичу, которая не была залита. Так как разраб2 первым освободился, он берёт в работу фичу3 и согласно workflow Винсента создаёт ветку от develop:

Теперь ветка с фичей3 содержит в себе баги фичи1, что нарушает наше требование №2. Если допустить, что разраб2 закончит ещё одну фичу раньше, чем разраб1 разберётся с багами, то у нас будет уже две готовых к релизу фичи, которые ждут третью:

Если в этот самый момент появится срочный баг с продакшена, то согласно workflow Винсента мы должны будем залить его в develop, а затем в master.

Даже если QA подтвердит, что фикс рабочий — это будет справедливо только для develop, так как на данном этапе код в этом окружении сильно отличается от кода в продакшене. Получается, мы не можем гарантировать, что этот фикс сработает в master точно так же, как develop, и/или не породит новых багов. Это, в свою очередь, нарушает наше требование №3.
Наш подход: каждая фича идёт в продакшен по готовности
Наше решение на данный момент устраивает и бизнес, и менеджмент, и команду разработки.
Наши правила:
три окружения: release, stage и develop;
функциональные ветки (с фичами/фиксами) начинаются только от release, так как должны быть основаны на надёжном и проверенном коде (см. требование №2);
по готовности функциональные ветки сначала заливаются в develop;
если QA находит баги, то фиксы по этим багам производятся в функциональной ветке и по готовности снова заливаются в develop;
как только QA подтверждает, что на develop багов нет — функциональная ветка заливается в stage;
если QA подтверждает, что багов в stage нет, а они могут появиться, только если мерж-конфликт был решён иначе, чем мерж в develop — то stage заливается в release (см. требование №1);
stage заливается в release обязательно с флагом --ff-only, что позволяет иметь тестовое окружение, которое по коду полностью идентично продакшену (см. требование №3);
функциональные ветки заливаются в develop и stage обязательно с флагом --no-ff, что позволяет в случае нештатных ситуаций убрать фичу из нужного окружения, отменяя только коммиты слияния;
очень срочные хотфиксы багов с продакшена допускается залить одновременно в stage и develop (QA сразу проверяет на stage) только по согласованию с ведущим разработчиком, QA и менеджментом, так как если QA найдёт баги на stage, то это заблокирует заливку и тестирование функциональных веток в этом окружении.
Рассмотрим ту же самую ситуацию с двумя разработчиками, тремя фичами и одним хотфиксом, но уже по нашим правилам.
Оба разработчика начали ветки от release:

Разраб1 закончил фичу1 и залил в develop:

Аналогичный сценарий — фича1 возвращена на доработку из-за багов, а разраб2 закончил фичу2 и залил в develop:

Фича2 проверена, багов нет, а значит, разраб2 заливает её в stage и приступает к фиче3:

Далее фича3 готова и заливается в develop, а фича2 проверена на stage, значит ветку release можно «перемотать» (fast-forward) до этого же состояния:

Фича3 также проверена в develop и заливается в stage, а разраб1, наконец, разобрался с багами и отдал фичу1 в тестирование на develop:

Далее по сценарию у нас был хотфикс:

Как видим, пока разраб1 воевал с багами в своей ветке, разраб2 успешно довёз до продакшена две свои фичи и срочный хотфикс. При этом соблюдались все требования к процессам в команде.
Единственное «узкое место», когда релиз в продакшен невозможен — это наличие багов на stage. Именно по этой причине в наших правилах есть пункт про заливку функциональных веток только с флагом --no-ff, чтобы в ветки окружений попадали только мерж-коммиты, которые легко отменить (revert или reset, как удобно команде).
Вместо послесловия
Идеального Git workflow нет, но существует удобный workflow для вашей команды.
Наш процесс не всегда был таким, и я уверен, что он не всегда будет именно таким. Он живой — мы его дорабатываем, подкручиваем и обсуждаем на ретроспективах.
Если вы только начинаете выстраивать свои процессы — обсуждайте, пробуйте и ищите своё. Берите за основу чужие подходы (наш, например), но всегда примеряйте их на свою команду и на ваши реальные, уже случившиеся кейсы. В конечном счёте, Git workflow — это просто инструмент. Помните: молоток не виноват в том, что вы ударили им по пальцу. Каждому инструменту — своё применение.
Всем лёгких и быстрых релизов!
