Недавно заспорил о том как стоит бранчится и обнаружил внезапно, что тема не так проста, как мне казалось изначально. Естественно, полез в инет и почитал о разных стратегия и обнаружил таки пару вещей, которых не знал. Чем и спешу поделиться.
Кстати, не могу найти нормальный перевод слову бранчится. Как действие слово «ответвляем» меня вполне удовлетворяет, а вот применительно к такому предложению «как стоит ответвляться/ветвится» звучит странно…. Мдя…Будь же ты, проклят герундий.
Ладно, назад к теме. Увы, серебряной пули среди стратегий нет. Так что, пробежимся по стратегиям.
Кстати, сразу напишу, что идет речь о том как бранчиться для создания постоянных (а не временных) веток. То есть, я не затрагиваю вопрос о том, что если мы делаем какие-то крупные и длинные изменения, что стоит их делать в отдельной ветке, которая будет удалена по окончанию работы.
— Простейшая стратегия
Если есть один человека, один продукт и одна версия то можно вообще не бранчиться. Все делается в trunk’е, ну разве что, можно помечать стабильные билды и релизовые билды.
Ну, естественно такая халява долго не держится. Буквально через пару выпущенных версии уже появляется необходимость сделать hotfix какому-то из ваших заказчиков и тогда приходится переходить к следующему этапу.
— Стратегия разумного минимума
Бранчимся только тогда, когда это уже абсолютно необходимо. То есть, как в прошлом методе ведем все в trunk, если надо сделать hotfix, то ответвляем от версии, которая отдана заказчику и там делаем исправления и мержим назад в trunk все исправления.
— Стратегия обычной средней фирмы
Естественно в какой-то момент, становится понятно, что для любой выпущенной версии нужно будет сто процентов делать хоть какие-то исправления, так что можно сразу нарезать бранчи для каждого релиза наперед. Да и плюс, людей прилично, кто-то работает над исправлением для предыдущей версии, кто-то работает над новой функциональность в trunk’е.
Вот тут уже начинаются расхождения.
а) Ранний бранчинг
Ветка создается в тот момент, когда начинается любая работа над релизом и вся работа делается в этой ветке, иногда это мержат в trunk (чаще всего в стабильных точках). Я знаю, зачем это делают на больших размерах (чуть позже напишу), но не совсем понимаю, зачем это делают в средних фирмах. В средних фирмах получается, что trunk оказывается фактически лишним.
б) Поздний бранчинг
Ветка создается только в тот момент, когда уже нужно стабилизироваться и что-бы туда не сыпали случайные изменения. Кстати, это стандартная стратегия из SVNBook.
Кстати, на этом размере появляется еще один вопрос в перпендикулярной плоскости. Как организовывать структуру веток. Можно так
/Product1/trunk
/Product1/branch1
/Product1/branch2
/Product2/trunk
/Product2/branch1
…
а можно
/trunk/Product1
/trunk/Product2
/branch1/Product1
/branch1/Product2
…
Мне по душе больше первый метод. Я считаю, что продукты должны быть по возможности независимые друг от друга и поэтому должна быть возможность отдельно их выпустить, а вывод должна быть возможность отдельно бранчить Product1 и отдельно Product2.
Если же они насколько связаны, что не имеет смысла их выпускать отдельно, то скорее у вас не два продукта, а один продукт с несколькими модулями.
Кстати, именно второй вариант (где бранчат все продукты вместе) для меня был не то, чтобы новостью, но я раньше с ним не сталкивался.
Ну и последняя добавка. Опять же с любой веткой для релиза можно поступать теме же описанными стратегиями.
— Стратегия крупной фирмы
А вот тут собственно говоря наступает самое интересное, то о чем я прочел.
Предположим, что у нас есть большое количество веток в которых мы ведем разработку. Например для версий 5.5, 6.4 и 6.5 и 7.0. Ну и еще параллельно ведется пару долгоиграющих проектов, который затрагивают несколько продуктов. Что мы хотим — иметь возможность перетягивать исправления. Условно говоря фиксы из 5.5, чтобы можно было применить в 6.4, 6.5, 7..0 и долгоиграющих проектах.
Проблема состоит в том, что кросс-бранчинговый мержинг — штука и сложная и опасная. И если начать туда сюда напрямую тасовать изменения, то можно во первых легко запутаться, а во вторых огрести много приключений.
Собственно откуда берутся приключения? Первое, мы плохо можем знать другую ветку. Человек работающий над 5.5 достаточно плохо осведомлен о специальном проекте, который собран из частей 6.5 и 7.0. Получается если он закидывает изменения — то он не знает того когда. Если же люди работающие на этим специальным проектом пытаются вытянуть изменения из 5.5, то они тоже могут не слишком хорошо знать код.
В таком случае весь мержинг делают через ветки от которых были порождены текущие. То есть, чтобы из 5.5 перенести в 6.5 то из 5.5 мержат в 5.0, из 5.0 мержат в trunk, trunk мержат в 6.0, 6.0 мержат в 6.5.
С одной стороны, количество мержингов становится ГОРАЗДО больше с другой стороны, мержиться с веткой от которой была порождена текущая всегда гораздо проще и гораздо больше шансов, что нам знакомы обе ветки.
Далее, мержинг проводится в тот момент, когда ветка стабильна (идеально в релизовом состоянии). И еще, как только от ветки что-то бранчится, то после этого в этой ветке не ведется никакой разработки. Условно говоря, как только сделан branch 2.0, то в trunk не кладется никакой код кроме мержей. Таким образом в trunk мержится только стабильный код и поэтому trunk остается в достаточно стабильном виде.
Что-то я замудро описал. В целом, разработки делаются только в «листах» дерева (веток от которых не было ничего ответвлено) и для переноса изменений, мы идем по дереву вверх и вниз. Такой метод сложен, но зато позволяется все НЕ листы ветки держать в стабильном состоянии. И это снижает вероятность проблем при следующих мержах.
Вот собственно эта схема для меня и была новая.
По-поводу структуры веток — вообще-то если продукты действительно разные, то более естественно раскладывать их по отдельным репозиториям, а не по под-каталогам — и администрировать легче и в логах не будет «каши»
М… Не совсем вижу в чем польза в раскладыванию по разным репозиториям. Точнее так — нагрузку на сервера можно уменьшить.
Но в целом, каша в логах будет только в том случае, если все лежат по папкам и пытаешься сделать show log на весь репозиторий.
Очень часто каждая команда разработчиков выбирает свой собственный подход для создания веток в репозитории. Поэтому поделюсь собственным опытом. Конечно он базируется на SVN Book и скорее всего в нем нет ничего хитрого и специфического.
Структура репозитория (по SVN Book):
Product1/
trunk/
branches/
branch1
...
tags/
tag1
...
Product2/
...
Каши с логами не будет если указывать URL к конкретному продукту и более того к конкретной ветке (или транку).
Trunk — это НЕстабильная ветка. В нем ведется разработка последней версии продукта, которая находится в Alpha или Beta стадии. Как только продукт достигает стадии RC, создается ветка соответсвующего релиза. В идеале в этой ветке ничего не делается и она нужна только для пост-релизного сопровождения данной версии (создание сервис-паков).
Существует еще один нюанс при создании ветки для версии продукта. Если необходимо вести разработку нескольких версий продукта одновременно (а это я настоятельно рекомендую всячески избегать), то ветку для версии можно создать на более ранних стадиях (Alpha или Beta). Но при этом всегда стоит помнить, что самая последняя версия остается в транке.
Любые изменения в версионных ветках ОБЯЗАТЕЛЬНО должны быть перенесены в транк (причем «один фикс — один мерж», а не пачкой раз в неделю или месяц или при стабилизации ветки). Изменения из веток для более ранних версий переносить в ветки для более поздних версий рекомендуется делать только по необходимости. Возможны ситуации, когда исходный код веток отличается радикально, тогда этот фикс выполняется заново, но уже базируясь на коде новой ветки. Иногда бывает, что некий дефект из ранней версии не применим к более поздней. Тогда ничего переносить не нужно.
Ответвления от версионных ветвей допускаются. И здесь работает тот же принцип что и с транком. В них можно вносить изменения. Они становится некими аккомулятивными ветками.
Пример:
Ведем разработку версии 5.0. Работаем в транке. Достигнута стадия RC — реализована вся функциональность для этой версии (это требование для завершения Alpha стадии) и исправлены все найденые дефекты (это требование для завершения Beta). В этот момент создается ветка v5.0. И в этот момент можно приступать к версии 6.0 в транке. Если вдруг необходим некий хот-фикс для 5.0 версии, то он выполняется в v5.0 ветке и переносится в транк. При этом новый 5.0 RC билд строится из v5.0 ветки. Если, через некоторое время, будет принято решения выпустить 5.1 сервис-пак для версии 5.0 (а на этот момент новых билдов 5.0 версии быть не может), то изменения вносятся в ветку v5.0 (плюс эти же изменения обязательно переносятся в транк) и когда 5.1 сервис-пак достигнет стадии RC — создается ветка v5.1 из ветки v5.0. Если вдруг потребуется хот-фикс для 5.1 версии, то как и прежде, эти изменения (в ветке v5.1) обязательно должны быть перенесены в транк и обязательно в ветку v5.0. К сожалению, здесь есть небольшая путаница, поскольку ветка v5.0 плавно превращается в ветку для версии 5.1 (хотя название ветки остается v5.0), а затем в ветку для версии 5.2. Возможно эту путаницу можно решить с помощью промежуточной ветки v5.x, но это уже по вашему желанию.
Насчет каши с логами — солидарен. Это проблема, только если пытаться работать со всем репозиторием, вместо отдельных его веток.
Насчет нестабильного Trunk — это четко по SVNBook.
Насчет переноса по фиксам — хорошее замечание. Я тоже так привык работать, когда фикс сразу кладется в release branch и в trunk, вместо того, что накапливается много в release branch’е.
Насчет примера. Мы точно так же и работали. И кстати, та же самая ситуация с тем, что возникается путаница, что в одной ветке может получить и 5.0 и 5.1 и 5.2.
Но это в целом решается с помощью tag’ов. Хотя для средних компаний редко нужно выпускать фиксы а-ля 5.1.1, когда идет работа уже над 5.2 (все на ветке 5.0), так что просто кладут в конец 5.0 и оттуда выпускают.
начинайте с простейшей стратегии