Branching стратегии в Git

Прежде чем поговорить о лучших практиках, давайте разберемся с худшими практиками и анти-паттернами при работе с любой бранч стратегией.
Branching & merging анти-паттерны:
- Merge Paranoia – когда девелоперы боятся мержить код, из-за последствий которые могут возникнуть (мерж конфликты) поэтому накапливается негативный эффект отложенной интеграции.
- Merge Mania – когда разработчики больше времени тратят на объединение изменений, чем на разработку.
- Big Bang Merge – В ветки не подмерживаются изменения, как следствие в конце происходит один гигантский мерж в конце.
- Never-Ending Merge – непрерывный мержинг, так как всегда есть что мержить.
- Wrong Way Merge – объединение более поздней бранчи с более ранней версией.
- Branch Mania – создание большого количества веток без видимой на то причины.
- Cascading Branches – создание веток без мержа их в mainline в конце разработки.
- Mysterious Branches – создание ветки без причины.
- Temporary Branches – создание ветки с изменяющейся причиной ее существования: ветка становится «permanent temporary workspac’ом».
- Volatile Branches – старт ветки в нестабильном состоянии или перенос нестабильных изменений в другие ветки.
- Development Freeze – остановка всей разработки для создания веток, объединения или создания релизов.
- Berlin Wall – использование веток для разделения людей в команде, вместо разделения на таски/фичи, над которыми они работают.
Git Flow (Feature Based Development)
Схематично Git Flow можно описать так:

Git Flow — одна из первых крупных стратегий ветвления, которая завоевала популярность. Git Flow описывает несколько веток для разработки, релизов и взаимодействия между ними.
Главные ветки:

По сути модель перекочевала с других существующих моделей. Репозиторий содержит 2 главные ветки:
master — дефолтная ветка знакомая каждому, кто работал с гитом. Параллельно в этой концепции существует еще одна ветка develop .
Master в этой концепции всегда содержит стабильный код, а develop бранча существует для того чтобы от нее бранчеваться и сливать туда уже готовые фичи для последующего мержа в master . Как следствие master выступает релизной (иногда) и stable бранчей в этой концепции.
В Git Flow мы можем использовать следующие типы веток:
- Feature branches
- Release branches
- Hotfix branches
Feature branches
- Могут бранчеватся от develop
- Должны вмержится в develop
- Naming convention: любые названия кроме master , develop , release-* , или hotfix-*
Release branches
- Могут бранчеватся от develop
- Должны вмержится в develop и master
- Naming convention: release-*
Помните, до того как вмержить код в релиз ветку, необходимо добавить ей тег с версией релиза (например «0.9 hotfix»)
Hotfix branches
- Могут бранчеватся от master
- Должны вмержится в develop и master
- Naming convention: hotfix-*
Плюсы и минусы Git Flow:
Плюсы:
- Git Flow используется многими распределенными командами, в тч и open source команды, которые имеют разные уровни квалификации. Сопровождающие проекта могут проводить код ревью и утверждать каждую строку кода в релизы.
- Git Flow хорошо подходит для «традиционной модели релизов», где релизы делаются раз в месяц или раз в пару недель.
- Git Flow также хорошо работает при работе с установленным продуктом или несколькими версиями в производстве.
Минусы:
- Git Flow может замедлять работу, когда приходится ревьювить большие пулл реквесты, когда вы пытаетесь выполнить итерацию быстро.
- Релизы сложно делать чаще, чем раз в неделю.
- Большие функции могут потратить дни на мерж и резолв конфликтов и форсировать несколько циклов тестирования.
- История проекта в гите имеет кучу merge commits и затрудняет просмотр реальной работы.
- Может быть проблематичным в CI/CD сценариях.
GitHub Flow

