Из всех моих разговоров с коллегами о разных аспектах разработки программного обеспечения одна тема всплывает чаще других. Да что там «чаще» — она повторяется снова и снова, как заезженная пластинка — это беседы на тему того, чем плох GitFlow и почему его стоит избегать.

Статья "Удачная модель ветвления для Git" описывающая метод, получивший в последствии название «GitFlow» стала де-факто стандартом того, как нужно начинать использовать Git в вашем проекте. Если поискать в Google что-то типа "git branching strategy" то вот как раз этот метод будет описан по первой ссылке (а скорее всего и по нескольким следующим).

Лично я ненавижу GitFlow и за последние годы убедил много команд разработчиков перестать его использовать, чем, как мне кажется, сохранил им уйму времени и нервов. GitFlow заставляет команды организовывать управление изменениями кода хуже, чем оно может быть реализовано. Но поскольку это такой популярный метод (по крайней мере в результатах поисковика), то команды без достаточного опыта, которые ищут «что-то, хотя бы как-то работающее» находят именно его при быстром поиске, да ещё и видят слово «успешный» прямо в заголовке статьи с его описанием — ну и начинают бездумно использовать. Я хочу хотя бы немного изменить этот паттерн поведения, описав в этой статье более простую и не менее успешную стратегию использования веток Git, которую я внедрил во многих командах. Часто эти команды пробовали использовать GitFlow, но испытывали проблемы, которые, пропали с переходом на ThreeFlow.

Я называю эту стратегию ThreeFlow потому, что в ней есть ровно три ветки. Не четыре. Не две. Три.

Ну и сразу предупреждение: серебряной пули нет и ThreeFlow тоже не панацея. Она подойдёт не всегда. Я думаю она будет плохо работать для embedded-разрботки и для опенсорсных проектов. Но она очень успешна для ситуаций, когда вся команда проекта работает в одной компании и нет никаких пул-реквестов от внешних разработчиков. Т.е. каждый в команде имеет полный доступ к коду и все необходимые права на запись в репозиторий.

Так а что же не так с GitFlow?


Если коротко, то «не так» с GitFlow его идея создания ветки для каждой разрабатываемой фичи. Ветки для фич — корень зла. Всё, что дают эти ветки — это проблемы, проблемы и снова проблемы. Если вы вообще больше ничего полезного не вынесете из этой статьи или прекратите читать её вот здесь — просто запомните мысль, что ветки для фич — это ужасно.

image

Справедливости ради стоит отметить, что оригинальная статья говорит о ветках для фич, что они «как правило, существуют лишь на машинах отдельных разработчиков, но не в главном репозитории (origin)». Но в этой же статье иллюстрирующие GitFlow рисунки показывают это иначе. Мы видим «origin» с ветками фич. Более того, я видел много команд разработчиков, использующих GitFlow и ни одна из них не обратила внимание на рекомендацию автора использовать ветки лишь на отдельных машинах разработчиков. Все, кого я видел, использовали ветки фич как долговременный инструмент, существующий в «origin».

Нет вообще ничего плохого в том, чтобы делать ветки для фич на своей машине. Это хороший способ переключаться между задачами, если потребности вашего проекта требуют работы над несколькими из них одновременно. Это хороший способ держать ветку master чистой, на случай необходимости сделать небольшой фикс без необходимости синхронизации всей вашей работы с удалённым репозиторием. Но я бы пошел дальше рекомендаций оригинального GitFlow и жестко запретил бы создание веток фич в origin.

Если вы используете длительно существующие ветки для отдельных фич, то ад их интеграции будет вашей постоянной реальностью. Два инженера успешно работают каждый над своей фичей, каждый в своей ветке, ничего вроде бы не предвещает беды. Но ни один из них не видит работы другого. Даже если они регулярно синхронизируют свои ветки с основной веткой разработки, то видят лишь комиты уже законченных и смердженных фич, но не текущую работу друг друга. И вот разработчик А вливает заканчивает разработку своей фичи и вливает код в основную ветку. Разработчик Б забирает эти изменения и получает классическую проблему «кто последний, тот и разгребает конфликты». Он, возможно, опоздал всего на минутку, но теперь потратит часы на попытки понять, что же здесь понаписывал разработчик А и как всё это должно быть смерджено. И чем дольше велась вот такая «изолированная» разработка в отдельных ветках фич — тем больше будет боли и страданий при мердже.

Длительно существующие ветки для фич — это вовсе не упрощение работы. Это просто откладывание проблем «на потом». Главной формой коммуникаций между разработчиками является исходный код. Вы можете сколько угодно утешать себя тем, что у вас есть регулярные стендапы, планнинг-митинги и ретроспективы, но это всё не важно. Представьте себе репетицию оркестра, где музыканты долго обсуждают, как они будут играть какое-то произведение, но потом дирижер просит их разойтись по комнатам и репетировать свои партии отдельно. Будет ли от таких репетиций толк? Так и с разработкой ПО — работа в ветках фич по сути своей является аналогом гробовой тишины в коммуникациях между разработчиками. Ветки для фич — это ужасно.

Кроме того, ветки для фич ужасно масштабируются. Один разработчик, создающий себе по ветке на фичу ради собственного комфорта — это ещё не беда. Но вот ваша команда растёт, и каждый разработчик имеет по ветке на каждую активно разрабатываемую фичу. Поздравляю, у вас теперь по проблеме на каждую пару веток. Пускай у вас всего 8 программистов и каждый из них работает всего над одной фичей в своей ветке. И вот у вас уже 28 (количество пар) оборванных коммуникационных линий. Добавляем ещё одного разработчика с ещё одной веткой — и вот у вас уже 36 «обрывов».

Использование флагов для включения фич


Вместо использования веток для разработки фич, попробуйте использовать флаги для их включения-выключения. Это просто. Начните разработку новой фичи с объявления булевого флага, по которому она будет включаться. Установите его по-умолчанию в false — и в этом случае вызывайте старый код, без кода для новой фичи:

