Когда master ломается, а в dev лежит мусор
В трёхветочной схеме dev → stage → master авария в продакшене — это всегда выбор между плохим и очень плохим. В dev лежат несколько незакрытых веток, часть из которых уже слита, но не готова к продакшену. В stage — то же самое, но с меньшим объёмом мусора. А в master — баг, который нужно исправить прямо сейчас.
Первое, что приходит в голову: слить dev в master и выкатить всё разом. Это быстро, это просто, и это почти всегда приводит к новым багам. Почему? Потому что вместе с фиксом в продакшен попадают все незавершённые изменения, которые лежат в dev. Даже если фикс сам по себе корректен, он может сломать систему, если выкатится вместе с незрелым кодом.
Поэтому правильный порядок действий здесь не просто последовательность команд, а защита от будущих ошибок.
Как устроена трёхветочная схема
В классической схеме dev → stage → master каждая ветка решает свою задачу:
dev— интеграционная ветка для разработки. В неё сливают все рабочие ветки, даже те, которые ещё не готовы к релизу. Здесь тестируют взаимодействие между изменениями, но не проверяют стабильность.stage— предрелизная ветка. В неё попадает только то, что готово к выкатке в продакшен. Здесь проверяют, что код работает в условиях, близких к боевым, но без риска для пользователей.master— продакшен. В неё попадает только то, что прошло черезstageи готово к выкатке. Здесь нет места для экспериментов.
Проблема в том, что в момент аварии dev и stage почти никогда не находятся в состоянии, пригодном для выкатки. В dev лежат несколько начатых веток, часть из которых уже слита, но не готова к продакшену. В stage — то же самое, но с меньшим объёмом мусора. Если выкатить dev или stage целиком в master, то вместе с фиксом в продакшен попадут и все незавершённые изменения.
Почему нельзя выкатывать dev или stage целиком
Представим, что в dev лежат три ветки:
- Новая фича, которая ещё не прошла код-ревью.
- Рефакторинг, который сломает API, если его выкатить без клиентского кода.
- Багфикс, который нужно выкатить срочно.
Если слить dev в master, то в продакшен попадут все три изменения. Даже если багфикс сам по себе корректен, вместе с ним выкатятся и незавершённые изменения, которые могут сломать систему.
То же самое касается stage: если в ней лежит незрелый код, то выкатка stage в master приведёт к тем же проблемам, только с меньшим объёмом мусора.
Тупик: почему слияние dev в master не работает
Первое, что пробуют сделать в такой ситуации, — это слить dev в master и выкатить всё разом. Это кажется логичным: если в dev лежит фикс, то почему бы не выкатить его вместе со всем остальным?
Проблема в том, что dev — это не стабильная ветка. В ней лежат все незавершённые изменения, которые ещё не готовы к продакшену. Если выкатить dev в master, то вместе с фиксом в продакшен попадут и все эти изменения. Это может привести к новым багам, которые будут сложнее исправить, чем исходный баг.
Кроме того, если в dev лежит несколько веток, то слияние может привести к конфликтам. Эти конфликты придётся решать в срочном порядке, что увеличивает риск ошибок.
Поэтому слияние dev в master — это тупик. Оно решает одну проблему, но создаёт несколько новых.
Как правильно сделать срочный фикс
Правильный порядок действий для срочного фикса:
- Создать ветку от
master. Это гарантирует, что в фикс не попадут незавершённые изменения изdevилиstage. - Сделать минимальное изменение. Фикс должен решать только одну проблему и не затрагивать ничего лишнего.
- Выкатить фикс в
master. Это единственный шаг, который нельзя откладывать. - Вернуть фикс в
stageиdev. Это нужно сделать сразу после выкатки, чтобы избежать потери фикса.
Почему именно в таком порядке? Потому что если сначала слить фикс в dev или stage, а потом выкатывать в master, то есть риск, что вместе с фиксом в продакшен попадут и другие изменения. А если сначала выкатить в master, а потом забыть вернуть фикс в dev и stage, то через некоторое время кто-то может выкатить в продакшен старую версию кода, в которой баг ещё не исправлен.
Что делать, если фикс уже сделан в dev-ветке
Если фикс уже сделан в dev, но ещё не выкатан в master, то его нужно перенести в ветку от master. Это можно сделать двумя способами:
- Слияние (
merge) — если фикс сделан в отдельной ветке, которая ещё не слита вdev. В этом случае можно просто слить эту ветку в ветку отmaster. - Перенос отдельного изменения (
cherry-pick) — если фикс уже слит вdevи его нельзя выделить в отдельную ветку. В этом случае нужно создать ветку отmaster, найти коммит с фиксом и перенести его в новую ветку.
cherry-pick — это не слияние. Он переносит только одно изменение, а не всю историю ветки. Это значит, что если в dev были другие изменения, они не попадут в ветку от master. Но у cherry-pick есть своя цена: если код в dev уже успели переписать, то при переносе фикса может возникнуть конфликт.
Почему фикс обязан вернуться в dev и stage
Если после выкатки фикса в master забыть вернуть его в dev и stage, то через некоторое время кто-то может выкатить в продакшен старую версию кода, в которой баг ещё не исправлен. Это называется потерей фикса.
Как заметить, что фикс потерян? Самый простой способ — сравнить master с dev и stage. Если в dev или stage нет коммита с фиксом, значит, его нужно вернуть.
Что делать с конфликтом при возврате фикса в dev
Если при возврате фикса в dev возникает конфликт, это значит, что код в dev уже успели переписать. В этом случае нужно:
- Решить конфликт вручную. Это единственный способ гарантировать, что фикс не сломает другие изменения.
- Проверить, что фикс работает. Даже если конфликт решён, нужно убедиться, что фикс не нарушил работу других частей кода.
Конфликт при возврате фикса в dev — это нормально. Он возникает потому, что dev — это живая ветка, в которой постоянно идут изменения. Главное — не игнорировать его, а решать сразу.
Как понять, что схема ветвления не выдерживает нагрузки
Трёхветочная схема dev → stage → master работает хорошо, пока количество изменений в dev не превышает определённого порога. Этот порог зависит от размера команды, частоты релизов и сложности проекта.
Признаки того, что схему пора менять:
- Частые конфликты при слиянии. Если каждый раз при слиянии ветки в
devвозникает конфликт, значит, ветки слишком долго живут и расходятся слишком далеко. - Долгие выкатки. Если выкатка в
masterзанимает больше времени, чем обычно, значит, вstageлежит слишком много изменений, которые нужно проверять. - Потерянные фиксы. Если фиксы часто теряются при возврате в
dev, значит, схема не справляется с нагрузкой.
В этом случае стоит перейти на схему, где релизы делаются из одной ветки. Например, можно использовать GitHub Flow или GitLab Flow, где каждая фича разрабатывается в отдельной ветке, а релизы делаются из master.
Позиция редакции
Трёхветочная схема dev → stage → master — это хороший старт для небольших команд. Она простая, понятная и решает основные задачи: разделение разработки и продакшена, предрелизное тестирование, защита от случайных выкаток.
Но у неё есть и недостатки:
- Сложность синхронизации. Если в
devлежит много незавершённых изменений, то выкатка фикса вmasterтребует дополнительных шагов. - Риск потери фиксов. Если забыть вернуть фикс в
devиstage, то через некоторое время баг может вернуться. - Проблемы с масштабированием. Если количество изменений в
devпревышает определённый порог, то схема перестаёт справляться с нагрузкой.
Поэтому мы считаем, что трёхветочную схему стоит использовать, пока она справляется с нагрузкой. Как только начинаются частые конфликты, долгие выкатки или потеря фиксов, стоит перейти на более простую схему.
Условие, при котором позиция неверна:
Если команда маленькая, релизы редкие, а в dev лежит всего несколько изменений, то трёхветочная схема может быть избыточной. В этом случае можно обойтись без stage и выкатывать напрямую из dev в master, но только если все изменения в dev готовы к продакшену. Однако это требует строгой дисциплины и постоянного контроля за состоянием dev.
Что делать, если всё пошло не так
Если вы уже выкатили в master незрелый код из dev, то первое, что нужно сделать, — это откатить изменения. Это можно сделать с помощью git revert или git reset, в зависимости от ситуации.
Если фикс потерян и баг вернулся, то нужно найти коммит с фиксом и перенести его в dev и stage с помощью cherry-pick.
Если конфликт при возврате фикса в dev не решается, то стоит подумать о том, чтобы переписать фикс с нуля, учитывая новые изменения в dev.
Главное — не паниковать. Даже если что-то пошло не так, всегда можно откатить изменения и начать сначала.