Перейти к содержимому

Стратегии ветвления

Одна ветка на всех — просто, но рано или поздно ломает прод: половина сделанной фичи улетает в релиз вместе с багфиксом, хотфикс в 3 часа ночи непонятно откуда бранчевать, а git log main превращается в кашу из сырого и готового кода. Стратегия ветвления — это договорённость команды о том, какие ветки существуют, откуда они растут и куда вливаются. Это не настройка Git — Git одинаков во всех стратегиях. Это настройка людей и процессов.

В краткой версии ты видел ветки как инструмент изоляции. Здесь — три классические стратегии целиком: как двигаются ветки, где их границы, сколько это стоит по накладным расходам и что выбрать для pet-проекта и для продуктовой команды. Плюс больная тема environment-веток и их современная альтернатива — feature flags.

Git Flow — самая старая и самая «тяжёлая» стратегия (первоисточник — статья Vincent Driessen «A successful Git branching model», 2010). Она родилась во времена, когда релизы выходили раз в месяц и требовали регламентированного процесса.

Пять типов веток:

Ветка Живёт Растёт от Вливается в Назначение
main постоянно только релизы, каждый коммит — тег
develop постоянно main main интеграция фич, «следующий релиз»
feature/* дни–недели develop develop одна фича/задача
release/* недели develop main + develop стабилизация: только багфиксы, без фич
hotfix/* часы–дни main main + develop срочный фикс продакшена

Как они двигаются на временной шкале:

ВРЕМЯ ──────────────────────────────────────────────────────────►
main ●─────●──────────────────●───────────────●──────────►
│ тег тег тег
│ \ / \ / \
develop ●──────●──●──●──●──●──●──●──●──●──●──●──●───────────►
/ / / \ \
/ / / \ \
feature/alpha ─●──●──● \ \
\ \
feature/beta ────────●──●──●
(релиз отрезали)
release/1.2 ──●──●──●──► в main (тег 1.2.0)
+ в develop (багфиксы релиза)
┌── здесь прод в продакшене: v1.2.0
hotfix/1.2.1 ──●──► в main (тег 1.2.1)
+ в develop

Разберём жизненный цикл по шагам:

Окно терминала
# 1. Начинаем фичу
git switch develop
git switch -c feature/payment-form
# 2. Работаем, вливаем обратно (--no-ff оставляет merge-коммит — граница фичи видна)
git switch develop
git merge --no-ff feature/payment-form
git branch -d feature/payment-form
# 3. Набрали фич на релиз — режем release-ветку и замораживаем фичи
git switch -c release/1.3.0 develop
# только багфиксы: git commit -m "fix: validation on empty card"
# 4. Релиз готов — в main под тегом и обратно в develop
git switch main
git merge --no-ff release/1.3.0
git tag -a v1.3.0 -m "Release 1.3.0"
git switch develop
git merge --no-ff release/1.3.0
# 5. Прод упал в 3 часа ночи — hotfix от main
git switch -c hotfix/1.3.1 main
# фикс, тест, затем:
git switch main && git merge --no-ff hotfix/1.3.1 && git tag -a v1.3.1
git switch develop && git merge --no-ff hotfix/1.3.1

Ключевые свойства Git Flow:

  • main священна: каждый коммит там — задеплоенный релиз с тегом. Откат = git checkout на предыдущий тег.
  • develop — буфер: фичи интегрируются туда, а в main попадают только через release-ветку после стабилизации. Важный нюанс: develop обычно не деплоится в прод — он для интеграционных окружений (dev/stage), и его состояние «почти готово, но не проверено» допустимо.
  • merge-коммиты обязательны (--no-ff): видна граница каждой фичи и каждого релиза, что критично при расследовании инцидентов.

Стоимость: две постоянные ветки, три типа временных, дисциплина, которую нужно поддерживать код-ревью и CI. Git Flow хорош там, где релизы редкие и дорогие: десктопные приложения, встраиваемое ПО, корпоративные системы с фиксированными окнами обновления.

Экстремальное упрощение: одна постоянная ветка и короткоживущие фича-ветки (описание процесса — GitHub Flow). Придумана в GitHub для веб-проектов с непрерывным деплоем.

ВРЕМЯ ─────────────────────────────────────────────►
main ●────●────────●──────●─────────●──────────●───►
\ / \ / \ / \ /
\ / \ / \ / \ /
\ / ● \/ \ /
feature/A ───────●──●───── /\ \ /
/ \ ●
feature/B ─────────────●──●──────●──●\ /
\ /
feature/C ────────────────●──●──●──────●─►

Правила простые:

Окно терминала
git switch main
git pull # main всегда деплоируема
git switch -c feature/search-suggestions
# ... коммиты, push, PR, ревью, CI ...
# в main мержим squash'ем или rebase'ом
git switch main && git pull
git branch -d feature/search-suggestions

Всё. Нет release-веток, нет develop, нет hotfix-веток (хотфикс — просто срочная фича-ветка от main). Ключевое требование — main всегда в состоянии «можно деплоить»: либо за счёт feature flags, либо за счёт того, что недоделанные вещи вообще не мержатся.

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

Самая радикальная стратегия: все коммитят в одну ветку (trunk), фича-ветки либо отсутствуют, либо живут меньше 1–2 дней (подробно — trunkbaseddevelopment.com).

ВРЕМЯ ─────────────────────────────────────────────►
main ●──●──●──●──●──●──●──●──●──●──●──●──●──●──►
▲ ▲ ▲ ▲
│ │ │ │
короткие ●──┘ ●──●───┘ ●──●──●─┘
ветки (≤2 дня) WIP-флаг WIP-флаг WIP-флаг

Механика:

Окно терминала
git switch main && git pull
# маленькая правка — сразу в main (коммит за секунды, push сразу)
git commit -am "feat: add tooltip to pricing (behind flag)"
git push
# правка побольше — короткая ветка на один день
git switch -c short-refactor main
# ...несколько коммитов за день...
git switch main && git merge --squash short-refactor
git push && git branch -d short-refactor

Как не сломать прод недоделанным кодом? Три кита TBD:

  1. Feature flags (паттерны — в статье Martin Fowler «Feature Toggles») — недоделанный код в main, но выключен в рантайме:
if (flags.isEnabled('new-checkout')) {
return <NewCheckout />; // недоделанное, но уже в main
}
return <OldCheckout />;
  1. Branching by abstraction (паттерн — Branch by Abstraction) — длинные рефакторинги без ветки: вводишь интерфейс, переводишь старый код на него, рядом пишешь новую реализацию, переключаешь флагом, старое удаляешь. История остаётся линейной, а рефакторинг идёт маленькими безопасными коммитами.
  2. Экстремально быстрый CI (10–15 минут максимум): если проверки идут час, никто не будет интегрироваться каждые пару часов.

TBD — стратегия Google, Meta, многих высоконагруженных команд. Она требует зрелой инженерной культуры, но даёт минимальный merge-головняк: интеграция происходит каждые часы, конфликты микроскопические.

Критерий Git Flow GitHub Flow Trunk-Based
Постоянных веток 2 (main + develop) 1 (main) 1 (main/trunk)
Жизнь фича-ветки дни–недели 1–3 дня часы–1–2 дня
Релизный цикл релизы из веток, недели деплой из main, дни деплой каждый коммит
Размер команды средние/большие, релиз-менеджер средние (2–15) большие, зрелые команды
Накладные расходы высокие (ритуалы, release-окна) низкие минимальные, но требует CI-культуры
Риск поломки main низкий (main защищена релизами) средний (защита CI + ревью) высокий без флагов, низкий с ними
Главный риск «релизная ветка живёт вечно» main перестаёт быть деплоируемой сломанный CI останавливает всех
Когда выбирать десктоп, редкие релизы веб/SaaS, регулярные деплои микросервисы, много инженеров

Что выбрать: pet-проект и продуктовая команда

Заголовок раздела «Что выбрать: pet-проект и продуктовая команда»

Pet-проект. Тебе не нужны ритуалы. Оптимум — GitHub Flow в облегчённом виде: работаешь в ветке feature/..., по готовности — PR к самому себе (да, PR в свой репозиторий — нормальная практика: заставляет посмотреть дифф глазами «чужого»), merge, deploy. Теги ставь только когда реально выпускаешь версию. Если проект настолько мал, что даже ветки избыточны — коммить в main, но с осмысленными сообщениями и тегами на релизах.

pet-проект (один разработчик):
main ──●──●──●────────●──────────●──►
\ feature/ тег \
●──●──● (PR к себе) v1.0 \
●──● feature/next

Продуктовая команда. Дефолт 2020-х — GitHub Flow + защита main. Но решай от релизного цикла:

РЕШЕНИЕ О СТРАТЕГИИ:
"Релизы раз в 1–3 месяца, нужен стабилизационный период"?
└── ДА → Git Flow (или хотя бы release-ветки поверх GitHub Flow)
└── НЕТ → "Деплоим несколько раз в неделю и чаще"?
├── НЕТ → GitHub Flow
└── ДА → "Инженеров > 20, CI < 15 мин, есть культура флагов"?
├── ДА → Trunk-Based
└── НЕТ → GitHub Flow с дисциплиной мелких PR

Переход между стратегиями — это смена договорённостей и автоматики, а не Git-команд. Начни с GitHub Flow, наращивай защиту main; TBD придёт сам, когда PR начнут мешать.

Старая и до сих пор встречающаяся практика — environment-ветки (разбор паттернов ветвления — в статье Martin Fowler «Patterns for Managing Source Code Branches»): отдельные долгоживущие ветки dev, stage, prod, и код «продвигается» мержем по цепочке:

Environment-ветки (антипаттерн для частых релизов):
feature ──► dev ──► stage ──► prod ──► релиз
│ │ │
▼ ▼ ▼
dev-env stage-env prod-env
БОЛЬ: мерж dev→stage→prod — это merge-коммиты.
Разошлись конфликты? "Релизное окно" съедено ручным разруливанием.
Где баг: в dev или в мерже stage? Хрен знает.

Проблемы: код «переплывает» тремя волнами мержей, и каждая — источник конфликтов и багов; история трёх веток расходится; «поднять только багфикс в prod, не поднимая половину dev» превращается в хирургию cherry-pick’ов.

Современная альтернатива — одна main + feature flags + развёртывание по коммитам:

Одна main + флаги:
main ──●──●──●──●──●──●──●──●──►
│ │ │ │
▼ ▼ ▼ ▼
┌──── флаги в рантайме ────┐
│ flagA │ flagB │ flagC │ │
└──► dev: A=0 B=0 C=0 │ одна и та же СБОРКА,
stage: A=1 B=0 C=0 │ разные конфигурации
prod: A=1 B=1 C=0 │

Environment теперь — это конфигурация одной сборки, а не разные коммиты. Инструменты: LaunchDarkly, Unleash, флажки в конфиге, простые env-переменные для мелочи.

Когда environment-ветки всё же допустимы: жёстко регулируемые отрасли (банки, медицина), где prod-код должен физически отличаться и проходить отдельный аудит. Но и там всё чаще делают одну main с флагами и отдельным аудитом конфигурации.

  1. Git Flow для проекта с еженедельным деплоем. Двойная бюрократия main/develop/release не окупается: release-ветки пустеют, develop отстаёт от main, команды путаются, куда мержить. Лечится переходом на GitHub Flow.

  2. Фича-ветки, живущие месяцами. Даже в GitHub Flow ветка «почти готова» три недели стоимостью даёт конфликты на каждый rebase и страховой PR на 2000 строк. Лечится: разбивкой фичи на этапы за флагами, ежедневным ребейзом на main.

  3. «main священна» без автоматической защиты. Договорённость «не пушим в main напрямую» без branch protection и CI держится на честном слове. Одна команда в новом проекте — и договорённости нет. Включай защиту на сервере с первого дня.

  4. Environment-ветки как «простое решение». Три долгоживущие ветки + «продвижение» мержами выглядят проще флагов первые две недели. На третьей неделе начинаются конфликты мержа dev→stage и релизы, сдвинутые на день из-за разруливания. Флаги + одна main почти всегда дешевле.

  5. Rebase веток, которыми делятся, в Git Flow. В Git Flow границы фич — merge-коммиты; ребейз feature-ветки перед вливом в develop ломает картину. Ребейз — инструмент GitHub Flow/TBD с короткими ветками и squash-мержем.

  1. Расскажи про Git Flow: какие ветки и зачем? main (релизы с тегами), develop (интеграция), feature/* (от develop в develop), release/* (от develop в main+develop, только стабилизация), hotfix/* (от main в main+develop). Для редких формальных релизов.

  2. Чем GitHub Flow отличается от Git Flow? Одна постоянная ветка вместо двух, нет release/hotfix-веток: фича-ветки от main короткие, деплой прямо из main, которую держат всегда деплоируемой. Для веб-проектов с частыми релизами.

  3. Что такое Trunk-Based Development и что ему нужно? Все коммитят в одну ветку, фича-ветки живут ≤ 1–2 дней или отсутствуют. Требования: быстрый CI, feature flags для недоделанного кода, развёртывание по коммитам, зрелая инженерная культура.

  4. Feature flags или environment-ветки? Флаги: одна сборка, окружения — конфигурация, нет мерж-коммитов между env. Environment-ветки: разные коммиты на окружения, дорогие продвижения и конфликты. Флаги — почти всегда лучше при частых релизах.

  5. Как выбрать стратегию для команды из 5 человек с деплоем раз в неделю? GitHub Flow: защита main, короткие фича-ветки, squash/rebase merge, теги на релизах. Git Flow избыточен, TBD не окупит инвестиции в CI и флаги.

  6. Почему в Git Flow hotfix вливается и в main, и в develop? Прод чинится тегом на main, но фикс должен попасть и в будущие релизы — иначе следующий релиз из develop «откатит» хотфикс. Забытый мерж develop←hotfix — классический источник регрессий.

  1. Нарисуй (на бумаге или в mermaid/тексте) жизненный цикл одной фичи в Git Flow: от задачи до тега на main. Критерий: в схеме видны develop, feature-ветка, release-ветка, merge в обе стороны и тег.
  2. В своём репозитории включи защиту main (branch protection: запрет push, требование PR). Сделай коммит в фича-ветке и пронеси его через PR с squash merge. Критерий: прямой push в main отклонён, PR схлопнут в один коммит.
  3. Сэмулируй hotfix в GitHub Flow: сделай коммит в main с сообщением «docs», затем от ветки hotfix/typo исправь его и влей через PR. Критерий: история main линейная (rebase merge), фикс — один коммит.
  4. Добавь в pet-проект feature flag на уровне env-переменной: if (process.env.ENABLE_X === '1'). Покажи, что одна и та же сборка ведёт себя по-разному при разных значениях. Критерий: поведение меняется без пересборки.
  5. Проанализируй свой текущий рабочий проект: сколько живёт самая старая открытая ветка? Сколько merge-коммитов между environment-ветками за месяц? Сформулируй, какая стратегия там реально используется и подходит ли она релизному циклу.