На днях вышла прекрасная, хотя и спорная статья — Please, stop using GitFlow! (и еще десяток на эту же тему после нее).
Ее основными тезисами были:
- GitFlow противоречит тезису "ветки должны быть короткоживущими".
Это важно, потому что чем меньше живет ветка — тем меньше шанс конфликтов (не только git, но и логических) - GitFlow препятствует rebase-ам, чтобы сохранить merge-коммиты.
Да, их можно сохранять и при ребейзах, но команды Git Flow не делают этого. - GitFlow отрицает подход Contunious Delivery, считая, что каждый акт Delivery должен совершаться релиз-инженером, да и в целом можно увидеть, что он ориентирован только на долгий релизный цикл.
- GitFlow не дружит с микросервисами поверх мультирепозиториев (впрочем, тут вообще мало что подходит, это идея, которая всегда плохо заканчивается)
Но проблема GitFlow в том, что он и с монорепозиториями тоже не дружит.
Я сам об это споткнулся в процессе дизайна пайплайнов CI: GitFlow чудовищно мешает, когда работает поверх монорепозитория с несколькими deliverables, например, когда в одном репозитории отдельно и бэкэнд, и фронтэнд — уже из-за того, что он позволяет докоммичивать в релизные ветки, могут возникнуть конфликты при обратном мердже, если в один момент времени существует больше, чем одна релизная ветка. Да даже если одна, все равно плохо — в таких условиях надо патчить в принципе релизные механизмы GitFlow, чтобы хоть как-то заработали отдельные релизы сущностей.
Так что делать-то?
Автору оригинальной статьи помог trunk-based development + feature flags — почти гугловый green trunk, но не совсем. Но это совсем не значит, что этот подход нужен именно вам.
Во-первых нужно понять приоритеты.
Как выглядят ваши релизы?
- Они короткие или длинные? Вы хотите релизиться раз в пару часов, дней, недель, или у вас коробочные продукты, которые выкатываются раз в полгода?
- Сколько занимает время самого релиза? Это может быть от пары минут до даже дней, если у вас есть долгоживущие клиенты, которые подключены по WebSockets, и не хотят отваливаться. Всякое бывает.
- Нужно ли поддерживать старые или будущие релизы? Есть ли у вас LTS-релизы, в которые вы обязаны делать backport-ы (это когда вы чините баг в основной ветке, а потом идете, и еще раз чините его в ветке 2016 года) изменений? Есть ли у вас canary и beta-релизы?
- Есть ли у вас вообще Continuous Delivery?
Кто работает в репозитории?
- Все (или почти все) ли члены команды самодостаточны, или им нужен фидбек старших товарищей? Нужны ли вам полноценные циклы design review и merge/pull-request-ов, или среднее время на PR — это 5 минут на каждые 100 строк кода?
- Работает ли в репозитории много команд, или только одна?
Есть ли у вас автоматизированные средства для проверки качества кода? Ну, или хотите ли вы их внедрить?
- линтеры
- проверки типов
- автотесты: юнит-тесты, интеграционные тесты и так далее
Очень важно понимать, что релизный процесс и рабочий процесс — это вообще разные потоки. Тут без вариантов, любой flow поверх гита так или иначе это делает, разделяя изменения и релизы. Работа с этими двумя процессами похожа на работу с очередями вроде Apache Kafka или SQS — есть ряд источников, которые помещают обновления в очередь, и есть ряд указателей-релизов, которые смотрят на какой-то момент в прошлом, периодически поднимаясь вверх по этой очереди.
Изменения разработчиков и релизы — это абсолютно разные вещи. Релиз включает в себя пачку (от одного до бесконечности) изменений разработчиков, да, но он не обязательно должен включать в себя всю сделанную разработчиком фичу, он может забрать, допустим, пару созданных им для работы классов или функций, но не реализованную задачу, и это нормально.
Важно понимать, что если вам иногда требуется релиз, даже если изменений нет — вы что-то делаете не так. Иногда такое происходит, когда требуется обновить базовый JSON или YAML, который забирается во время сборки от внешнего поставщика — секреты, переводы, внешняя конфигурация.
Это неправильно, потому что это завязка на внешний фактор риска, и это неправильно, потому что это можно обновлять наживую во время работы с продакшном — автоматически или по кнопке, и в ситуации, когда это нужно будет сделать срочно — вы будете ждать полноценного релиза. Все, что можно вынести из релизного процесса, стоит вынести из него. Релиз — это дорогая операция, и если вы можете уменьшить их количество при той же скорости производства — стоит это делать.
Собираем свой flow
В общем случае вам будет нужна единая мастер-ветка, в которую будут попадать все изменения. Очень сложно представить ситуацию, где все вместе работают над отдельными задачами, синхронизируясь друг с другом время от времени (если это не поддержка старых версий). Есть подход, в котором заводится отдельная ветка для документации или для отдельного проекта, но по сути это отдельный репозиторий в рамках общего, и к нему надо относиться точно так же, хотя это практика, которая может привести к расхождениям между кодом и документацией к нему, а так же к невозможности одновременно держать документацию, скажем, к v1 и v2 решениям.
В целом, если вы можете жить в монорепозитории, стоит это делать. Да, нужно разово все настроить, но у вас нет никаких значимых плюсов в отдельных репозиториях, кроме потенциального риска плохо настроенного и тормозящего CI, а потенциальных минусов очень много: про преимущества монорепозиториев написана не одна и не две статьи.
Если ваши релизы частые — нужно явно внедрять Continuous Delivery.
Время релизного инженера — это издержки, которые будут расти при росте количества релизов и количества deliverables. Лучше потратьте время разработчика на что-то еще, рук не хватает всем и всегда, даже если вы Google. По личной статистике — около 10% ресурсов команды -в абсолютно разных проектах — в среднем уходят на релиз-инженера, если релизы не автоматизированы.
Для автоматизации релизов надо разово вложиться в инфраструктуру. Это разовая издержка, которая включает в себя:
Хорошие автотесты
CI-автоматику для выкладки релизов
Если у вас сервера и deliverables:
- prestable/staging-окружения
- механизмы быстрого отката
- мониторинги и alert-ы (в идеале интегрированные с авто-откатами)
- аварийные сценарии (если релиз не прошел или прошел частично)
Если у вас публикация пакетов или артефактов — все зависит от вашего flow, но я буду рад помочь с построением релизного механизма, пишите
Если релизы редкие, нужны backport-ы ИЛИ если в один момент времени потенциально может существовать более чем один обслуживаемый релиз — они явно будут существовать в отдельных ветках, которые не должны мерджиться обратно. После того, как вы отбранчуете эту ветку от основной рабочей (master/develop/trunk), вам может понадобиться внести в них несовместимые изменения, или просто отдельные коммиты. Для второго есть отличный механизм в git — cherry-picking. Ваши изменения должны "течь" только в одну сторону — от разработчиков в прод, из прода в основную рабочую ветку им попадать в действительности не нужно.
- Чем меньше и атомарней коммиты, тем меньше проблем при их переносе
- Используйте агрессивные правила линтинга, которые защитят вас от ненужных конфликтов. В случае с JS — есть пара хороших наборов eslint-правил. Prettier может подойти в вашем случае (но не гарантированно).
Если у вас больше, чем 5-6 разработчиков, или опытная команда — вам может подойти подход trunk-based development для повседневной работы разработчиков (но не обязательно, что для релизов). Если счет разработчиков в одном репозитории идет на сотни — или вы хотите объединить сотни разработчиков в одном репозитории, у вас почти нет альтернативных вариантов, разве что сменить git на что-то еще. Google рапортовал в разных статьях о значительном приросте производительности после перехода на этот подход, но, к сожалению, цифры разнятся, хотя и значимы во всех источниках. Facebook не сообщает о цифрах, но известно, что они тоже пользуются этим подходом.
Несмотря на то, что Trunk-based — это просто "вливайте минимальные изменения сразу в рабочую ветку", он, в отличии от других подходов, требует вложений в инфраструктуру и настройку ее под себя. В целом, кроме обычных CI/CD, вам потребуется еще:
механика feature flags — это разовая издержка.
Для однопакетного репозитория стоимость внедрения сильно непредсказуема, но в целом должна быть не очень большой.
Для монорепозитория же она примерно пропорциональна количеству пакетов в монорепозитории. Переведите 2-3 разных и экстраполируйте, будет относительно точная, но слегка завышенная (потому что чем дальше, тем проще) оценка
В случае работы с короткоживущими ветками — merge queue, а в случае работы прямо в транке — механизм отката коммитов, не проходящих тесты.
второй вариант сделать сложнее, и он теоретически может провоцировать конфликты, так что лучше короткоживущие ветки.
В целом, эволюционный механизм для любого типичного проекта сейчас выглядит как-то так:
- github-flow
- осознание потребности контроля за качеством кода
- внедрение CI, автотестов
- осознание потребности релизных механизмов
- внедрение чистого git-flow или его вариации (любое решение с долгими ветками + релизными ветками)
- осознание потребности в CD
- настройка git-flow под свои нужды, внедрение CD, staging-релизов, интеграция с внешними инструментами
- осознание проблемы с синхронизацией работы разработчиков
- долгая работа над возможностью внедрения trunk-based и разгребание технического долга
- переход на trunk-based-подобный подход
Но забегать вперед — не всегда лучшая идея. Иногда стоит посидеть на github-flow или отложить разгребание техдолга (или на время нанять отдельного GitOps-а — да, такая роль и профессия теперь тоже есть). Более того, ваша реализация может и скорее всего должна начать отличаться от "эталонной". Это нормально, и много разных команд работают в своих собственных парадигмах, если описывать тут их все — это вполне может выйти в отдельный цикл статей.
Поймите, на каком этапе вы, что у вас болит, и что можно сделать, чтобы не болело.
Ваш Git — это часть вашей инфраструктуры и кодовой базы, в нем точно так же копится технический долг, и его точно так же надо обслуживать. К сожалению, это не очевидно для большинства, и это зачастую спотыкается о непонимание команд и руководителей.
Если вы ощущаете, что ваш Git делает вам больно — напишите мне, я буду рад помочь.
fougasse
Ну так не все релизят по 5 раз в день.
И длинные ветки тоже не для всех проблема.
И не все используют микросервисы.
Jabher Автор
Так я об этом и говорю. Нужно тюнить процессы отдельно под объем работ, которые делают команда/команды, и отдельно под релизный цикл.
Быстрые релизы требуют автоматики и вложений в инфраструктуру, долгие — требуют атомарных коммитов, чтобы не было проблем с подмердживанием и в рабочую ветку, и в старые релизы.
К монорепе и декомпозиции, кажется, рано или поздно приходят почти все (а если не пришли — надо просто подождать лет пять), поэтому подстраховаться будет не лишним, но если не очень хочется, можно жить с этим, держа в уме, что при появлении второго репозитория и потребности в оркестрации параллельных релизов GitFlow сломается и будет дергаться в конвульсиях.
Длинные ветки не проблема, если в команде 3 человека, работающие над разными частями. Если команда начала раздуваться — пора переходить на короткоживущие.
Я же как раз ровно об этом пишу.
fougasse
Имеем параллельные релизы в гитфлоу, штук 5, и пока он не дёргается.
Команда из 5-7 человек все работают в одной репе, но, естественно, стараются, по возможности, разделять, что не отменяет изменения в одном коде разными людьми.
Вот я, если честно, не понимаю, почему нужно переходить на короткие ветки? Модно? Начиная с какого размера команды?
Jabher Автор
Риск и разрешаемых, и несовместимых конфликтов прямо пропорционален количеству людей, параллельно работающих над проектом, количеству изменений в рамках каждого коммита/ветки, и времени жизни ветки. Тот же google исследовал этот вопрос.
VolCh
Да даже с одним человеком бывает, что получаешь конфликт, который сходу и не знаешь как разрулить.
Jabher Автор
Да, это так. В этом и смысл TBD в целом — он призывает делать максимально атомарные изменения. Добавил метод, создал класс, удалил класс. Шансы конфликтов резко падают.
Мне больше всего нравится объяснение его в этом выступлении — https://www.youtube.com/watch?v=hIW5ynk8HWc.
fougasse
Не очень понятно как быть с фиксами, котрые должны пойти в несколько релизов.
Признаюсь, видео не смотрел, можете на пальцах рассказать как быть в TBD, если есть 5 активных релизов, условно 1.0, 2.0, 3.0, 3.1, 3.2, транк 4.0 и баг нашли в 1.0. Как фикс проводят, через транк?
Jabher Автор
через cherry pick, в статье же написал. Либо заново имплементируют, если прям несовместимые изменения, но тут очевидно и так.
Ну и да, очень сложно аргументировать, когда твои аргументы не смотрят даже.
solver
Выглядит конечно прикольно.
Но я не могу себе представить проекты «в вакууме», фичи в которых которые можно перключать флагами. Во всех реальных проектах, в которых я участвовал, подавляющее большинство фич это смесь кода, данных (бд) и внешних (по отношению к коду) систем. Ну там другие сервисы, другие системы вообще, какие-то интеграции со своими схемами, версиями АПИ и прочего. Как в этой ситуации можно простым флагом отключить фичу? Надо откатывать изменения в бд (не только схемы, но и менять данные), менять схемы работы с интеграциями и т.д. и т.п.
Эти шансы резкопадают, да, но резко возрастает микроменджмент мелких задач.
Да и много мелких МР говороит о том, что кто-то должен целыми днями сидеть и ревьювить эту кучу мелких изменений… А разработчики должны по КД затягивать эти изменения к себе в ветки.
В такой волне микрозадач как можно сосредоточится на своей задаче?
Если посмотреть доклады Максима Дорофеева (и не только его), то станет понятно, что режим работы с отвлечением каждые (условно) 30мин на 2мин ревью убивает продуктивность на корню.
chapuza
— Отсюда, но я много на каких конференциях это слышал от представителей разных крупных бизнесов.
Мы, кстати, тоже используем уже года два как, и нарадоваться не можем.
Jabher Автор
Объем кода-то тот же. Более того, смотреть отдельные изменения проще, чем одно большое разом. Если у вас отсмотр 10 PR на 100 строк занимает сильно больше времени, чем одного на 1000, значит, у вас хреново построен процесс отсмотра PR-ов.
VolCh
Ну как сказать, контекст-то теряется.
solver
Отсмотр 10 PR на 100 строк отвлечёт меня 10 раз за день, вместо одного.
Плюс надо еще затянуть к себе эти изменения 10 раз, плюс 10 потенциальных разруливаний конфликтов…
Все специалисты говорят в один голос, что возврат фокуса внимания, после отвлечения (его еще называют состоянием потока), происходит в период до 25мин.
Получаем, что до 250 минут из рабочего дня просто выкидываем, на (условно) «вспомнить чем занимался».
P.S. В принципе, после объяснений с доклада как они мержатся «чуть ли не каждые 10 мин», становится понятно, зачем обычным приложениям известных компаний нужны сотни и тысячи разработчиков. С таким подходом вдумчиво писать код просто некогда))
chapuza
Вам же ясно и четко сказали:
Выключайте все нотификации, когда работаете. Раз, или два, в день — разгребаете, отвечаете, делаете CR. Количество отвлечений устанавливается вами. 10 коротких PR по-прежнему понять (и простить) в разы проще, чем одного монстра.
VolCh
Это проще, если эти 10 PR не взаимосвязаны. Ну запушили новый класс или, ещё "лучше" интерфейс. Вроде разумные имена и аргументы у методов, если вне контекста. А смотришь через несколько дней реальное использование — уже не видишь, что изначально было неразумно. А было бы вместе сразу обратил бы внимание, что, например, метод getUserName(id) в сервисе всегда вызывается как getUserName(this.authService.currentUser.id) и уже неоднозначно должен ли он быть в этом сервисе, может в сущности user, а может в сервисе Auth.
chapuza
Мир неидеален, и для решения подобных проблем люди придумали технический долг и рефакторинг.
Но в сравнении с монстром на 1000 строк — это все равно цветочки, потому что абстракции, рожденные тысячами строк — человеческий мозг просто не сможет охватить. И остается только синтаксис проверять, увы. Или потратить столько же времени, сколько автор кода, но создание полной картины.
Jabher Автор
trunk-driven в многих ипостасях на самом деле имеет ответ, как делать — "закоммиттил, создал свой PR, отсмотрел хотя бы один-два, начал делать дальше". Эдакие помидорки, но из кода.
fougasse
Но мы не гугл, к сожалению, и работаем как есть.
Да, не по фен-шую, но проблем с конфликтами страшных не было, видимо, получается делить работу суб-оптимально.