Он выглядит почти так же как и Git Flow, но фиксированная ветка всего одна — master ; всё остальное принадлежит тематическим ветвям. Тематические ветви, в свою очередь, создаются в форках — клонированных копиях репозитория. То есть центральный репозиторий тематических веток не содержит. В том числе и после слияния, так как метки веток при этом снимаются и их головы становятся анонимными.
GitLab Flow
Как и в GitHub Flow, фиксированная ветка всего одна — master, всё остальное принадлежит тематическим ветвям. Однако, если в том случае релизы размещались в коммитах master-a, то здесь для каждого релиза создаётся своя, отдельная ветка. Причём никакого мержа этих веток с parent’ом не производится. Если ветка отбранчевалась, значит она будет жить своей жизнью, получая исправления ошибок в виде отдельных коммитов (возможно, портированных из head/master с учётом накопившейся разницы в функционале между ветками).
Environment branches в GitLab flow

Release branches в GitLab flow

Trunk Based Development (TBD)
На официальном сайте эта концепция отображается такой схемой:

Лично для меня, когда я стал изучать эту концепцию эта схема показалась совсем не понятной и не раскрывала суть этой концепции. Давайте же разберемся подробнее простым языком в чем суть Trunk Based Development.
Что такое Trunk Based Development (TBD) ?
TBD прозволяет бизнесу тестировать бизнес-гипотезы «As soon as possible». Тк позволяет очень быстрыми итерациями релизить код на продакшн.
В Trunk Based Development можно выделить следующие особенности:
- Ветки живут максимум 2 дня
- Feature Flags
- Branch By Abstraction
- Continuous Code Review (это концепция из экстримального программирования которая говорит о том, что код который попадает на ревью, должен ревьювится как можно быстрее)
- master всегда готов к деплою, даже если в нем есть не готовые фичи
Feature Flags
Feature Flags — это концепция в которой у нас есть файл конфигурации, где прописано какая из фич включена/выключена и в коде существует проверка которая позволяет пропускать какую-то логику, например
if (configurationManager.getParameter("someFuncIsEnabled")) < // do some stuff >else < // do default logic >
Приемущества использования Feature Flags
- Можем мержить и деплоить код, который еще не готов
- A/B тесты
- Шаринг кода между недоработанными фичами, за счет мержа всего кода в master
Branch By Abstraction
Trunk Based Development предлагает вместо создание ветки для фич, создавать ветку на изменение одной абстракции.
Представим, что у нас есть объект Car , у которой есть абстракция “передние колоса” и “задние колеса”, которые мы хотим заменить на другой тип колес. В случае с feature branch, мы бы разработали реализацию нового типа колес в 1 бранче, с Branch By Abstraction все немного сложнее.
Рассмотрим рисунок и шаги которые нам помогут раскрыть порядок действий при TBD подходе