if(newCodeEnabled) {
    // новый код
} else {
    // старый код
}

imageСам флаг может быть как жестко зашит в код, так и вынесен во внешний конфиг (возможно с использованием чего-то типа Consul или Zookeeper), что даст возможность включать и выключать новую функциональность для тестирования или даже в продакшене. Руководители проекта и заказчики очень любят видеть перед собой панель управления продуктом со списком фич, которые они сами могут включить или выключить, без необходимости привлечения разработчиков и пересборки проекта.

Когда два разработчика работают в одной (основной) ветке над разными фичами, то создают по флагу на каждую из них. И просто комитят\забирают код регулярно. Шансы на возникновение конфликта в таком случае минимальны. Каждый может комитнуть код, когда считает нужным. Каждый может синхронизировать свой локальный репозиторий с основным — и рассинхрон будет минимальным (уж точно не больше одного рабочего дня). Конфликтов либо не будет вовсе, либо они будут минимальны. Значительно проще понять, что изменил твой коллега вот в этом десятке строк за последний час, чем разгребать глобальные изменения за дни или недели, как предлагает нам GitFlow.

И да, если вы пишете тесты для своего кода (а ведь вы пишете их, да?), то нужно тестировать и ветку кода с отключенным флагом, и ту, где флаг включен. Если ведётся разработка двух взаимозависимых фич — на время разработки вам понадобится 4 теста для всех их комбинаций. Это звучит как угроза усложнения и замедления разработки, но не забывайте, что после окончания разработки новых фич «старые» блоки кода (и тесты для них) будут удалены, так что геометрического увеличения сложности вы не получите.

Флаги для новых фич могут использоваться и более динамично. Вы можете привязать их к определённым группам пользователей для бета-тестирования или A/B тестов.

Когда разработка фичи завершена и она включена по-умолчанию в продакшене, вы можете запланировать небольшую низкоприоритетную задачу по удалению старого кода и самого флага. Или, если вы по каким-то причинам хотите сохранить возможность отключения фичи (проблемы со стабильностью нового кода, регулирование нагрузки на backend), этого можно и не делать. В любом случае, важно осознанно принять решение об удалении или оставлении флага и старого кода — если об этом забыть, то со временем ваш код обрастёт мхом старого неиспользуемого функционала, который будет только отвлекать разработчиков и не приносить никакой реальной пользы.

Ценность подхода с флагами для включения новых фич просто невозможно переоценить. Я гарантирую, что как только вы начнёте использовать флаги для фич вместо веток для них же, то никогда не захотите вернуться обратно. На моей памяти почти всегда разработка большой новой фичи в отдельной долгоживущей ветке рано или поздно приводила к проблемам, требующим внимания сразу нескольких программистов и глубокого знания Git. В то же время подход с работой в одной ветке и флагами для новых фич ни разу не привёл к каким-то конфликтам, которые нельзя было бы решить за пару минут одним человеком.

Итак, ThreeFlow


Так, мы разобрались с тем почему ветки для фич это плохо и на что мы их можем заменить. Теперь мы можем поговорить собственно о модели ветвления ThreeFlow, которая из этого следует.

image

В этом подходе все разработчики работают в одной мастер-ветке. Если фича тривиальна — она просто реализуется и добавляется одним комитом. Если разработка фичи займёт какое-то время, то сначала добавляется флаг (по-умолчанию выключенный) для её активизации. Разработчик включает данный флаг локально для разработки и тестирования новой фичи, но код в основном репозитории по-прежнему использует «старые» ветки кода. Для добавления коммитов в master используется перемещение (rebase). Если вы использовали локальную ветку для работы над фичей, она должна быть перемещена в master, таким образом в origin у нас не будет никаких следов этой ветки.

Вот и всё. Так и происходит весь процесс разработки. Одна ветка, master. Весь код в ней. Всё необходимое включается или выключается флагами. У всех разработчиков один и тот же, часто синхронизируемый друг с другом код. Всё остальное в ThreeFlow касается уже только стратегии релизов, а не разработки.

Релизы


Когда приходит время релиза (по графику или когда скажет руководство) делается «срез» ветки master в ветку релиз-кандидатов. Одна и та же ветка используется для всех релиз-кандидатов.

Предназначение данной ветки — дать билд, который получит команда QA для выполнения регрессионных (и других) тестов. Теоретически, новые фичи данного релиз-кандидата уже были проверены QA по ходу их разработки и включения, но, возможно, QA захотят перепроверить их в релиз-кандидате.

Для создания релиз-кандидата вы делаете что-то вроде этого:

$ git checkout candidate  # предполагаем, что candidate указывает на origin/candidate
$ git pull # убедимся, что у нас актуальная копия репозитория
$ git merge --no-ff origin/master
$ git tag candidate-3.2.645
$ git push --follow-tags

Причина использования флага "--no-ff" здесь в том, что мы хотим создать merge-коммит (новый коммит с двумя родителями). Одним из его родителей будет предыдущий HEAD ветки релиз-кандидатов, а второй — HEAD ветки master. Это позволит вам легко отслеживать в истории кто и когда создал релиз-кандидат, а также что конкретно в него вошло (какие коммиты ветки master).

Также вы могли заметить, что мы создали тег для релиз-кандидата. Чуть детальнее об этом.

Если при тестировании релиз-кандидата обнаружатся баги, то они будут исправлены прямо в ветке релиз-кандидата, там же будет промаркирован новый релиз-кандидат, а изменения с исправлениями будут смерджены обратно в master. Эти изменения тоже должны быть применены с параметром "--no-ff", ведь мы хотим аккуратно показать, какой именно код был перемещен между ветками.

Когда релиз-кандидат протестирован и одобрен, мы обновляем ветку релизов таким образом, чтобы её HEAD указывал на HEAD ветки релиз-кандидатов. Поскольку у нас есть тег для каждого релиз-кандидата, то мы можем именно его запушить в ветку релизов:

