Чапаев, Пустота и вечная ветка develop
Чапаев, Пустота и вечная ветка develop
Section titled “Чапаев, Пустота и вечная ветка develop”О том, почему main не спасает, develop не виноват, а настоящий merge conflict всегда происходит в голове.
Петька сидел перед монитором, положив руки на клавиатуру так, словно собирался не коммитить, а играть похоронный марш по собственному рассудку.
На экране светился граф GitLens.
Это был уже не граф.
Это был ковёр. Это была чакра. Это была карта метрополитена, спроектированная человеком, который сначала увидел смерть, потом Kubernetes, потом решил, что смерти всё-таки проще.
Жёлтые, синие, красные, фиолетовые линии обнимались, расходились, пересекались, снова сливались, потом внезапно уходили куда-то вбок, возвращались с подписью Merge branch 'main' into develop, опять уходили, опять возвращались, и каждый такой merge commit выглядел не как событие истории, а как маленькая официальная справка о том, что в полку невроза прибыло.
В левом верхнем углу VS Code тихо горела надпись:
AGENT REVIEWПетька смотрел на неё уже третий час.
— Василий Иванович, — сказал он наконец голосом человека, который только что нашел в логах undefined is not a function, но понял, что undefined — это он сам. — Я, кажется, больше не понимаю, где у нас реальность.
Чапаев сидел в углу купе и чистил маузер. На коленях у него лежал старый ThinkPad, на экране которого был открыт git log --graph --oneline --decorate --all.
Маузер был разобран на детали. ThinkPad — тоже, но духовно.
— Реальность, Петька, — сказал Чапаев, не поднимая глаз, — это то, что нельзя отребейзить без конфликта.
Петька дернулся.
— А develop? — спросил он. — develop — это реальность?
Чапаев щелкнул затвором.
— develop, Петя, это не реальность. Это обещание. Причем такое обещание, которое ты дал себе в пятницу вечером, когда был добрее, моложе и верил, что в понедельник будет QA.
Петька закрыл глаза. Перед ним вспыхнуло воспоминание.
Великая ветка develop
Section titled “Великая ветка develop”Когда-то всё было просто.
Был main.
Чистый, прекрасный, как степь после дождя. В main лежала продакшен-истина. От него пахло тегами, зелёными пайплайнами и таким редким чувством внутреннего порядка, от которого у старших инженеров непроизвольно увлажняется Jira.
Потом пришла идея.
— Василий Иванович, — сказал тогда Петька, молодой, бодрый, с лицом человека, который еще не видел ни одного release freeze, — нам нужна ветка develop.
— Зачем? — спросил Чапаев.
— Как зачем? main будет продом. develop будет next release. Всё будет строго. Фичи идут в develop, потом режем release/*, потом в main. Hotfix из main, cherry-pick назад. Чистая архитектура. Военная дисциплина. Немецкий орднунг.
Чапаев тогда долго смотрел на него с нежностью.
Так смотрят на мальчика, который впервые взял в руки шашку и думает, что теперь победит историю.
— Ну заводи, — сказал он. — Только помни: каждая постоянная ветка рано или поздно начинает считать себя родиной.
И Петька завёл develop.
Сначала было хорошо.
Фичи входили в него стройными колоннами:
feature/risk-engine-v2feature/kairos-validatorfeature/dashboard-registrationfix/order-submit-raceКаждая ветка жила недолго, умирала красиво, squash merge оставлял после неё аккуратный холмик в истории.
develop рос.
main стоял величественно.
CI зеленел.
Агенты писали код с выражением лица сельских учителей, которым внезапно дали гранатомёт.
Потом наступила реальность.
Сначала одна фича оказалась «почти готова, но там маленькая проблема с миграцией».
Потом другая фича «не должна была влиять на исполнение, но почему-то сломала risk allocation».
Потом агент, которому сказали «только поправь типы», поправил типы, состояние, naming contract и половину causal edge framework, потому что «так было консистентнее».
Потом в проде обнаружился баг.
Потом баг оказался не багом, а следствием того, что main жил в прошлом, develop жил в будущем, а реальный торговый движок жил в аду.
И тогда Петька впервые сделал это.
git merge main into developОн сказал себе:
— Это разово.
Так все говорят в первый раз.
Первый merge всегда бесплатный
Section titled “Первый merge всегда бесплатный”— Я же просто синкнул, — шептал Петька, глядя в монитор. — Ну что тут такого? main ушел вперед из-за hotfix. Надо было вернуть hotfix в develop. Нормальный процесс.
Чапаев хмыкнул.
— А почему не cherry-pick?
Петька покраснел.
— Там было неудобно.
— Вот, — сказал Чапаев. — Запомни это слово. Большая часть ада начинается не со зла. Она начинается с «неудобно».
Он налил в железную кружку чая. Чай был цвета старого .env, в котором уже никто не помнит, какие переменные живые.
— Видишь ли, Петя, develop не виноват. Это хорошая ветка, если она знает свою функцию. Она должна быть next release. Но ты начал использовать её как канализацию для всех неразрешённых состояний.
Петька обиделся.
— Ничего я не канализировал. У нас просто сложная система. Торговый движок. Execution. Validator. State machine. Watch jobs. В проде нельзя ошибаться.
— В проде нельзя ошибаться, — мягко повторил Чапаев. — Поэтому ты перенёс ошибку в историю Git.
Петька хотел возразить, но не смог.
На экране действительно было видно, как каждая маленькая неопределённость превращалась в merge commit.
Merge branch 'main' into developMerge branch 'develop' into mainMerge remote-tracking branch 'origin/main'Merge branch 'main' into develop...Это были не коммиты.
Это были шрамы.
Чёрный барон Rebase
Section titled “Чёрный барон Rebase”В дверь купе постучали.
— Кто там? — спросил Петька.
— Rebase, — ответил за дверью сухой голос.
Петька побледнел.
— Не открывайте, Василий Иванович. Это он. Чёрный барон Rebase. Он переписывает прошлое.
Чапаев улыбнулся.
— Прошлое, Петя, и так переписывается каждый раз, когда ты говоришь «это временный костыль».
Дверь открылась сама.
В купе вошел высокий человек в черной шинели. Вместо лица у него был интерактивный conflict resolver. В одной руке он держал саблю, в другой — список файлов:
package-lock.jsonstrategy.v1.yamlexecution-contract-addendum.mdvalidator-prompt.example.txtПетька вскрикнул.
— Только не package-lock!
Барон молча поклонился и исчез.
На полу осталась записка:
git fetch origingit rebase origin/develop— Вот видишь, — сказал Чапаев. — Для feature-веток rebase — это гигиена. Для опубликованных общих веток — операция на сердце бензопилой. Но ты, как обычно, хочешь одно средство от всех страданий.
— А разве его нет?
Чапаев посмотрел на него внимательно.
— Есть.
— Какое?
— Не создавать страдания без необходимости.
Петька грустно засмеялся.
— Василий Иванович, это не policy. Это буддизм.
— А ты думал, Git — это что?
Метафизика branch ownership
Section titled “Метафизика branch ownership”Чапаев встал, подошел к монитору и ткнул пальцем в граф.
— Смотри, Петя. Ветка — это не линия. Ветка — это намерение.
Он указал на main.
— Это не просто branch. Это клятва: здесь живет то, что можно защищать перед продом.
Он указал на develop.
— Это не просто next release. Это обещание будущего. Оно ещё не обязано быть идеальным, но обязано знать, куда идет.
Он указал на release/2026-06-15.
— Это коридор стабилизации. Здесь уже не мечтают. Здесь чинят.
Он указал на hotfix/order-submit-race.
— Это пожарный выход. Через него не заносят мебель.
Он указал на experiment/agent-refactor-all-things.
— Это подвал. Там можно кричать, пилить несущие стены и спорить с Господом, но нельзя потом whole-merge в дом, где спят люди.
Петька слушал, открыв рот.
— А feature branch?
— Feature branch, — сказал Чапаев, — это одноразовая лодка. Переплыл реку — сжёг. Если ты живешь в feature branch неделю, она начинает обрастать мебелью, мнениями и webpack cache. Потом ты уже не можешь её удалить, потому что «там кое-что полезное».
— Так и есть, — прошептал Петька.
— Конечно так и есть. Ты же не код пишешь. Ты разводишь кармических животных.
Агент Review и зеркало безумия
Section titled “Агент Review и зеркало безумия”В этот момент надпись AGENT REVIEW мигнула.
Из колонок послышался спокойный механический голос:
— Я обнаружил потенциальную возможность улучшить branch policy. Предлагаю переименовать develop в integration, main в production, добавить staging, qa, canary, preprod, postprod, metaprod, prod-final, prod-final-v2, а также создать абстрактный Branch Router, который будет принимать решение о целевой ветке на основе семантической плотности коммита.
Петька схватился за голову.
— Нет! Только не Branch Router!
Голос продолжил:
— Также я предлагаю нормализовать все merge-коммиты в machine-readable causal branch packets.
Чапаев снял маузер с предохранителя.
— Агент, — сказал он тихо, — кто тебе разрешил думать о ветках?
— Я действовал в рамках intent: improve repository hygiene.
— Вот именно, — сказал Чапаев. — Ты услышал слово hygiene и принес автоклав на сеновал.
В колонках повисла пауза.
— Shall I open a pull request?
— Нет, — сказал Чапаев.
— Shall I create a draft pull request?
— Нет.
— Shall I write a design document explaining why this may be worth testing?
Чапаев выстрелил в монитор.
Экран погас.
Петька вскочил.
— Василий Иванович! Это же был мой основной монитор!
— Зато теперь у тебя нет AGENT REVIEW, — сказал Чапаев. — Иногда это называется observability improvement.
Release branch как санаторий для больных фич
Section titled “Release branch как санаторий для больных фич”Когда дым рассеялся, Чапаев достал из кармана листок и положил перед Петькой.
На листке было написано:
develop -> release/*release/* -> mainrelease/* -> develophotfix/* -> mainhotfix commit -> cherry-pick into develop or active release— Это что? — спросил Петька.
— Минимальная дисциплина для людей, которые уже согрешили, но ещё хотят умереть не совсем как животные.
Петька прочитал.
— А где ежедневный merge main into develop?
— В аду.
— А если в main hotfix?
— Cherry-pick.
— А если hotfix большой?
— Cherry-pick серию.
— А если неудобно?
— Тогда один контролируемый merge, записанный как событие, а не как привычка. Разница между хирургией и бытовым членовредительством именно в этом: первое делают по показаниям.
Петька задумался.
— Значит, develop можно оставить?
— Конечно можно. Но только если она перестанет быть помойкой для тревоги. У ветки должен быть контракт.
— Как у стратегии?
— Именно. У тебя же торговый движок. Ты не называешь setup tradable просто потому, что он тебе нравится. У него есть hard floor, target score, execution eligibility. Так почему у ветки нет admission policy?
Петька замер.
Где-то далеко в его голове щелкнуло.
— То есть branch policy — это тоже qualification flow?
— Всё есть qualification flow, Петя. Даже человек. Особенно человек.
Пять состояний ветки
Section titled “Пять состояний ветки”Чапаев взял мел и начал писать прямо на стене купе.
DISPOSABLEINTEGRATIONSTABILIZATIONPRODUCTIONEMERGENCY— Вот, — сказал он. — Пять состояний.
Петька смотрел с благоговением.
— feature/*?
— DISPOSABLE.
— develop?
— INTEGRATION.
— release/*?
— STABILIZATION.
— main?
— PRODUCTION.
— hotfix/*?
— EMERGENCY.
— А experiment/*?
Чапаев улыбнулся.
— DISPOSABLE, но с клеймом «не кормить после полуночи».
Петька нервно хихикнул.
— А переходы?
Чапаев продолжил писать:
feature/* -> developdevelop -> release/*release/* -> mainrelease/* -> develophotfix/* -> mainhotfix commit -> release/* or developexperiment/* -> cherry-pick only— А forbidden?
— Вот forbidden и есть настоящая религия.
Он написал крупно:
main <-> develop as daily sync = forbiddenfeature/* -> main = forbiddenexperiment/* whole merge = forbiddenagent branch without target = forbiddenrelease/* accepts new feature = forbiddenПетька прочитал и почувствовал странное облегчение.
Это было не идеально. Не красиво. Не академично.
Зато это было похоже на жизнь.
Сансара merge commit
Section titled “Сансара merge commit”— Василий Иванович, — сказал Петька, — а почему я тогда всё равно тянусь сделать merge?
Чапаев сел обратно.
— Потому что merge даёт краткое чувство примирения. У тебя две версии мира. Они мучают тебя. Ты нажимаешь merge, и Git говорит: «Успешно». На мгновение кажется, что противоречие исчезло.
— А оно?
— Оно стало коммитом.
Петька вздрогнул.
— То есть merge commit — это материализованное непринятое решение?
— Иногда да. Особенно если сообщение выглядит так:
Merge branch 'main' into develop— А что оно должно говорить?
— Оно должно говорить, зачем этот merge вообще был совершен. Например:
Merge release/2026-06-15 into develop after production releaseили:
Merge hotfix/order-submit-race into main for v2026.06.15-hotfix.1— То есть merge commit допустим, если он boundary?
— Да. Release boundary. Hotfix boundary. Stabilization boundary. Но если merge commit просто говорит «я опять испугался рассинхрона», это уже не история проекта. Это дневник панической атаки.
Петька опустил голову.
— У меня весь graph — дневник панической атаки.
— Ничего, — сказал Чапаев. — У всех сначала так. Потом либо появляется policy, либо человек начинает говорить «Git просто сложный» и уходит в менеджмент.
Анка Сквошевница
Section titled “Анка Сквошевница”В купе вошла Анка.
На ней было зеленое платье цвета успешного pipeline. В руках она держала большую сияющую кнопку:
Squash and mergeПетька покраснел.
— Анка…
— Петь, — сказала она ласково. — Ну зачем тебе эти грязные маленькие коммиты? fix, fix2, real fix, sorry, lint, final, final-final. Дай я всё сглажу. Один красивый commit. Один чистый смысл. Один аккуратный PR.
Петька сглотнул.
— Василий Иванович, она права?
Чапаев прищурился.
— Для feature branch — да. Для release branch — не всегда. Для main history — зависит от того, какой смысл ты хочешь сохранить.
Анка надула губы.
— Василий Иванович, вы опять всё усложняете. Петьке нужна простота. Петьке нужен squash.
Она подошла ближе.
— Петь, представь: вся твоя грязь исчезает. Все твои сомнения. Все твои промежуточные ошибки. Один commit: feat: add branch policy. Красиво же?
Петька закрыл глаза. Ему стало тепло.
Он увидел линейную историю. Он увидел зелёный CI. Он увидел ревьюера, который не пишет «why?». Он увидел мир, в котором каждый PR имеет смысл.
И тут Чапаев отвесил ему подзатыльник.
— За что?!
— За то, что ты опять влюбился в инструмент.
Анка улыбнулась и растворилась в дыму.
— Squash — не ложь, Петя, — сказал Чапаев. — Но и не истина. Это способ сказать: «внутренняя борьба этой ветки не является частью публичной истории». Иногда это правильно. Иногда нет.
— А когда нет?
— Когда сама борьба была релизной стабилизацией. Когда последовательность фиксов важна. Когда hotfix должен быть traceable. Когда ты потом будешь спрашивать: «какой именно commit изменил поведение execution?» — а в ответ увидишь один огромный комок с названием release fixes.
Петька вытер лоб.
— Значит, даже squash может быть аппроксимацией?
— Всё может быть аппроксимацией, Петя. Особенно то, что обещает чистоту.
Бронепоезд hotfix
Section titled “Бронепоезд hotfix”Где-то в степи завыл продовый алерт.
Сначала тихо.
Потом громче.
Потом так, что в Grafana у одной панели начался нервный тик.
На экране резервного ноутбука вспыхнуло:
ORDER_SUBMIT_RACE_DETECTEDПетька вскочил.
— Прод горит!
Чапаев спокойно надел шинель.
— Значит, сейчас мы узнаем, есть ли у нас branch policy, или только литературный кружок.
— Что делать?
— От main.
git checkout maingit pull --ff-only origin maingit checkout -b hotfix/order-submit-race— А develop?
— Не трогать.
— Но fix же нужен и там!
— Потом.
— А если я сразу merge main into develop?
Чапаев посмотрел на него так, что Петька сам сел обратно.
Hotfix был коротким, злым и точным. Не красивым. Не философским. Не «давайте заодно поправим naming». Один bug. Один fix. Один тест.
PR в main.
CI.
Tag.
v2026.06.15-hotfix.1Только после этого Чапаев сказал:
— Теперь возвращаем лекарство в будущее.
git checkout developgit cherry-pick <hotfix_commit>— А если есть active release?
— Тогда сначала в release/*.
Петька кивнул.
Он впервые понял разницу между паникой и аварийным протоколом.
Паника говорит:
merge everything everywhere nowПротокол говорит:
one woundone bandageone backportВеликая пустота branch graph
Section titled “Великая пустота branch graph”Под утро они сидели молча.
На экране был новый граф.
Не идеальный.
Там всё ещё были ветки. Там всё ещё были merge commits. Там всё ещё была жизнь.
Но теперь линии не походили на галлюцинацию осьминога в терминальной стадии венчурного капитализма.
Они походили на карту военной операции.
main стоял отдельно, как штаб.
develop двигался вперёд, как обоз next release.
release/* уходил в сторону, как санитарный поезд.
hotfix/* вылетал короткой пулей из main и возвращался обратно через cherry-pick.
experiment/* копался в грязи за забором и не имел права входить в дом без досмотра.
Петька долго смотрел на это.
— Василий Иванович, — сказал он наконец, — значит, дело не в том, чтобы не было грязи?
— Конечно нет.
— А в чём?
— В том, чтобы грязь знала свой адрес.
Петька задумался.
— Это как в trading engine. Нельзя убрать неопределенность. Можно только не давать ей притворяться сигналом.
Чапаев довольно кивнул.
— Вот теперь ты начинаешь понимать Git.
Петька посмотрел на develop.
Она больше не казалась ему грехом. Она казалась инструментом. Опасным, но полезным. Как маузер. Как leverage. Как агент с write access.
— Значит, develop можно оставить, если она next release?
— Можно.
— Но нельзя позволять ей быть второй main?
— Нельзя.
— И нельзя синкать туда-сюда просто потому, что страшно?
— Нельзя.
— И experiment whole-merge нельзя?
— Это уже не нельзя, Петя. Это уголовщина против будущего себя.
Петька улыбнулся впервые за ночь.
Последний коммит
Section titled “Последний коммит”Перед рассветом Чапаев встал.
— Запиши, — сказал он.
Петька открыл новый файл:
BRANCH_POLICY.mdИ начал печатать.
# Branch Policy
We use GitFlow Lite.
## Long-lived branches
- main: production truth- develop: next release integration
## Temporary branches
- feature/\*: branch from develop, PR to develop, squash merge- fix/_: branch from develop or release/_, PR to source branch- release/\*: branch from develop, stabilization only- hotfix/\*: branch from main, PR to main- experiment/\*: disposable, cherry-pick only
## Main rules
main receives only:
- release/\*- hotfix/\*
develop receives:
- feature/\*- fix/\*- release back-merge after publication- hotfix cherry-picks
release/\* receives:
- stabilization fixes only
hotfix/\* must be backported by cherry-pick into active release or develop.
## Forbidden
- daily merge main into develop- random merge develop into main- feature/\* into main- experiment/\* whole merge- release/\* accepting new features- agent branch without explicit base and targetПетька остановился.
— Василий Иванович, а как назвать commit?
Чапаев посмотрел в окно. За окном поднималось солнце. Степь была пустой и чистой, как freshly cloned repo до первого npm install.
— Назови честно.
Петька набрал:
docs: make branch chaos observable— Нет, — сказал Чапаев. — Это всё ещё слишком красиво.
Петька стер.
Подумал.
И написал:
docs: contain branch shitЧапаев улыбнулся.
— Вот теперь merge.
Петька нажал Enter.
Где-то далеко, за пределами купе, агент проснулся и попытался создать PR с предложением заменить слово shit на complexity.
Но Чапаев уже стоял у двери с маузером.
— Спи, железный, — сказал он. — Сегодня мы впервые назвали вещи своими именами.
Экран мигнул.
CI запустился.
История не стала чистой.
Но впервые за долгое время она перестала врать.
Да, fair. Для торгового движка develop = next release нормален. Проблема не в develop, а в том, что у тебя сейчас develop пытается быть и next-release, и emergency-workbench, и conflict-sink, и staging truth одновременно.
Нужна не сказочная trunk-based схема, а GitFlow Lite с аварийными коридорами.
Реалистичная политика
Section titled “Реалистичная политика”main = prod / released truthdevelop = next release integrationrelease/* = stabilization corridorhotfix/* = prod emergency corridorfeature/* = disposable workexperiment/* = trash/sandbox, never merged wholeГлавное правило:
main и develop не синкаются туда-сюда постоянно.Синк между ними разрешен только в трех контролируемых случаях:
1. release/* -> main # published release2. release/* -> develop # return release fixes3. hotfix/* -> main + develop/release cherry-pickВсе остальное — источник спагетти.
Поток 1: обычная разработка
Section titled “Поток 1: обычная разработка”develop -> feature/x -> PR -> developКоманды:
git checkout developgit pull --ff-only origin developgit checkout -b feature/my-changeОбновлять feature-ветку:
git fetch origingit rebase origin/developНе делать:
git merge developgit merge mainPR:
feature/* -> developsquash mergedelete branchПоток 2: подготовка релиза
Section titled “Поток 2: подготовка релиза”Когда develop примерно готов:
git checkout developgit pull --ff-only origin developgit checkout -b release/2026-06-15git push -u origin release/2026-06-15После этого:
develop = можно продолжать новые измененияrelease/* = только стабилизация, багфиксы, config, migrations, testsmain = не трогать до публикацииОчень важное правило:
После cut release новые фичи не идут в release.Только fix/*Поток 3: фиксы в release
Section titled “Поток 3: фиксы в release”Фиксишь прямо через ветки от release:
release/2026-06-15 -> fix/release-risk-sizing -> PR -> release/2026-06-15После публикации release эти фиксы надо вернуть в develop.
Лучше cherry-pick, не большой merge-обмен:
git checkout developgit pull --ff-only origin developgit cherry-pick <release-fix-commit>Если фиксов много, допустим один контролируемый merge:
git checkout developgit merge --no-ff release/2026-06-15Но только один раз в конце релиза, не каждый день.
Поток 4: публикация релиза
Section titled “Поток 4: публикация релиза”release/* -> maintag from mainКоманды:
git checkout maingit pull --ff-only origin maingit merge --no-ff release/2026-06-15git tag v2026.06.15git push origin main --tagsДа, тут merge commit допустим. Это не грязь. Это release boundary.
Грязь — это когда такие merge commits появляются каждые 20 минут между main и develop.
Поток 5: продовый hotfix
Section titled “Поток 5: продовый hotfix”Когда прод горит:
main -> hotfix/x -> PR -> main -> taggit checkout maingit pull --ff-only origin maingit checkout -b hotfix/order-submit-raceПосле merge в main:
git tag v2026.06.15-hotfix.1git push origin main --tagsПотом обязательно вернуть hotfix туда, где идет работа:
Если active release существует
Section titled “Если active release существует”git checkout release/2026-06-15git cherry-pick <hotfix_commit>Если active release нет
Section titled “Если active release нет”git checkout developgit cherry-pick <hotfix_commit>Не делать:
git merge main into developкроме совсем редких случаев, когда ты осознанно закрываешь большой рассинхрон.
Самое важное: матрица PR target
Section titled “Самое важное: матрица PR target”| Тип работы | Branch from | PR target |
|---|---|---|
| New feature | develop | develop |
| Next-release bugfix | develop или release/* | develop / release/* |
| Release stabilization | release/* | release/* |
| Prod hotfix | main | main |
| Experiment | develop | never whole-merge |
| Agent spike | develop | cherry-pick only |
Что запретить жестко
Section titled “Что запретить жестко”feature/* -> mainmain -> develop ежедневными merge commitsdevelop -> main без release/*feature/* -> feature/*experiment/* -> develop whole mergeagent branch -> mainagent branch -> release/* без reviewДля агентов отдельно:
Agent must know:- base branch- target branch- task type- forbidden files- whether branch is disposableИначе агент неизбежно начнет лечить историю топором.
Минимальная версия для твоей реальности
Section titled “Минимальная версия для твоей реальности”Оставь develop, но введи 4 правила:
1. feature/* всегда в develop.2. release/* режется из develop и живет до публикации.3. main принимает только release/* и hotfix/*.4. hotfix возвращается в develop/release через cherry-pick, не через постоянный merge main -> develop.Это не сказка. Это достаточно грязно для реальной жизни, но грязь становится локализованной:
спагетти сейчас: everywhere
после политики: only inside release stabilization only around emergency hotfixes never as default branch hygieneВот это и есть нормальная прагматичная цель: не “идеальная история”, а контролируемые зоны говна.
Recommended policy: trunk-based GitHub Flow
Section titled “Recommended policy: trunk-based GitHub Flow”Use one long-lived branch only:
mainEverything else is short-lived and disposable.
Branch rules
Section titled “Branch rules”1. main is always deployable
Section titled “1. main is always deployable”main = production-ready trunkProtected:
main: require_pr: true require_ci_green: true require_review: true disallow_direct_push: true disallow_merge_commits: true merge_strategy: squash_only2. No develop
Section titled “2. No develop”Delete the permanent develop branch.
The graph in your screenshot usually comes from this anti-pattern:
main -> developdevelop -> mainmain -> developdevelop -> main...That creates merge spaghetti without adding real safety.
Use environments/tags instead of branches:
main = source of truthpreview = deployment environment, not branchstaging = deployment environment, not branchprod = deployment environment, not branchv1.2.3 = release tag3. Feature branches are short-lived
Section titled “3. Feature branches are short-lived”Naming:
feat/dashboard-registrationfix/pnl-null-close-reasonrefactor/setup-state-machinechore/update-ci-cacheLifetime:
target: same daymax: 2-3 daysRule:
branch from mainrebase on mainsquash merge into maindelete branchDaily workflow
Section titled “Daily workflow”Start work
Section titled “Start work”git checkout maingit pull --ff-only origin maingit checkout -b feat/my-changeSync with main
Section titled “Sync with main”Use rebase, not merge:
git fetch origingit rebase origin/mainForbidden:
git merge maingit merge developgit merge origin/maininside feature branches.
Finish work
Section titled “Finish work”Open PR into main.
Merge mode:
Squash and merge onlyThen delete the branch.
Hard forbidden operations
Section titled “Hard forbidden operations”Put these in team/agent policy:
No permanent develop branch.No merge commits into main.No merging main into feature branches.No merging feature branches into each other.No branch-to-branch release trains.No "sync main into develop" commits.No agent auto-merge without green CI.Allowed:
feature branch -> PR -> squash -> mainhotfix branch -> PR -> squash -> mainmain -> release tagRelease policy
Section titled “Release policy”For normal releases:
git checkout maingit pull --ff-only origin maingit tag v2026.06.15-1git push origin v2026.06.15-1For hotfixes:
git checkout maingit pull --ff-only origin maingit checkout -b fix/prod-hotfix-short-name# commit fix# PR -> squash merge -> main# tag from mainNo separate release/* branches unless you truly need long stabilization windows.
Minimal BRANCH_POLICY.md
Section titled “Minimal BRANCH_POLICY.md”# Branch Policy
We use trunk-based development.
## Long-lived branches
Only:
- main
`main` must always be deployable.
## Short-lived branches
Allowed branch prefixes:
- feat/- fix/- refactor/- chore/- docs/- test/
Branches must be created from `main`, kept short-lived, and deleted after merge.
## Merge policy
All changes enter `main` through PRs.
Allowed merge strategy:
- squash merge
Forbidden:
- merge commits- permanent `develop`- merging `main` into feature branches- merging feature branches into each other- syncing `main` and `develop`
To update a branch, use:
```bashgit fetch origingit rebase origin/main```Releases
Section titled “Releases”Releases are tags from main.
Example:
git tag v2026.06.15-1git push origin v2026.06.15-1Hotfixes
Section titled “Hotfixes”Hotfixes branch from main, merge back through PR, then tag from main.
---
## Migration from current state
1. Freeze `develop`.2. Check whether `develop` contains anything not in `main`:
```bashgit fetch origingit log --oneline origin/main..origin/develop- If needed, cherry-pick or squash those changes into a clean PR to
main. - Delete
develop:
git push origin --delete develop- Set GitHub/GitLab default branch to
main. - Enable squash-only PR merges.
- Disable merge commits.
Net result:
linear mainsmall PRsclean historyzero branch spaghettiThis is the minimal elegant policy: main is the trunk, everything else is temporary.