- Создаем ветку, оборачиваем переднее колесо в абстракцию, отправляем Pull Request, мержим
- Создаем ветку, описываем новый тип передних колес и добавляем feature flag переключения типов колес, отправляем Pull Request, мержим
- Создаем ветку, оборачиваем заднее колесо в абстракцию, отправляем Pull Request, мержим
- Создаем ветку, описываем новый тип задних колес и добавляем feature flag переключения типов колес, отправляем Pull Request, мержим
- Включили в проде новый тип колес, убедились, что все ок
- Удаляем старые колеса отдельными Pull Request`ами
И что нам это дало?
- Частые интеграции! Мы уже пришли ранее к тому что нужно часто интегрировать маленькие кусочки кода(CI), теперь при таком подходе можно делать «микрокусочки».
- Постепенное изменение/рефакторинг кода. Вместо переделки всего разом, меняем постепенно, шарим изменения до того как закончим большую задачу.
- Возможность переключения на другие фичи. В случае, если нужно переключиться на другую задачу, мы можем смежить последнее изменения и вернуться к доработке потом.
Continuous Code Review
Ревьювим чужие pull requests, сразу после того как отправили свой. Из-за этого что пулл реквесты маленькие(изменение одной абстракции), их ревью занимает не более чем пару минут, Если от создания пулл реквеста до аппрува прошло 10 минут, то это приемлемый результат, если больше 1 часа, то это считается очень плохим результатом.
Что нам дает концепт Continuous Code Review
- Шаринг знаний. Все понимают как меняется сервис, переиспользуют код, подсказывают друг-другу лучшие практики.
- Снижение тех долга за счет того что мы все рефракторим на ходу.
- Ускорение деплоя. (пулл реквест весит не пару дней, а несколько минут и может сразу отправляться в прод)
Плюсы и минусы Trunk Based Development
Плюсы:
- Позволяет быстро выполнять итерации и при этом поддерживать качество.
- Хорошо работает в сценариях CI/CD.
- Можно релизиться как можно чаще, в том числе несколько раз в день.
Минусы:
- Лучше всего работает когда у вас команда состоит с опытных разработчиков.
- Наполовину готовые функции, неправильно реализованными feature flag’ами, может вызвать проблемы.
- Изначально может вызвать некоторые проблемы, если нет хорошего покрытия тестами или уверенности в стабильности системы.
branch instruction
машинная команда или оператор языка программирования, позволяющий изменить порядок выполнения команд или операторов программы. Различаются команды безусловных (unconditional branch) и условных (conditional branch) переходов.
Branch-free code is favored. — Приветствуется программный код без переходов см. тж. branch, branch point, jump
Англо-русский толковый словарь терминов и сокращений по ВТ, Интернету и программированию. . 1998-2007 .
- branch history table
- branch point
Смотреть что такое «branch instruction» в других словарях:
- branch instruction — šakojimo komanda statusas T sritis automatika atitikmenys: angl. branch instruction; branching instruction; decision instruction vok. Verzweigungsbefehl, m; Verzweigungsinstruktion, f rus. команда ветвления, f pranc. instruction de branchement, f … Automatikos terminų žodynas
- Branch predictor — In computer architecture, a branch predictor is the part of a processor that determines whether a conditional branch in the instruction flow of a program is likely to be taken or not. This is called branch prediction. Branch predictors are… … Wikipedia
- Instruction pipeline — Pipelining redirects here. For HTTP pipelining, see HTTP pipelining. Basic five stage pipeline in a RISC machine (IF = Instruction Fetch, Decode, EX = Execute, MEM = Memory access, WB = Register write back). In the fourth clock… … Wikipedia
- Branch table — In computer programming, a branch table (sometimes known as a jump table) is a term used to describe an efficient method of transferring program control (branching) to another part of a program (or a different program that may have been… … Wikipedia
- Branch (computer science) — For other uses, see Branch (disambiguation). A branch is sequence of code in a computer program which is conditionally executed depending on whether the flow of control is altered or not (at the branching point). The term can be used when… … Wikipedia
- instruction de branchement — šakojimo komanda statusas T sritis automatika atitikmenys: angl. branch instruction; branching instruction; decision instruction vok. Verzweigungsbefehl, m; Verzweigungsinstruktion, f rus. команда ветвления, f pranc. instruction de branchement, f … Automatikos terminų žodynas
- Branch predication — is a strategy in computer architecture design for mitigating the costs usually associated with conditional branches, particularly branches to short sections of code. It does this by allowing each instruction to conditionally either perform an… … Wikipedia
- Branch misprediction — occurs when a central processing unit (CPU) mispredicts the next instruction to process in branch prediction, which is aimed at speeding up execution. During the execution of certain programs there are places where the program execution flow can… … Wikipedia
- Instruction level parallelism — (ILP) is a measure of how many of the operations in a computer program can be performed simultaneously. Consider the following program: 1. e = a + b 2. f = c + d 3. g = e * fOperation 3 depends on the results of operations 1 and 2, so it cannot… … Wikipedia
- Instruction path length — is a term frequently used to simply describe the number of machine code instructions required to execute a section of a computer program. The total path length for the entire program could be deemed a measure of the algorithms performance on a… … Wikipedia
- branch — [branch, bränch] n. [ME branche < OFr brance < LL branca, a claw, paw] 1. any woody extension growing from the trunk or main stem, or from a main limb, of a tree or shrub 2. anything physically resembling a branch, as a tine of a deer s… … English World dictionary
Прохождение Learn Git Branching
Если у вас возникли сложности с прохождением интерактивного курса Learn Git Branching, подсмотрите решение в этой шпаргалке.
Learn Git Branching — это интерактивный учебник по Git, направленный на закрепление теории прохождением наглядной практики. Если вы столкнулись с трудностью в процессе прохождения, выберите необходимый раздел, уровень и подсмотрите решение.
- Введение
- Едем дальше
- Перемещаем труды туда-сюда
- Сборная солянка
- Продвинутый уровень
- Push & Pull
- Origin и Git remotes
Введение
Прохождение Learn Git Branching начинается с азов.
1. Знакомство с Git commit
В этом уровне объясняется, что такое коммиты и как их следует делать. Для прохождения уровня достаточно ввести две команды для коммита, а именно:
git commit -m "First commit" git commit -m "Second commit"
Примечание Чтобы перейти в консоль LearnGitBranching, нажмите Tab .
Вообще, достаточно и просто двух команд git commit . Однако правилом хорошего тона является указание ветки (у нас это main ) и именование коммитов в соответствии с тем, какие изменения были в них внесены.
2. Ветвление в Git
В этом уровне следует создать и переключиться на новую ветку bugFix :
git branch bugFix git checkout bugFix
3. Git merge
Теперь надо понять, как объединять изменения из двух разных веток. Для этого будем использовать команду git merge . Такой тип слияния создаёт коммит, который имеет сразу двух родителей. Для прохождения уровня введите следующие команды:
git branch bugFix git checkout bugFix git commit -m "First commit" git checkout main git commit -m "Second commit" git merge bugFix
4. Git rebase
Это второй способ объединения изменений в двух ветках. При ребейзе Git по сути копирует набор коммитов и переносит их в другое место. В учебнике сказано, что с rebase история коммитов будет чище, но в основном используется именно merge .
Давайте посмотрим на решение:
git branch bugFix git checkout bugFix git commit -m "First commit" git checkout main git commit -m "Second commit" git checkout bugFix git rebase main
Едем дальше
Далее в прохождении Learn Git Branching вы столкнётесь с демонстрацией способов перемещения по дереву коммитов.
1. HEAD
Суть в том, чтобы отделить HEAD от ветки bugFix и присвоить его последнему коммиту в этой же ветке:
git checkout C4
2. Относительные ссылки: ^
Перемещаемся на первого родителя ветки bugFix :
git checkout bugFix^
3. Оператор «~» и branch forcing
Именно на этой задаче у многих возникают проблемы, потому что схема выглядит весьма запутанной. Но чтобы осуществить перемещение, достаточно ввести в консоль Learn Git Branching такие команды:
git checkout C1 git branch -f main C6 git branch -f bugFix bugFix~3
4. Отмена изменений в Git
Здесь просто отменяем изменения локально и удалённо. Поскольку изначально мы находимся на локальной ветке (она отмечена звёздочкой), то и начинаем с неё:
git reset HEAD~1 git checkout pushed git revert HEAD
Перемещаем труды туда-сюда
Теперь речь пойдёт о перемещении изменений — возможности, позволяющей разработчику сказать «Хочу, чтобы эти изменения были вот тут, а вот эти — вон там» и получить точные, правильные результаты, не теряя при этом гибкости разработки.
1. Git cherry-pick
Здесь нужно перенести копии выбранных комитов в main , что делается всего одной строкой:
git cherry-pick C3 C4 C7
2. Git interactive rebase
Это задача с использованием интерактивного окна для перемещения элементов. Для начала впишите код:
git rebase -i HEAD~4
Затем выделите и переставьте элементы в интерактивном окне Learn Git Branching следующим образом:
Сборная солянка
На этом прохождение основ в Learn Git Branching заканчивается, и дальше именно вы решаете, какую команду выбрать. Я просто приведу свои варианты решения задач из сборной солянки, но вы всегда можете воспользоваться другими.
1. Выберем один коммит
git checkout main git cherry-pick C4
2. Жонглируем коммитами
git rebase -i main git commit --amend -m "an updated commit message" git rebase -i main git branch -f main caption
3. Жонглируем коммитами №2
git checkout main git cherry-pick C2 git commit --amend -m "an updated commit message" git cherry-pick caption
4. Теги
git checkout C2 git tag v1 C2 git tag v0 C1
5. Git describe
git describe main git describe side git describe bugFixgit commit -m "Finish the task"
Продвинутый уровень
Вот мы и подошли к продвинутому уровню в прохождении Learn Git Branching.
1. Rebase на нескольких ветках
Перемещения здесь выглядят сложными, но на деле всё просто:
git rebase main bugFix git rebase bugFix side git rebase side another git rebase another main
2. Определение родителей
Решить можно в три, две и в одну команду. Сделаем в одну:
git branch bugWork HEAD~^2~
3. Спутанные ветки
Задание считается сложным, но достаточно просто абстрагироваться и вспомнить полезную команду git cherry-pick :
git checkout one git cherry-pick C4 C3 C2 git checkout two git cherry-pick C5 C4 C3 C2 git branch -f three C2
Push & Pull
Теперь переходим к удалённым репозиториям. В этих ответах только код.
1. Введение в клонирование
git clone
2. Удалённые ветки в Git
git commit -m "First commit" git checkout o/main git commit -m "Second commit"
3. Git fetch
git fetch
Оптимизация работы программы по скорости методами программирования без условных операторов
Лобашевская, В. А. Оптимизация работы программы по скорости методами программирования без условных операторов / В. А. Лобашевская. — Текст : непосредственный // Молодой ученый. — 2021. — № 20 (362). — С. 33-36. — URL: https://moluch.ru/archive/362/80996/ (дата обращения: 08.01.2024).
В статье приводится описание техники программирования без использования условных операторов. Такая техника позволяет минимизировать эффект ошибочного предсказания ветви процессора. Приводится сравнение скорости работы функций с условными операторами и без них.
Ключевые слова: условный оператор, предсказатель переходов, branch misprediction, бенчмарк, Си, ассемблер.
Введение
Внимательно посмотрев на таблицу 1 «задержек, которые должен знать каждый программист» [1] можно заметить строчку «Branch mispredict». Ошибочное предсказание ветви (branch mispredict) — это ситуация, когда процессор не смог угадать ветвь, по которой пойдет программа в условном переходе [2]. Если процессор не смог угадать ветвь, то ему требуется дополнительное время на удаление и загрузку инструкций (команд) для другой ветви. На это тратится какое-то время, в 5–10 раз большее, чем обращение к кешу L1 процессора.
Задержки, которые должен знать каждый программист (часть)
L1 cache reference
L2 cache reference
Main memory reference
Если понимать, как работает ошибочное предсказание, то можно уменьшить количество условных переходов в программе и сильно оптимизировать ее работу по времени. Такой подход называется безусловным программированием (branchless programming). Подробнее о предсказателе переходов процессора можно прочитать в соответствующих источниках [3].
В этой статье будет продемонстрированы временные затраты программы на разрешение ошибочного предсказания, а также способы написания условий без условных переходов.
Простейший пример
Рассмотрим простейший пример, функция min():

Это обычный способ написания функции нахождения минимума на языке Си. Он понятен всем разработчикам. Но в нем существует условный оператор if, который, если верить таблице выше, может замедлить выполнение нашей программы. Напишем функцию min_branchless():

В данном случае мы используем особенность языка Си, которая позволяет преобразовать значение false в 0, а значение true в 1. Мы используем сумму произведений с взаимно-исключающими условиями, чтобы получить необходимый нам результат. Например a = 5 и b = 7, тогда выражение (a < b) будет равно 1, а выражение (b перехода .
Проверим, какая из этих функций будет работать быстрее дизассемблировав их:

Важное замечание. Во время компиляции здесь и далее используются флаги оптимизации (для gcc -O2 например). Также сокращены адреса для повышения читаемости.
Видно, что операций в min_branchless() несколько больше, чем в min(). А в min() вообще нет операций условных переходов (таких как jg, je, jle и т. д.). Это связано с тем, что современные компиляторы достаточно умные, и могут понять, что в этой функции мы хотим найти минимум из двух чисел и используют соответствующую ассемблерную команду cmovg (выделено жирным).
В простейшем случае применение branchless программирования не позволит ускорить работу программы.
Пример с ускорением работы
Рассмотрим функцию, которая заменяет все строчные буквы на прописные — toUpper(). Обычная реализация этой функции выглядит следующим образом:

Поясним, что мы пишем программы на языке Си в котором строка представляется в виде участка памяти, разбитого на байты — отдельные ASCII символы, оканчивающиеся символом ‘\0’. Поэтому мы используем цикл по этому участку памяти for (; *str; ++str).
Поскольку символы имеют представление ASCII, то чтобы изменить регистр символа нужно добавить или вычесть к нему 32. Латинские буквы в ASCII таблице идут подряд и ‘A’ отличается от ‘a’ на 32 единицы.
Дизассемблировав эту функцию мы обнаружим условный переход переходов ja.

Время выполнения этой функции для строки размером 1024 случайных символа (небольшой размер, чтобы минимизировать влияние промахов кэша) четыре миллиона раз составляет примерно 21,9 секунд.
Изменим функцию to_upper() с применением принципов безусловного программирования:

В этой функции отказываемся от условного оператора if, но оставляем взаимоисключающие условия. Результат вычисления условий умножается на необходимый нам результат: на (*str — 32) если буква была строчной (т. е. делаем букву заглавной), или на (*str) т. е. на сам символ (т. е. символ не меняется).
Время выполнения этой функции условиях идентичных условиям из to_upper() составило 11,2 секунды, что в 2 раза быстрее первого варианта.
Функцию to_upper() можно ускорить еще больше:

Время выполнение этой версии функции составило 7,0 секунд, что примерно в 3 раза быстрее первого варианта.
Взглянув на дизассемблерный код to_upper_branchless2() можно увидеть, что там отсутствуют команды условных переходов:

Именно поэтому скорость выполнения этой функции выше, чем у той, где есть условные переходы.
Выводы
Очевидно, техника программирования без условных операторов позволяет значительно (в несколько раз) оптимизировать программу по скорости выполнения. Однако, этот способ программирования значительно усложняет восприятие программного кода, и не каждый программист поймет, что происходит в такой оптимизированной функции. Этот способ можно применять в программах критичных к времени выполнения, например высоконагруженных сервисах, или огромных научных вычислениях.
- Jonas Bonér Latency Numbers Every Programmer Should Know — Текст: электронный // Текстовый документ на гит репозитории — URL: https://gist.github.com/jboner/2841832 (дата обращения 28.04.2021)
- Иерархия памяти. — Текст: электронный // Википедия свободная энциклопедия. — URL: https://computer_en_ru.academic.ru/4251/branch_misprediction (дата обращения 30.04.2021)
- Предсказатель переходов. — Текст: электронный // Википедия свободная энциклопедия. — URL: https://ru.wikipedia.org/wiki/Предсказатель_переходов (дата обращения 30.04.2021)
Основные термины (генерируются автоматически): ASCII, функция, время выполнения, условный оператор, эта, безусловное программирование, ошибочное предсказание, переход, скорость выполнения, язык Си.