$ git push --force origin candidate-3.2.647:release

Параметр "--force" здесь означает, что мы игнорируем все изменения в ветке релизов и просто насильно устанавливаем её HEAD на тот же коммит, который обозначает последний созданный тег релиз-кандидата (candidate-3.2.647 в примере выше). Заметьте, что это вовсе не является слиянием (merge), но это потому, что нам оно здесь и не нужно. Мы не хотим усложнять историю в Git, да и вообще единственной причиной создания ветки релизов является теоретическая необходимость экстренного фикса обнаруженной на продакшене критической проблемы. Да, этот "--force" перетрёт все хотфиксы в ветке релизов. Но знаете, если вы релизите следующую версию продукта с новыми фичами в то же время, пока другой член вашей команды фиксит баги на продакшене — у вас серьёзные проблемы с управлением проектом и коммуникациями. Их стоит решить ещё до начала всех этих танцев вокруг веток и релизов. Фиксы в ветке релизов должны быть очень редкими и, конечно, должны быть потом смерджены в ветки релиз-кандидатов и master.

Причина, по которой мы используем "--force", а не merge в том, что при merge коммит в HEAD ветки релиз-кандидатов и коммит в HEAD ветки релизов могут иметь разные sha-1, а это не то, что нам нужно. Мы не хотим создавать новый коммит с релизом, мы хотим назвать релизом именно тот коммит, который был выбран релиз-кандидатом, который тестировался командой QA и был одобрен к релизу тем, кто за это отвечает. Именно это и делает "--force".

image

Если вы будете следовать этим рекомендациям, то история в вашем git-репозитории будет выглядеть очень похоже на рисунок выше, показывая в точности какие коммиты перемещались между ветками.

Release Notes


Вы можете легко генерировать «release notes» к новым релизам. Нужно всего лишь получить разницу между прошлым тегом релиза и текущим тегом релиз-кандидата. Поскольку в ветке релизов у нас лежит то, что когда-то точно было релиз-кандидатом, мы можем узнать каким именно:

$ git describe --tags release
candidate-3.1.248

Теперь, когда мы знаем, что в релиз-кандидате у нас сейчас candidate-3.2.259, можно получить разницу между этими двумя тегами:

$ git log --oneline candidate-3.1.248..candidate-3.2.259

Ну или даже проще, без тегов, просто сравниваем HEAD веток release и candidate:

$ git log --oneline release..candidate

Применяемые операции


Вот некоторые часто используемые операции при работе по ThreeFlow. Все примеры предполагают, что ваши локальные ветки правильно соотнесены с удалёнными ветками и содержат актуальные изменения. Если вы не уверены в этом — всегда будет хорошей идеей лишний раз сделать git fetch и потом использовать имена вроде origin/master вместо просто master

Как мне сделать релиз-кандидат из ветки master?

$ git checkout candidate
$ git pull
$ git merge --no-ff master
$ git tag candidate-3.2.645 #optionally tag the candidate
$ git push --follow-tags

Как мне сделать релиз из релиз-кандидата?

$ git push --force origin <tag for the candidate>:release

Если вы почему-то решили не тегировать релиз-кандидаты, то придётся сделать:

$ git push --force origin candidate:release

Как мне найти ветку, в которой есть некоторый конкретный коммит?
Иногда люди хотят убедиться, что какое-то конкретное изменение вошло в релиз-кандидат или в релиз. Вот как это проверить:

$ git branch -r -contains <sha of commit>

Как мне найти тег, на который указывает HEAD некоторой ветки?

$ git describe --tags <branch>

Как мне узнать, какие коммиты войдут в некоторый новый релиз?

$ git log --oneline release..<tag of release candidate>

или:

$ git log --oneline release..origin/candidate

Как мне настроить ветки релиз-кандидатов и релизов?
Любой проект начинается с первого коммита. Обычно это что-то простое, вроде добавления readme-файла. Я советую просто сделать ветки релиз-кандидатов и релизов из этого коммита. Что нам нужно получить, это первый merge-коммит с двумя родителями. Таким образом мы получим корректную историю. Так что подойдёт, в общем, любой коммит master-ветки. Почему бы не взять первый?

$ git branch candidate `git log --format=%H --reverse | head -1`
$ git checkout candidate
$ git push

Чтобы сделать ветку для релизов:

$ git branch release
$ git branch release --set-upstream-to=origin/release

Вопросы


А это разве описана не «модель кактуса»?
Вы можете подумать, что описанная в статье стратегия ветвления очень похожа на "модель кактуса", описанную Jussi Judin (тоже в качестве альтернативы GitFlow и тоже использующую ветку master для всей работы). Да, большей частью так оно и есть. Ключевым отличием является то, что Judin предлагает перемещать коммиты из ветки master в ветку релизов выборочно («cherry-picks»). Я категорически против этого. Выборочное перемещение коммитов — крайняя мера, которая должна использоваться в последнюю очередь при каком-то уж совсем катастрофическом состоянии master и большой необходимостью срочного релиза. Я предпочитаю использовать перемещение (rebase), а не слияние (merge). И избегать выборочности.

Другим отличием является существование в ThreeFlow ветки релиз-кандидатов, которую я принимаю как минимально необходимое зло. Лично моей целью является поддержание ветки master в таком состоянии, чтобы в каждый коммит можно было ткнуть пальцем и тут же спокойно выложить его в продакшн. Но я заметил, что многим коммандам трудно и некомфортно работать в таком режиме. Люди предпочитают иметь буффер в виде команды QA, которым нужно дать одобренный разработчиками билд («вот этот бери, а не вот тот, тот плохой») и получить от них фидбек о его качестве. И модель ThreeFlow даёт им такую возможность. В тщательно подходящим к качеству продукта командах различия между ветками релиз-кандидатов и релизов будут минимальными.

