Webrium
Подписаться

Когда dev, stage и master разошлись: как выкатывать фичи

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

Схема стыка веток: совпадение и расхождение dev, stage и masterСовпадаютРасходятся

Как устроена схема dev-stage-master и почему она ломается

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

Откуда берётся расхождение веток

Расхождение веток — не случайность, а следствие того, как Git работает с историей изменений. Вот четыре типичных сценария, когда коммиты оказываются не там, где ожидались.

Коммит попал в master или stage мимо dev

Предположим, в продакшене обнаружился баг, и его нужно исправить срочно. Хотфикс залили сразу в master, а потом перенесли в dev не слиянием, а cherry-pick. Теперь один и тот же код лежит двумя разными коммитами: один в master, другой в dev. История веток перестаёт совпадать, потому что cherry-pick создаёт новый коммит с теми же изменениями, но другим идентификатором.

В stage или master откатили то, что осталось в dev

Откат — это новый коммит, который отменяет изменения. Если после отката в master кто-то продолжил работать в dev, где отменённые изменения остались, ветки разойдутся. Например, в master откатили фичу А, но в dev её дорабатывают дальше. Теперь в dev есть изменения, которых нет в master, и схема ломается.

Хотфикс вернули переносом коммита, а не слиянием

Если хотфикс перенесли cherry-pick, а не merge, в истории master появится новый коммит, которого нет в dev. Слияние бы сохранило связь между ветками, а перенос — нет. В результате master и dev перестают быть синхронизированными.

Ветку переписали после слияния

Если после слияния в master ветку dev переписали (например, rebase), история коммитов изменится. Rebase перезаписывает историю, создавая новые коммиты с теми же изменениями, но другими идентификаторами. Теперь master перестанет быть подмножеством dev, потому что коммиты в dev уже не те, что были при слиянии.

В каждом случае расхождение — это не ошибка, а следствие того, что Git не хранит информацию о синхронизации веток. Он хранит только историю изменений, и если её переписали или перенесли, связь теряется.

Инвариант: что должно быть в master, должно быть и в dev

Схема dev-stage-master держится на одном правиле: master — это подмножество stage, а stage — подмножество dev. Это означает, что всё, что попало в master, должно быть и в stage, и в dev. Проверить это можно так: список коммитов, которые есть в master, но которых нет в dev, должен быть пуст. Если он не пуст — схема уже сломана.

Эту проверку можно автоматизировать. Например, скрипт, который сравнивает историю коммитов в master и dev и падает, если находит расхождения. Такой скрипт не даст забыть о проблеме, но не решит её сам по себе — он только покажет, что схема перестала работать.

Почему ветка не может быть окружением

Когда программист один, dev — это кандидат на релиз. Всё, что в него попало, готово к выкатке. Но как только программистов становится несколько, dev превращается в свалку начатого. Если в dev лежат две фичи, А и Б, и одна из них не готова, выкатить другую уже нельзя — они связаны в момент слияния.

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

Главная развилка: ветка или фича

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

Ветка как единица релиза

В этом случае dev — это кандидат на релиз, и если в нём что-то не готово, выкатка задерживается. Все фичи должны быть готовы одновременно, иначе релиз не состоится. Это просто, но негибко: одна задержка блокирует всех.

Фича как единица релиза

В этом случае dev — это рабочая ветка, а релиз собирается из master и отдельных веток фич. Так можно выкатывать фичи по одной, не дожидаясь остальных. Но такой подход требует дополнительной инфраструктуры: нужно уметь включать и выключать фичи по отдельности, например, с помощью переключателей функций.

Три схемы по возрастанию зрелости

Строгое продвижение вперёд только слияниями

Самая простая схема: dev → stage → master. Работает, пока программистов мало и все фичи готовы одновременно. Как только появляется задержка, схема ломается, потому что неготовая фича блокирует релиз.

Релизная ветка от master из готовых фич

Вместо того чтобы выкатывать dev целиком, создаётся релизная ветка от master, в которую сливаются только готовые фичи. Так можно выкатывать часть изменений, не дожидаясь остальных. Это гибче, но требует дисциплины: нужно следить за тем, какие фичи готовы, а какие нет.

Одна основная ветка с переключателями функций

Самая зрелая схема: одна ветка (обычно master), в которой фичи включаются и выключаются переключателями. Выкатка отвязана от слияния, и можно выкатывать фичи по одной, не дожидаясь остальных. Это требует инфраструктуры для управления переключателями, но снимает проблему расхождения веток.

Что меняется, когда программистов несколько

Когда программист один, он сам решает, что готово к релизу. Когда их несколько, решение о составе релиза должно приниматься совместно. Но если решение принимается слиянием в общую ветку, оно фактически принимается в одиночку — тот, кто слил последним, решил за всех.

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

Тупик: перед релизом вычистить dev откатами

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

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

Граница: когда трёхветочная схема ещё работает

Трёхветочная схема работает, пока:

  • все фичи готовы одновременно;
  • расхождения веток редки и легко исправляются;
  • решение о составе релиза принимается совместно, а не слиянием в общую ветку.

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

Позиция редакции

Трёхветочная схема dev-stage-master — это не решение, а отсрочка. Она работает, пока команда маленькая и все фичи готовы одновременно. Как только появляются задержки, схема начинает ломаться, и её приходится чинить вручную.

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

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

Эта позиция неверна, если:

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

Что дальше

Если трёхветочная схема уже не работает, пора задуматься о переходе на более зрелую. Например, на релизные ветки от master или на переключатели функций. Главное — не пытаться чинить схему одноразовыми действиями, а признать, что она исчерпала себя, и перейти на что-то более подходящее для текущих условий.

С чем это связано

Метка над заголовком говорит, зачем туда идти: продолжить тему, перейти к практике или увидеть возражение.

Разделение незавершённого кода и срочного исправления в продакшенеМусор в devФикс в master
Практика

Срочный фикс в master, когда в dev лежит несделанное

Как быстро и безопасно исправить баг в продакшене, если в dev уже лежит незавершённая разработка.

Цепочка коммитов уходит за границу указателя ветки: последний коммит недостижим, но физически не удалёнуказатель веткикоммит не удалён
Предыстория

Git-приёмы, которые перестают быть страшными

Как git хранит историю и почему опасные команды не стирают данные — база, чтобы не бояться reset, rebase и bisect при выкатке фич.

Конфликт — это пересечение двух намерений, а не поломка кодаслилось самоконфликт
Углубление

Конфликт слияния: почему он ваш и что с ним делать

Как конфликты слияния превращаются в потерю кода и времени — и почему их можно разрешать без компромиссов.

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

Деплой без кластера: что осталось живым

Как обойтись без кластера и не потерять надёжность — проверенная схема с релиз-каталогами, симлинками и systemd для выкатки сервисов.

Письмо по вторникам

Один разбор недели и короткий список того, что изменилось. Без дайджестов на сорок ссылок.

Начните вводить — материалы появятся здесь.

выбрать · Enter открыть · Esc закрыть