Стратегии ветвления
Одна ветка на всех — просто, но рано или поздно ломает прод: половина сделанной фичи улетает в релиз вместе с багфиксом, хотфикс в 3 часа ночи непонятно откуда бранчевать, а git log main превращается в кашу из сырого и готового кода. Стратегия ветвления — это договорённость команды о том, какие ветки существуют, откуда они растут и куда вливаются. Это не настройка Git — Git одинаков во всех стратегиях. Это настройка людей и процессов.
В краткой версии ты видел ветки как инструмент изоляции. Здесь — три классические стратегии целиком: как двигаются ветки, где их границы, сколько это стоит по накладным расходам и что выбрать для pet-проекта и для продуктовой команды. Плюс больная тема environment-веток и их современная альтернатива — feature flags.
Git Flow: полный церемониал
Заголовок раздела «Git Flow: полный церемониал»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.0hotfix/1.2.1 ──●──► в main (тег 1.2.1) + в developРазберём жизненный цикл по шагам:
# 1. Начинаем фичуgit switch developgit switch -c feature/payment-form
# 2. Работаем, вливаем обратно (--no-ff оставляет merge-коммит — граница фичи видна)git switch developgit merge --no-ff feature/payment-formgit 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 под тегом и обратно в developgit switch maingit merge --no-ff release/1.3.0git tag -a v1.3.0 -m "Release 1.3.0"git switch developgit merge --no-ff release/1.3.0
# 5. Прод упал в 3 часа ночи — hotfix от maingit switch -c hotfix/1.3.1 main# фикс, тест, затем:git switch main && git merge --no-ff hotfix/1.3.1 && git tag -a v1.3.1git 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: main + фича-ветки
Заголовок раздела «GitHub Flow: main + фича-ветки»Экстремальное упрощение: одна постоянная ветка и короткоживущие фича-ветки (описание процесса — GitHub Flow). Придумана в GitHub для веб-проектов с непрерывным деплоем.
ВРЕМЯ ─────────────────────────────────────────────►
main ●────●────────●──────●─────────●──────────●───► \ / \ / \ / \ / \ / \ / \ / \ / \ / ● \/ \ /feature/A ───────●──●───── /\ \ / / \ ●feature/B ─────────────●──●──────●──●\ / \ /feature/C ────────────────●──●──●──────●─►Правила простые:
git switch maingit pull # main всегда деплоируемаgit switch -c feature/search-suggestions# ... коммиты, push, PR, ревью, CI ...# в main мержим squash'ем или rebase'омgit switch main && git pullgit branch -d feature/search-suggestionsВсё. Нет release-веток, нет develop, нет hotfix-веток (хотфикс — просто срочная фича-ветка от main). Ключевое требование — main всегда в состоянии «можно деплоить»: либо за счёт feature flags, либо за счёт того, что недоделанные вещи вообще не мержатся.
Когда защита включена, GitHub Flow масштабируется до средних команд — это дефолт большинства продуктовых команд сейчас.
Trunk-Based Development: все в main
Заголовок раздела «Trunk-Based Development: все в main»Самая радикальная стратегия: все коммитят в одну ветку (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-refactorgit push && git branch -d short-refactorКак не сломать прод недоделанным кодом? Три кита TBD:
- Feature flags (паттерны — в статье Martin Fowler «Feature Toggles») — недоделанный код в main, но выключен в рантайме:
if (flags.isEnabled('new-checkout')) { return <NewCheckout />; // недоделанное, но уже в main}return <OldCheckout />;- Branching by abstraction (паттерн — Branch by Abstraction) — длинные рефакторинги без ветки: вводишь интерфейс, переводишь старый код на него, рядом пишешь новую реализацию, переключаешь флагом, старое удаляешь. История остаётся линейной, а рефакторинг идёт маленькими безопасными коммитами.
- Экстремально быстрый 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-ветки vs feature flags
Заголовок раздела «Environment-ветки vs feature flags»Старая и до сих пор встречающаяся практика — 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 с флагами и отдельным аудитом конфигурации.
Типичные ошибки и грабли
Заголовок раздела «Типичные ошибки и грабли»-
Git Flow для проекта с еженедельным деплоем. Двойная бюрократия main/develop/release не окупается: release-ветки пустеют, develop отстаёт от main, команды путаются, куда мержить. Лечится переходом на GitHub Flow.
-
Фича-ветки, живущие месяцами. Даже в GitHub Flow ветка «почти готова» три недели стоимостью даёт конфликты на каждый rebase и страховой PR на 2000 строк. Лечится: разбивкой фичи на этапы за флагами, ежедневным ребейзом на main.
-
«main священна» без автоматической защиты. Договорённость «не пушим в main напрямую» без branch protection и CI держится на честном слове. Одна команда в новом проекте — и договорённости нет. Включай защиту на сервере с первого дня.
-
Environment-ветки как «простое решение». Три долгоживущие ветки + «продвижение» мержами выглядят проще флагов первые две недели. На третьей неделе начинаются конфликты мержа dev→stage и релизы, сдвинутые на день из-за разруливания. Флаги + одна main почти всегда дешевле.
-
Rebase веток, которыми делятся, в Git Flow. В Git Flow границы фич — merge-коммиты; ребейз feature-ветки перед вливом в develop ломает картину. Ребейз — инструмент GitHub Flow/TBD с короткими ветками и squash-мержем.
Вопросы на собеседовании
Заголовок раздела «Вопросы на собеседовании»-
Расскажи про Git Flow: какие ветки и зачем? main (релизы с тегами), develop (интеграция), feature/* (от develop в develop), release/* (от develop в main+develop, только стабилизация), hotfix/* (от main в main+develop). Для редких формальных релизов.
-
Чем GitHub Flow отличается от Git Flow? Одна постоянная ветка вместо двух, нет release/hotfix-веток: фича-ветки от main короткие, деплой прямо из main, которую держат всегда деплоируемой. Для веб-проектов с частыми релизами.
-
Что такое Trunk-Based Development и что ему нужно? Все коммитят в одну ветку, фича-ветки живут ≤ 1–2 дней или отсутствуют. Требования: быстрый CI, feature flags для недоделанного кода, развёртывание по коммитам, зрелая инженерная культура.
-
Feature flags или environment-ветки? Флаги: одна сборка, окружения — конфигурация, нет мерж-коммитов между env. Environment-ветки: разные коммиты на окружения, дорогие продвижения и конфликты. Флаги — почти всегда лучше при частых релизах.
-
Как выбрать стратегию для команды из 5 человек с деплоем раз в неделю? GitHub Flow: защита main, короткие фича-ветки, squash/rebase merge, теги на релизах. Git Flow избыточен, TBD не окупит инвестиции в CI и флаги.
-
Почему в Git Flow hotfix вливается и в main, и в develop? Прод чинится тегом на main, но фикс должен попасть и в будущие релизы — иначе следующий релиз из develop «откатит» хотфикс. Забытый мерж develop←hotfix — классический источник регрессий.
Практика
Заголовок раздела «Практика»- Нарисуй (на бумаге или в mermaid/тексте) жизненный цикл одной фичи в Git Flow: от задачи до тега на main. Критерий: в схеме видны develop, feature-ветка, release-ветка, merge в обе стороны и тег.
- В своём репозитории включи защиту main (branch protection: запрет push, требование PR). Сделай коммит в фича-ветке и пронеси его через PR с squash merge. Критерий: прямой push в main отклонён, PR схлопнут в один коммит.
- Сэмулируй hotfix в GitHub Flow: сделай коммит в main с сообщением «docs», затем от ветки
hotfix/typoисправь его и влей через PR. Критерий: история main линейная (rebase merge), фикс — один коммит. - Добавь в pet-проект feature flag на уровне env-переменной:
if (process.env.ENABLE_X === '1'). Покажи, что одна и та же сборка ведёт себя по-разному при разных значениях. Критерий: поведение меняется без пересборки. - Проанализируй свой текущий рабочий проект: сколько живёт самая старая открытая ветка? Сколько merge-коммитов между environment-ветками за месяц? Сформулируй, какая стратегия там реально используется и подходит ли она релизному циклу.