А это разве описана не GitFlow просто без веток для фич?
На самом деле я объяснял данную стратегию тем, кто до этого использовал GitFlow похожим образом: «Вы не используете ветки для фич, вся разработка идёт в ветке develop, которую мы теперь будем называть master, а то, что вы называли master мы будем называть веткой релизов». Основной идеей ThreeFlow была минимизация сложности. GitFlow поощряет создание новых сущностей (веток) по любому поводу (для фич, релизов, хотфиксов). Чем больше проект и чем дольше он идёт — тем страшнее выглядит его история. ThreeFlow стремиться минимизировать количество веток — никаких веток для фич или хотфиксов. Фичи пишутся в мастере, хотфиксы накатываются на релиз-кандидат или даже на релиз. И вместо кучи веток релизов у нас всегда есть то, что мы называем текущим релиз-кандидатом и текущим релизом. Всего лишь три ветки. Всегда.

Нам также не нужно придумывать систему именования веток (у нас их всего три и их имена константны): master, candidate, release.

Всегда есть ответ на вопрос «а куда положить мой код?». Если это хот-фикс проблемы на продакшене — в release. Если это фикс бага в релиз-кандидате — в candidate. Если это обычная ежедневная работа — в master.

Что на счёт ревью кода?
Если у вас есть правило ревьювить весь код перед тем, как он попадёт в основную ветку разработки, то будет логичным добавить ещё одну ветку (давайте назовём её develop — да, украдём это название у GitFlow, почему бы и нет). Итак, вся разработка будет идти в ней, а затем ревьювер будет переносить одобренные коммиты из неё в мастер (ну или просить их доработать). Конечно, нужно будет как-то отслеживать, что было перенесено, а что нет и это может вызвать затруднения. Нужно признать, что строгое следование идее ревью кода перед комитом в основную ветку может не сработать для вашей команды при использовании ThreeFlow или потребует дальнейшей адаптации этого подхода. Я слышал, что люди успешно применяли инструменты типа Gerrit для подобных целей, хотя сам его никогда не использовал.

Что на счёт кодовых баз, в которых хранятся несколько артефактов?
Во многих случаях в одной кодовой базе действительно хранится код, из которого может собираться несколько проектов. Эти индивидуальные артефакты сборки требуют отдельных циклов проверки QA-отделом, будут иметь отдельные версии релиз-кандидатов. Как ThreeFlow будет работать в данном случае?

Будет работать хорошо. Совсем недавно я как-раз работал в подобном проекте. У нас был один Git-репозиторий, из которого собиралось и деплоилось несколько разных артефактов. Решение очевидно: каждый артефакт добавляет в репозиторий по две ветки. Вы всё так же пишете весь код в master и работаете над фичами с помощью отключаемых флагов. Для этого вам не нужно знать, сколько и каких артефактов будет собираться из репозитория. Но вот дело доходит до релиза и здесь каждому артефакту становятся необходимыми свои ветки для релиз-кандидатов и релизов: foo_candidate, foo_release, bar_candidate, bar_release. Вот и всё.

Это масштабируется лучше, чем вы думаете. В одном из моих последних проектов у нас была одна большая кодовая база, из которой собиралось 4 разных артефакта. Кое-какой общий код, что-то индивидуально для каждого подпроекта — ну, вы понимаете. С одной стороны — 8 веток для релиз-кандидатов и релизов, плюс один master. Но с другой стороны — над каждым артефактом работала своя отдельная команда, и для каждой из них актуальными были лишь их три ветки, так что их общее количество мало кого волновало.

А можно как-то избежать набора дополнительных аргументов команд Git?
Одной из особенностей предлагаемого подхода является то, что почти каждая используемая команда действительно имеет дополнительные аргументы. Каждый раз, когда вы делаете слияние (merge), нужно не забыть добавить "--no-ff". Когда вы делаете релиз и тегируете его — я советую применять "--follow-tags" при пуше, чтобы сохранить теги в origin. Вы можете сделать так, чтобы эти теги применялись по-умолчанию:

$ git config --global merge.ff no

Теперь вы можете использовать команду merge без параметра "--no-ff" (он будет добавляться неявно)

Аналогично с тегами при пуше:

$ git config --global push.followTags true

Также можно настроить автоматический rebase при pull:

$ git config --global branch.master.rebase true

Вы можете даже сделать так, чтобы все новые ветки автоматически ребейзелись при pull в случае если вы используете локальные ветки для работы над отдельными фичами:

$ git config --global branch.autosetuprebase always

Также можно убрать ключ "--global" из вышеуказанных комманд, если вы хотите применить данные правила лишь к текущему репозиторию, а не ко всем вообще.

А можно я буду использовать слияние для релиз-ветки?
Ну, во-первых, вы свободный человек и можете делать всё, что захотите. Я просто описываю стратегию, которая хорошо работает для меня и некоторых других людей. Мне кажется, что она лучше, чем GitFlow (потому, что проще).

Во-вторых, да, если вам не нравится идея делать push с ключом "--force" и терять какую-то часть исторической информации, вы можете делать merge с ключом "--no-ff". Плюс тут ещё и в том, что не нужно запоминать разных способов переноса комита между ветками. Просто делайте себе merge --no-ff всегда, да и всё.

На самом деле первая версия ThreeFlow описывала как-раз именно такое поведение, слияние с параметром --no-ff для релизов. Это работало нормально, история была хорошо читаема. Единственное, что мне не нравилось, так это то, что артефакт сборки из релиз-ветки формально не был тем же коммитом, который до этого считался релиз-кандидатом и прошел через QA и утверждение к релизу. Получается, мы протестировали одно, потом сделали что-то другое и вот это другое релизнули. Плохо. Можно, конечно, заменить слияние на fast forwarding, но это тоже ведёт к потере информации, да ещё и не факт, что гарантированно удастся.

