Стратегии ветвления: Git Flow, GitHub Flow, trunk-based
Урок про то, как команда договаривается, какие ветки создавать, когда их сливать и когда выпускать релиз.
Стратегия ветвления — это согласованный набор правил о том, какие ветки живут в репозитории, как они называются, куда вливаются и в какой момент попадают в продакшен.
Зачем вообще нужна стратегия
Пока вы работаете в одиночку, ветки — просто удобство. Как только в репозитории несколько человек, отсутствие договорённостей превращает историю в кашу: кто-то держит фичу неделями, кто-то коммитит прямо в основную ветку, релиз собирают вручную «по памяти». Стратегия ветвления отвечает на три практических вопроса: где живёт код, готовый к релизу; как новая работа попадает туда; как чинят прод, когда он горит. Три популярных ответа — Git Flow, GitHub Flow и trunk-based development — различаются в основном тем, насколько долго живут ветки и как часто вы выкатываете.
Git Flow: много веток, явные релизы
Git Flow (модель Винсента Дриссена, 2010) держит две долгоживущие ветки: main (то, что в проде) и develop (накопитель следующего релиза). Вся работа идёт во временных ветках трёх типов.
- feature/* — отходят от
developи вливаются обратно вdevelop. - release/* — отходят от
develop, когда набралось достаточно фич; в них только стабилизация (фикс багов, бамп версии), затем вливаются и вmain, и обратно вdevelop. - hotfix/* — отходят от
main, чинят горящий прод, вливаются и вmain, и вdevelop.
git checkout develop
git checkout -b feature/oauth-login
# ... работаем, коммитим ...
git checkout develop
git merge --no-ff feature/oauth-login
# Стабилизируем релиз
git checkout -b release/1.4.0 develop
# правим версию, ловим баги
git checkout main && git merge --no-ff release/1.4.0
git tag -a v1.4.0 -m "Release 1.4.0"
git checkout develop && git merge --no-ff release/1.4.0
Флаг --no-ff заставляет Git создавать merge-коммит даже там, где возможна перемотка (fast-forward), — так в истории видны явные «пакеты» фич. Git Flow хорош для продуктов с явными нумерованными релизами: десктопные приложения, мобильные приложения с ревью в сторах, библиотеки, прошивки. Минус — тяжеловесность: две постоянные ветки, ветки-релизы, частые слияния туда-обратно. Для веб-сервиса, который катится по десять раз в день, это лишняя бюрократия.
GitHub Flow: одна основная ветка и Pull Request
GitHub Flow радикально проще: есть одна вечная ветка main, которая всегда деплоима. Любая работа — это короткоживущая ветка от main, оформленная как Pull Request; после ревью и зелёных проверок она вливается обратно и сразу (или почти сразу) выкатывается.
git checkout main && git pull
git checkout -b fix/cart-total-rounding
# правим, коммитим, пушим
git push -u origin fix/cart-total-rounding
# открываем Pull Request, проходим ревью и CI, мёржим, деплоим
Здесь нет develop и нет release-веток: релиз — это просто очередное слияние в main. Модель идеальна для веб-приложений с непрерывным деплоем. Цена простоты — дисциплина: main обязан всегда быть зелёным, поэтому без автотестов и защиты ветки (об этом отдельный урок) GitHub Flow быстро ломается.
Trunk-based development: ветки на часы, не на дни
Trunk-based доводит идею до предела: все коммитят в один «ствол» (main, он же trunk) либо напрямую, либо через ветки, которые живут часы, максимум день-два. Долгих feature-веток нет принципиально — это убирает «ад слияний», когда ветка отстаёт на сотни коммитов.
А как же незаконченные фичи?
Их прячут за feature-флагами: код вливается в trunk сразу, но включается флагом, когда будет готов. Так недописанная функция уже интегрирована, но не видна пользователю.
FEATURES = {"new_checkout": False}
def checkout(cart):
if FEATURES["new_checkout"]:
return new_flow(cart)
return legacy_flow(cart)
def new_flow(cart): return "new checkout: " + cart
def legacy_flow(cart): return "legacy checkout: " + cart
print(checkout("book"))
FEATURES["new_checkout"] = True
print(checkout("book"))
Вывод:
legacy checkout: book new checkout: book
Trunk-based — основа практик Google и многих CI/CD-команд. Требует зрелой автоматизации (быстрые тесты, надёжный CI) и культуры маленьких частых коммитов, зато даёт минимальные конфликты и мгновенную интеграцию. Логика тут контринтуитивная, но проверенная: чем чаще и мельче вы интегрируете, тем дешевле каждое слияние. Боль возникает не от частоты слияний, а от их размера — а размер прямо пропорционален тому, сколько ветка прожила в отрыве от ствола.
Как выбрать модель под команду
Практическое правило простое: отталкивайтесь от того, как часто вы реально выкатываете и насколько зрелы ваши тесты. Редкие нумерованные релизы и потребность вести несколько версий одновременно — это Git Flow. Веб-сервис с деплоем по нажатию кнопки и хорошим CI — GitHub Flow. Большая команда, которая хочет минимума конфликтов и готова вкладываться в автоматизацию и feature-флаги, — trunk-based. Размер команды тоже влияет: для двух-трёх человек тяжёлая церемония Git Flow избыточна, а для полусотни разработчиков без строгих правил любой репозиторий быстро превращается в хаос. Наконец, не бойтесь упрощать: команды чаще страдают от слишком сложного процесса, чем от слишком простого.
Как это работает под капотом
С точки зрения Git любая ветка — это просто двигающийся указатель на коммит (файл в .git/refs/heads/). Все три стратегии оперируют одним и тем же примитивом, разница только в политике: сколько указателей вы держите и как долго. «Долгоживущая ветка» расходится со стволом тем сильнее, чем дольше живёт, поэтому риск тяжёлого слияния растёт со временем — отсюда и стремление trunk-based сливать как можно чаще. Merge-коммит с --no-ff отличается от fast-forward тем, что имеет двух родителей и физически фиксирует точку интеграции; это решение о форме истории, а не о содержимом кода.
Частые ошибки
- Тянуть Git Flow в веб-сервис. Если вы деплоите по несколько раз в день, две постоянные ветки и release-ветки только мешают — берите GitHub Flow или trunk-based.
- «Бессмертные» feature-ветки. Ветка, которая живёт месяц, гарантирует болезненное слияние и потерю контекста. Дробите задачу.
- GitHub Flow без тестов и защиты. Раз
mainвсегда деплоится, один непроверенный мёрж кладёт прод. Без CI и branch protection эта модель опасна. - Trunk-based без feature-флагов. Вливать в ствол полусырую фичу без флага — значит ломать всем сборку.
- Хотфикс мимо процесса. В Git Flow забыли влить hotfix обратно в
develop— и в следующем релизе баг возвращается.
Итоги
- Стратегия ветвления — это договорённость о форме работы, а не фича Git.
- Git Flow: две постоянные ветки + feature/release/hotfix; для продуктов с явными нумерованными релизами.
- GitHub Flow: одна деплоимая
main+ короткие PR-ветки; для непрерывного деплоя. - Trunk-based: ветки на часы + feature-флаги; минимум конфликтов, но требует зрелого CI.
- Чем чаще релизы, тем короче должны жить ветки.