Собираем свой flow для git с нуля

?v=1

На днях вышла прекрасная, хотя и спорная статья — 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 делает вам больно — напишите мне, я буду рад помочь.

© Habrahabr.ru