По моему мнению, push + force более ясно говорит о том, что содержимое релиза на самом деле не является веткой в терминологии цепочки наследуемых коммитов и не должно трактоваться так. Это просто указатель на актуальный код, который сейчас работает в продакшене. А сама ветка release просто указывает на серию тегов, которые когда-то выкладывались в продакшн. Ну и поскольку это всё-таки ветка с актуальным кодом, то вы всегда можете сделать хотфикс для продакшена прямо в ней.

Подытожим


Использовать Git без внятной стратегии ветвления — опасное дело. Поэтому возьмите вот эту:

  • Есть три ветки: master, candidate, release
  • Работаем в master. Все новые комиты добавляются с помощью перемещения (rebase).
  • Фичи в процессе разработки отключаются флагами. Включаются тогда, когда будут готовы.
  • Когда приходит время сделать релиз-кандидата — он делается из master путём слияния (merge с ключом "--no-ff") его коммитов с веткой candidate
  • Все баги, которые QA найдут в релиз-кандидате, фиксятся прямо там и затем вливаются (тот же merge с ключом "--no-ff") в master
  • Когда принимается решение выложить релиз-кандидат в продакшн, он пушится с ключом "--forced" в ветку release
  • Все хотфиксы для продакшена делаются на ветке release и затем вливаются в candidate и master

image

Вот и всё. По моему мнению ThreeFlow это одна из простейших стратегий ветвления для Git, которая в то же время даёт вам всё необходимое.

Попробовали и вам подошло? Попробовали и не подошло? Почитали описание и считаете того, кто предлагает использовать "--force" в качестве регулярной операции, полным идиотом? Не стесняйтесь рассказать об этом в комментариях!

Комментарии (50)


  1. fshp
    29.12.2017 00:25

    Проблема в том, что "фича" это не всегда кусок кода. Это может быть большая задача по смене интерфейсов, раскиданных по куче разных файлов. И просто так обернуть в if это не выйдет.


    Когда мне говорят: "Бэкпортни вот эту фичу в релиз месячной давности", то я благодарю коллег, себя и Линуса за то, что у нас фичи в отдельных ветках. Я просто черрипикаю диапазон коммитов, относящихся к этой фиче, а не ищу нужные изменения в фарше из несвязанных коммитов в мастере (в вашем случае).


    Так же мне не понятно почему хранить ветку на сервере это плохо? Что бы мне поработать на другой машине, мне нужно пушнуть ветку на свой хостинг?


    Ну и последнее. Каждый рабочий день я начинаю с того, что делаю ребейз своей текущей задачи на dev. Это ведь не сложно, верно?


    1. ZyXI
      29.12.2017 09:32
      +1

      Ну и последнее. Каждый рабочий день я начинаю с того, что делаю ребейз своей текущей задачи на dev. Это ведь не сложно, верно?

      Если там конфликты в том месте, которое вы активно (т.е. в нескольких коммитах) редактировали, то вы имеете хорошие шансы получить цепочку конфликтов при rebase и делать кучу бесполезных действий для их решения. И даже если конфликтов как бы нет, есть шансы сделать изменения некорректными и не заметить: к примеру, если функция была удалена в файле, который вы не меняли, а вы её у себя используете. Заметить при тестах и исправить ситуацию одним изменением легко, исправить по всей ветке — не очень. Поэтому я в своё время уговорил команду Neovim, что для больших PR можно (и нужно) делать merge, а не rebase. А у себя (и в wiki Neovim) имею команду git ri, которая как бы rebase --interactive, только ничего никуда не переносит, лишь даёт отредактировать историю либо с начала ветки, либо с последнего merge.


      1. nick_volynkin
        29.12.2017 09:47

        Думаю, что «это ведь не сложно» — это сарказм.


        Не могли бы вы поделиться кодом вашей команды (алиаса) git ri?


        1. ZyXI
          29.12.2017 10:19

          https://github.com/neovim/neovim/blob/master/CONTRIBUTING.md#pull-requests-prs


          Мой отличается только тем, что я вместо sh написал dash (у Gentoo в качестве sh стоит bash, а мне нужно проверять такие вещи на чём?то поближе к POSIX shell).


        1. fshp
          29.12.2017 12:18

          Не сарказм.


      1. fshp
        29.12.2017 12:18

        цепочку конфликтов

        Для этого был придуман rerere.


        исправить по всей ветке — не очень

        Делайте коммиты с исправлениями, затем rebase + fixup.
        Но серебренной пули не бывает, если ветка действительно огромная, то почему бы и простой мерж не использовать?


        1. Bonart
          29.12.2017 12:37

          Чего только люди ни придумают, лишь бы не видеть настоящую историю изменений.


          1. fshp
            29.12.2017 12:55
            +1

            А зачем нужна настоящая история? Кому какое дело до того, от какого коммита я начал работать? Зачем кому-то нужен в истории коммит и моими отладочными сообщениями?
            История должна быть читаема, каждый коммит должен собираться и работать. Это главное.


            Я не топлю за линейную историю, нет. Я не мержу ветки через ff. Я только поддерживаю актуальность своей через rebase.


        1. ZyXI
          29.12.2017 23:37

          rerere у меня включён. Но я ни разу не видел, чтобы он сработал против «цепочки конфликтов», и не знаю, с чего бы он вообще должен бы сработать на ней: никогда нет никаких «recorded resolutions» для первого rebase (а цепочка конфликтов, тем не менее, есть), их никогда и не будет, если в начале цепочки конфликтов после следующего rebase в конфликтующий код вносятся изменения (что, скорее всего, так).


          А «почему не использовать»: не везде разрешено. Кроме того, конкретно в Neovim для меня изменение означало, что я всегда могу использовать merge для больших веток, независимо от того, есть ли там мешающие конфликты или merge требуется для других целей (например, потому что иначе не пройдёт проверка clint.py). git ri я и так всегда использую до последнего во всех проектах (пока у меня нет причин хотеть быть основанным на последнем master).




          Исправления, затем rebase+fixup? Чем это поможет? Вот люди переименовали в master функцию foo в функцию bar, я её использовал в коммите X, а потом в коммите Y решил, что здесь больше подойдёт функция baz. Если нет конфликтов после rebase и нет проваленных тестов (а их нет, т.к. переименованная функция более не используется, а использовалось в новом коде, который в master отсутствует и конфликтов не добавляет), то «исправления, затем rebase+fixup» не поможет, нет причин делать исправления. Поможет, если я буду запускать тесты после каждого коммита из rebase, но это именно то, что я имею ввиду под «не очень».


          И, самое главное: зачем нужна «настоящая история изменений»: попробуйте делать bisect с точностью до конкретного коммита конкретной feature ветки, а не с точностью до коммитов слияний. Если вы пропустили такую проблему при rebase, то у вас куча коммитов в результате не скомпилируется, а более всего подверженные проблеме длинные ветки так же чаще всего добавляют проблем.


          Поэтому никогда никаких rebase, пока я могу их не делать. И git ri, поскольку авторы git обожают сваливать в одну кучу слабо связанную (семантически) функциональность, ещё иногда её даже не позволяя использовать отдельно. К тому же, даже если я хочу именно изменить историю и перенести ветку, гораздо проще сначала только изменить историю, потом только перенести ветку: так легче исправлять свои ошибки, и легче их вообще увидеть (у меня тут конфликт, потому что я промахнулся с тем, куда делать squash/fixup, или потому что в master что?то поменяли?).


          1. fshp
            29.12.2017 23:47

            Сколько разработчиков, столько и мнений. И нет ни одного правильного. Кому как удобнее.


            я её использовал в коммите X, а потом в коммите Y решил, что здесь больше подойдёт функция baz.

            Значит это изменение нужно добавить в коммит X. Например разработчики git или linux не примут такой патч в одной серии, а попросят причесать историю.


            1. ZyXI
              30.12.2017 01:18

              Здесь не только «кому как удобнее», есть ещё и объективные показатели. У разных разработчиков разные приоритеты этих показателей, а в разных проектах разные вероятности различного рода конфликтов, но «правильный» ответ под конкретную ситуацию найти можно. Возможно, наши споры помогут кому?то это сделать.


              По поводу добавления изменения в X: я иногда оставляю изменения с неправильной концепцией в ветке просто чтобы показать reviewer’у, что «я проверил эту альтернативу (другую зависимость, другой подход к созданию новой функциональности, …), и она не работает». Пояснения либо в описании изменения, либо в комментариях PR, но не в комментариях кода, если только нет оснований полагать, что кто?то не должен пытаться использовать эту альтернативу опять. Тут чисто вопрос какие формы коммуникации в команде приемлемы — где провальные решения предполагается документировать и предполагается ли их документировать вообще; ну и вопрос, насколько причёсанная история нужна, тоже.


  1. mjr27
    29.12.2017 00:51
    +1

    Мне казалось, первое апреля далеко.


    Если по пунктам:


    1. А чем собственно плоха система "1 задача — 1 ветка"? Проблема с merge conflicts в случае единой ветки разработки никуда не исчезает же, просто тонким слоем размазывается по всей истории коммитов. Ну и плюс правило "последний коммит в фичаветке — это мердж из мастера" это уже давно хороший тон.
    2. Код ревью каждого отдельного коммита — это то, с чем справится pyflakes / статический анализатор. Без общего контекста задачи ревьюер на большее не способен.
    3. Точечный перенос коммитов из ветки develop в master не работает. Нарушение хронологии приводит к тому, что ответственный за это будет 40 часов в неделю пытаться разрулить какие-то конфликты. Оно ему надо?
    4. if(FEATURE_ENABLED) работает только в вакууме. Пример — практически любое изменение структуры БД.
    5. Один поломанный коммит ленивого разраба внезапно убивает тесты / CI для всех последующих. За день до релиза вероятность этого увеличивается до 100%.
    6. И наконец, где при такой схеме бедному разработчику хранить коммиты с шикарными описаниями "test" и "dummy", особенно если он работает из более чем одного места?

    В качестве бонуса: могу ошибаться, но изменившаяся индентация вокруг if(FEATURE_ENABLED) делает бесполезным git blame.


    P.S: CVS в 2017? Не-не-не, Дэвид Блейн


  1. mwizard
    29.12.2017 01:03

    В плюс ко всему, что написали господа выше — если так сильно хочется иметь отключаемые в реалтайме фичи — нет никакой проблемы сделать if (SOME_FEATURE) вашим первым коммитом в собственной feature-ветке. И да, если у вас "долгоживущие ветки", значит вы плохо поделили задачи, и они у вас слишком большие. Разделите задачи так, чтобы каждую можно было сделать за день, а мердж конфликтов дневных изменений — не такая и архисложная задача, как показывает практика.


  1. vism
    29.12.2017 01:52

    В оригинале эта статья написана была 9 апреля (может весь апрель никому не верь?).
    И выше написанных вопросов ему не задали.
    И с автором тут не по обсуждать тоже, перевод…

    Теперь не опытные ребята из ру-сообщества прочитают эту статью и внедрят где-то, мде :(
    а тем временем 12+ и 1- у статьи, 14 в закладках…


    1. JekaMas
      29.12.2017 19:24

      Господи, и правда ведь… Ждем волны вопросов на собеседованиях: " вы используете gitFlow? Фу, это вызывает кучу проблем, о которых я читал".


  1. Bonart
    29.12.2017 03:06

    Статья лучших советов для претендентов на премию Дарвина.
    Худший вид костылей (ветвление на флагах), худший вид фальсификации истории изменений (rebase), худший вариант покрытия тестами (удвоение на каждую фичу).
    Следуйте всем этим рекомендациям, если хотите развести Г-код, провалить проект и научиться повторять это на регулярной основе со 100% гарантией.
    Этот метод контроля версий следовало бы назвать методом трех хромосом, но такое незаслуженно обидит страдающих синдромом Дауна…


    1. mwizard
      29.12.2017 08:45

      Печально, что при всем этом материал плюсуют и вносят в закладки. Не хотелось бы попасть в команду, где в качестве рабочего процесса используется это.


      1. Bonart
        29.12.2017 11:23

        У корпоративных блогов до некоторого порога число плюсов и добавлений в закладки никак не коррелирует с качеством статьи.
        Так что все не настолько плохо.


      1. third112
        29.12.2017 13:23

        Возможно многие плюсуют и вносят в закладки не потому, что согласны с автором, а потому что сделана попытка выявить и обобщить проблемы. Некоторые поверхностные публикации про git создают обманчивое впечатление беспроблемности.


    1. tangro Автор
      29.12.2017 12:27

      Ну, а Вас не удивляет, что примерно так же, например, Google пишет Chrome? И trunk-based разработка у них есть, и флаги для включения фич — об этом есть видео на Youtube, можете посмотреть.


      1. Bonart
        29.12.2017 12:36
        +1

        Нет, не удивляет. В гугле тоже могут использовать плохие практики, как и в любом другом месте.


      1. symbix
        30.12.2017 03:14

        А еще у гугла и фейсбука монорепозитории на стопицот гигабайт. И что теперь, всем так же делать предлагаете? :-)


  1. mad_nazgul
    29.12.2017 07:16

    Я честно говоря проблемы с GitFlow встречал только при работе с говнокодом.
    Когда был один God Object, который изменялся 5 программистами при добавлении новых фич.
    Т.к. мне не хотелось лезть в ад merge, я свои фичи выделял в отдельные классы и старался их вызывать минуя God Object.
    В таком виде GitFlow хорошо работал.
    Я не мешал другим, они не мешали мне. :-)


  1. ggo
    29.12.2017 09:33

    Любой подход хорош в своем контексте.

    Описанный здесь подходит в простых проектах в редкими изменениями.
    В сложных проектах с большим количеством изменений, не могу представить альтернатив nvie.com/posts/a-successful-git-branching-model.

    Соотношение простых проектов к сложным? Думаю, большинство простые. С учетом парадигм devops, Conf-as-code, Infra-as-code репозитарии плодятся как кролики. Далеко не во всех нужно применять строгие санитарные правила.


    1. nick_volynkin
      29.12.2017 09:50

      Для простых проектов есть GitHub Flow и GitLab Flow, но они и на сложных хорошо работают.


      Правила хороши тем, что стандартизируют процесс. Если у вас много проектов и в каждом свои правила, поддерживать их станет значительно сложнее.


    1. rustler2000
      29.12.2017 12:18

      В вашей ссылке и есть gitflow


      1. ggo
        29.12.2017 18:11

        Да, это как бы первоисточник.


  1. petuhov_k
    29.12.2017 09:35

    А как будут выглядеть эти if-ы если разные фичи меняют один код? Что-то не совсем понятно, что и в какой if пойдёт и как потом включить обе фичи одновременно.


    1. nick_volynkin
      29.12.2017 09:51

      Видимо, будет комбинаторный взрыв if-ов.


  1. symbix
    29.12.2017 09:49
    +1

    Проблемы со слиянием (merge или rebase — не суть) веток возникают тогда, когда ветки живут долго и сильно расходятся с master-веткой. Если дробить мелко, проблем вообще не возникает — если конфликты и есть, то они почти всегда тривиальны. Если же возникает редкая ситуация, когда конфликты нетривиальны, то в любом случае мержить должны оба разработчика вместе — каждый добавил что-то свое, и тут уже надо разобраться, как правильно объединить изменения (которые вполне могут оказаться противоречивыми). При этом можно легко параллельно работать хоть с 10 фиче-ветками, что для крупного проекта — совершенно нормальная ситуация.


    С фичефлагами получится, что сначала оба разработчика пишут if (feature_a) и if (feature_b), а потом кому-то (очевидно, обоим вместе) придется написать if (feature_a && feature_b). В случае с нетривиальным конфликтом ситуация никак не улучшается. Но при этом:


    Если ведётся разработка двух взаимозависимых фич — на время разработки вам понадобится 4 теста для всех их комбинаций.

    А если ведется разработка 10 фич, при этом взаимозависимость тривиальная и все мержится без конфликтов? :-) А что делать со всякими декларативными штуками, типа конфигов в json?


    Еще, такой подход фактически вносит запрет рефакторинга. Как метод переименовать, например?


    1. nick_volynkin
      29.12.2017 09:54

      Добавлю, что слияние без конфликтов совсем не означает, что полученный код не содержит багов. Как выше писали, если в одной ветке функция удалена или переименована, а в другой ветке её код никак не менялся, вполне вероятно, что после слияния код сломается.


      1. mad_nazgul
        29.12.2017 09:59

        Поэтому работать надо через «интерфейсы»
        Для чего придумали «страшное» слово «инкапсуляция»
        <:o)


        1. Dim0v
          29.12.2017 11:51

          Интерфейс не является чем-то неприкасаемым. В одной из веток из интерфейса точно так-же могут удалить или переименовать метод и сломать тем самым код после бесконфликтного мерджа с другой веткой, использующей этот удаленный метод.


          1. mad_nazgul
            29.12.2017 13:16

            «Интерфейс» это «контракт», если он меняется, то это значит, что поменялось что-то глобальное. Т.е. как минимум перед «рубить с плеча» изменения должны помечаться как depricated.
            А при «переименовании»/«удалении» возможно нужно просто создать новый интерфейс?!


            1. symbix
              29.12.2017 18:10
              +1

              Если это не публичное API, а исключительно внутренний интерфейс, зачем такие сложности? Обсудили в команде, взяли и поменяли.


              Другое дело, что, да, старый удобно ненадолго оставить для упрощения автоматизированного рефакторинга в ветках перед мержем.


      1. symbix
        29.12.2017 11:44

        Естественно. Поэтому
        1) делается ветка production != master. (Ну или develop и production = master, тут уж дело вкуса)
        2) прежде чем вливать фиче-ветку в мастер, всегда сначала делается rebase на мастер, после чего заново прогоняются все тесты.


  1. AstarothAst
    29.12.2017 11:21

    А зачем тут вообще нужен git? Сетевая шара, куда разрабы копипастят свои наработки, реализует все ровно тоже самое, только без git :D


    1. fshp
      29.12.2017 12:23

      Google Doc. Там и версии хранятся, и видно кто над чем работает!


    1. mjr27
      29.12.2017 13:29

      Когда-то, на рубеже веков, мы так и работали — rsync на машины остальных разрабов.


      Читал и ностальгировал.


      1. fshp
        29.12.2017 14:53

        Rsync в 98 году появился, через 8 лет после CVS.


        1. mjr27
          29.12.2017 16:44

          Но тем не менее, в том коллективе, где я был молодым специалистом, было принято именно так. Видимо, хипстеры и тогда были.


          С ужасом представляю, что они использовали в 97


          1. fshp
            29.12.2017 17:05

            А как у вас мерж происходил то? Rsync просто перезатирает файлы, он не накладывает патчи.


            1. mjr27
              29.12.2017 17:39

              — Мужики, я работаю в oic/lib/
              — Хорошо


              ....


              — Мужики, никто oic/lib/bibl_stroki.c не трогал последние два дня?
              — Я!
              — И я!
              — Мля!


              Как-то так.


            1. mjr27
              29.12.2017 17:42

              Когда я уходил, тимлид (точнее, Ведущий Специалист) что-то начинал разбираться с SVN. Но это уже без меня было.


  1. edubrovka
    29.12.2017 12:28

    1. Использование какой бы то ни было стратегии без понимания какие проблемы она решает и какие недостатки и ограничения имеет тоже не самый лучший вариант. У нас были прецеденты, когда люди использовали гит и git-flow, но совершенно не понимали зачем. И на самом деле им это было не нужно. Мастера с тегами хватало за глаза.
    2. Выбор стратегии — это больная тема. После некоторых реформ мы для себя выбрали слегка расширенный GitFlow, так как стратегии "хватит мастера" и "каждому заказчику по ветке" совсем не работали и создавали еще больше хаоса. Пришлось некоторых долго обучать
    3. ThreeFlow для нас не подходит, так как есть внешние зависимости и инсталляции очень старых версий, которые по лицензионным причинам получают только критические фиксы, но никак не новые фичи. Хотя для новых продуктов есть мысль использовать ***Flow: по ветке на каждое окружение на котором будет выполняться код. Например, dev, alpha, beta, pre-release, release. 5 веток лишь для примера, каждый может для себя разыграться от OneFlow, и только ставить тэг на то, что пошло в продакшен, или 100500Flow, если хватит ресурсов компании. :)
    4. И да, интеграция feature веток может быть болью (но мы не испытываем проблем с этим, главное интегрироваться часто), но и отключенная флагом функциональность тоже может принести немало боли, когда код покрывается if-ами, конфиги флагами, и неявно влияет на выполнение включенного.


  1. ab0ris
    29.12.2017 12:28

    По-сути стараемся решить одну проблему: мердж двух сильно отличающихся веток в develop может вызвать много конфликтов

    Добавляя несколько новых:
    — Свитчи: не только будут разбросаны по всему проекту, но не понятно как использовать с html, css файлами и другими, которые не содержат кода
    — Также подразумевает что часть времени разработчиков будет тратиться на очистку проекта от старого кода: код со свитчами, ссылки на устаревшие файлы и тд, что тоже может быть не весьма очевидно
    — Невозможно оттестировать и зарелизить только одну фичу продолжая разработку над другими
    — GitFlow удобно использовать с CI: 'develop' деплоится на staging автоматически при мердже и фичи можно демонстрировать продуктовой команде. В вышеописанном сценарии это невозможно делать не переключая свитчи и не делая новый комит, тем самым сводя на нет идею production-ready мастера


  1. masb
    29.12.2017 12:28

    ThreeFlow крайне усложняет ревью и в мастер ветке всегда потенциально неработающий код. Куда надежней для каждой фичи создавать отдельную ветку, над которой легко можно сделать ревью и смерджить в девелоп.


  1. dude_sam
    29.12.2017 14:11

    Неплохой механизм, но в каких-то замкнутых на себя продуктах или маленьких утилитах а ля shell скриптов.
    А когда, каскадно изменяюсятся схемы БД (а то и 2-3), пакеты etl, аналитические кубы и отчёты, rest-сервисы и т.д и т.п. и e.t.c. и всё должно проходить через жёсткий code review, то git flow предпочтительней.
    #imho


  1. norlin
    29.12.2017 16:54
    +1

    работаю в компании, которая использует такой подход. С маленькими фичами это более-менее норм (главный минус – засорение кода всякими временными/недоработанными фичами). Но если задача вырастает за пределы одного файла – то начинается ад. Флаг надо пробрасывать везде, другие девелоперы вынуждены вместо одного мерджа постоянно поддерживать две ветки кода, "выключенный" код, тем не менее, легко может что-то поломать. Если речь про web/JS проект – не получится добавлять "дебажный" код, т.к. он станет виден пользователям. И т.д. и т.п.


  1. insensible
    29.12.2017 20:04

    Работаем два года по GitFlow (на самом деле, rebase flow) в команде из 5 разработчиков над относительно крупным проектом с высокой плотностью как мелких, так и крупных задач. Все очень легко и понятно, возможные нечастые конфликтные ситуации (случаются не часто) обсуждаются в команде и легко решаются. Накладные расходы на содержание feature веток минимальные. Поэтому я как то в упор не вижу проблем, описанных автором и не понимаю, зачем так жестко критиковать хорошо зарекоммендовавший себя за много лет механизм.