Ветвление в Subversion


Рано или поздно руки любого кто однажды притронулся к SVN, дотягиваются и до одного из самых мощных инструментов этой системы — ветвления. Поразительно как кажущаяся видимая сложность этого механизма оборачивается эффективным решением многих задач, которые возникают при любой разработке.

Данная статья основывается на предыдущей, а также на мысли, что лучший способ что-либо понять — сделать это самому.

Что такое ветвление

Ветвление (branching) SVN — это именно то, что стоит за этим словом: создание ветвей. Ничем большим или меньшим это понятие не обладает.

То чем мы занимались до этого (см. статью «Введение в Subversion»), было движением по единственной координате — времени. Однако представьте себе некий проект, в котором одновременно идет работа по многим направлениям разом, но в неком одном общем. Например, это может быть разработка «ПрограммаХ», когда один отдел работает над известными ошибками, другой занимается разработкой плагинов, а третий работает по главному вектору разработки. Вся работа, проделываемая ими, сказывается на разнице «ПрограммаХ» от самой себя, например, в какой-то момент времени в основной версии программы содержится некая ошибка, в то время как в версии отдела по борьбе с ошибками ее уже нет.

краткий итог:

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

Зачем нужны ветки

Представьте себе, что вам поручили оптимизировать схему подключения к основной программе плагинов. Или другой случай — в вашей среде разработки принято делать технический релиз в субботу вечером, чтобы группа по работе над качеством могла приступить к своему делу в понедельник. Как бы то ни было, из-за того, что потребуется внести много мешающих основной линии разработки изменений, самое эффективное решение — создание отдельной ветки, в то время как в основной не прекращает кипеть работа по своим графикам. Суть в примерах только в том, что они порождают различные «типы» веток. Таких несколько:

  • Функциональные ветки;
  • Ветки релизов;
  • Метки.
это принципиально:

Тип ветки — условное понятие. С точки зрения SVN любая ветка — это обычная директория в хранилище. То, что делает эту директорию тем или иным типом ветки находится в вашем ее восприятии. Это порой путает, но является исключительно простым по сути, надо только запомнить.

Именно наличие нескольких типов веток и объясняют странную на первый взгляд структуру хранилища по умолчанию:

Структура хранилища по умолчанию

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

Функциональные ветки

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

Метки

Может возникнуть вопрос о причине вынесения меток в отдельную директорию. Дело в том, что обычно это просто ссылка на ту или иную правку какой-либо ветки. Почему ссылка? Вспомните, что говорилось о «дельте данных» в предыдущей статье. Точно такая же схема используется и при создании ветки, а в случае метки — копии ветки. Т.к. при создании метки ничего нового не появляется «дельта» по сути равна нулю и создаются просто ссылки на объекты ветки из которых создается метка. Таким образом, метки уменьшают многословность и неопределенность вида «ветка branches/version_3.14@364» сводя ее к примерно следующему «tags/october» — октябрьский релиз. Данные при этом не копируются, хранилище не растет вширь, навигация по множеству релизов упрощается. Совокупность меток, релизов, а при необходимости и функциональных веток, помогает организовать работу для решения задачи из второго примера в начале этой главы.

краткий итог:

Ветки — простые директории. То, что заставляет директории быть ветками различного типа, кроется в нашем их восприятии. Однако именно это восприятие и дает нам возможность решать широкий круг задач, будь то борьба с ошибками или предоставление клиентам регулярных релизов. Мы помогаем сами себе, а ветки — лишь инструмент.

Использование веток

Разберемся с ветвлением на примере движка блога WordPress, сопровождая процесс знакомства временными диаграммами правок хранилища и иллюстрациями изменяющейся файловой структуры проекта.

Пусть нашей целью будет обновление WordPress v2.2.3 на v2.3.0. Также предположим, что в нашем распоряжении имеется хранилище, расположенное по адресу http://127.0.0.1/svn/web-dev причем со структурой директорий как это рассматривалось выше. Главная ветка разработки с номером правки 35 содержит WordPress v2.2.3.

уступка реальности:

для тех, кто имеет дело с WordPress не секрет, что переход на v2.3.0 сопряжен также и с изменением БД ввиду введения в понятие движка меток-тегов. Также не во всех случаях обновление возможно простым «копированием поверх». Для упрощения примера опустим эти тонкости. Вопрос синхронного закрепления в хранилище, как файлов, так и содержимого БД — предмет дальнейшей статьи «smarty commit».

Таким образом, начальная картина хранилища и ее временная диаграмма будут иметь вид:

Начальное состояние хранилища

Каковы будут действия для выполнения поставленной задачи:

  1. Создадим метку tags/v223 для текущего состояния trunk — по сути именованный backup;
  2. Создадим ветку branches/v230 на основе trunk — поле деятельности, в котором и будет происходить файловые операции по обновлению WordPress;
  3. Выполним все операции, требующиеся для обновления;
  4. Сольем trunk и branches/v230 чтобы в будущем вести в trunk работу над v2.3.0.

Создание

Создание ветки осуществляется обычным копированием того, что будет ответвлено (обычно это trunk) в ту директорию хранилища, которая соответствует типу создаваемой ветки.

чтобы никто не ошибся:

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

Обратимся к нашему примеру, где для начала необходимо создать метку. Откройте хранилище http://127.0.0.1/svn/web-dev посредством пункта контекстного меню TortoiseSVN→Repo-browser на любой директории любого локального диска. Выделите директорию trunk и для команды контекстного меню Copy to… задайте адрес http://127.0.0.1/svn/web-dev/tags/v223. Копирование произойдет очень быстро т.к., по сути, никакие данные не копируются, о чем уже говорилось выше. Ветка успешно создана, а о том, что это метка говорит нам только ее месторасположение.

на заметку:

При каждом создании ветки создается новая правка, именно поэтому в нашем примере номер правки с которой метка начинает жить самостоятельно — 36.

Для того чтобы выполнить второй пункт нашего алгоритма повторите действия по созданию ветки (сначала, если вы поспешили закрыть браузер хранилища), но конечной целью укажите branches/v230.

можно иначе:

клиент TortoiseSVN предоставляет альтернативный способ ветвления посредством команды TortoiseSVN→Branch/Tag… Смысл у нее тот же самый, все изменения также производятся на стороне хранилища без передачи данных клиенту, и команда, по сути, является просто быстрым способом создания ветки без обозревания хранилища.

Мы добились некоторых результатов, которые можно было бы отразить следующим образом:

Состояние хранилища после создания веток

краткий итог:

Ветка — копия чего-либо. Но технически копия «легкая». Это означает, что при ее создании создается новая правка, все объекты которой указывают (ссылаются) на объекты предыдущей правки. Настоящее копирование произойдет только при изменении чего-либо в ветке. Все это напоминает обычное закрепление изменений и по сути таковым и является, с той лишь разницей, что добавляется дополнительная информация об истории.

Изменение

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

Теперь, когда у нас есть новая ветка branches/v230 мы можем поступить по-разному:

  1. Сделать checkout ее содержимого в какую-то директорию и начать с ней работать;
  2. Переключить нашу, уже существующую рабочую копию, которая указывает на trunk так, чтобы она стала отражать содержимое branches/v230;
  3. Скопировать рабочую копию trunk в какую-нибудь директорию и поступить с ней как это описано в предыдущем пункте.

Первый способ в большинстве случаев будет удобен пользователям denwer ввиду возможности автосоздания виртуальных хостов — очень удобно произвести выгрузку ветки в отдельную директорию в home комплекса, перезагрузить его и работать в отдельной ветке с возможностью всегда изменить и копию соответствующую trunk.

чтобы никто не забыл:

Мы исходим из web-ориентированности повествования. У нас тут всякие Apache и прочие www.

Третий вариант является частным случаем второго, который, в свою очередь, будет работать максимально эффективно при следующих условиях:

  • Нет необходимости работать одновременно в нескольких рабочих копиях разных ветвей;
  • Хранилище расположено удаленно;
  • Переключение предполагается произвести между trunk и только что созданной на ее основе ветки.

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

о вечном:

В теории все замечательно, однако на практике немного информации все же передается, но по сравнению с checkout ее объем пренебрежительно мал.

Так как в способе первом ничего интересного нет, попытаемся освоить другой метод. Освоение будет заключаться в выполнении над рабочей копией trunk команды TortoiseSVN→Switch… с указанием того на что мы будем переключаться branches/v230. После завершения операции рабочая копия будет представлять собой содержимое этой самой ветки, update, commit и некоторые прочие операции по умолчанию будут работать уже не с trunk, а с branches/v230. В общем, произошло нечто ожидаемое — переключение на другую ветку разработки без утомительных выгрузок в новую рабочую копию и прочего. Теперь можно работать, как это делалось прежде. Для нашего примера это все необходимые действия по обновлению WordPress — копирование поверх обновленных файлов движка, проверке все ли в порядке, доводка напильником и прочими подручными средствами. Естественно, что в ходе этих действий можно и нужно использовать всю мощь SVN: update, commit, revert и прочее.

если подумать:

Команды switch и update очень похожи — обе изменяют рабочую копию на основе анализа хранилища. На самом деле update является частным случаем switch, которая может сравнивать и изменять текущую рабочую копию с различными ветками в хранилище. Это более гибкий и мощный инструмент. Оправданием существования update может послужить то, что это менее гибкий и мощный инструмент.

Для того чтобы продолжить линию нашего примера представим, что в ходе изменений было создано 7 правок и мы остановились на правке №44:

Состояние хранилища после изменения ветки

Копирование изменений между ветками

Очень распространенный вопрос, который решается в ходе интенсивного использования ветвления — копирование изменений между ветками.

То как часто это следует делать, необходимо ли делать вовсе и прочие вопросы, связанные с этой операцией решаются исходя из типа ветки, внутренней политики разработки и чувства меры. Например, самый распространенный случай, когда имеется главная ветка разработки и несколько функциональных ветвей. Тогда если в ветвях делаются изменения по каким-то причинам выходящие за рамки целей создания этих ветвей, их необходимо копировать в главную ветку разработки как можно скорее. Примером такого рода изменений может служить обнаруженная и исправленная ошибка, в то время как ветки не предназначались для их отлова и исправления. Как видно существует довольно много «если» и вопрос своевременности копирования изменений из ветки в ветку по праву можно считать тонким.

из чувства справедливости:

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

Копирование части изменений

Пусть в ходе работ по обновлению было выяснено, что в обеих ветках (trunk и branches/v230) в файле www/wp-config.php, который не был перезаписан обновлением, была замечена ошибка. Таким образом, ошибка содержится в обеих ветках. Предположим, что мы справили ее в текущей на данный момент branches/v230, и закрепили исправление в правке №44. Задача состоит в копировании исправления в tags/v223.

Предыдущая мысль — типичный пример засады, которые встречаются на пути не только начинающих пользователей SVN. Ошибка заключается в предании забвению факта, что все правки любой из веток — фотографии, слепки всего дерева хранилища на какой-то момент времени, а значит, мы не можем изменить уже закрепленные правки — tags/v223 «потеряна» для нас. К тому же эта метка, как и другие ветки, живет отдельно и изменения в ней не будут видны, например пользователям trunk.

Следует поступить по следующей схеме:

  • Портировать изменения в самую общую ветвь — trunk, обязательно указав в комментарии, что это порт и описав его суть, чтобы для людей пользующихся одновременно trunk и branches/v230 не поставил в тупик неожиданный могущий возникнуть конфликт;
  • Удалить tags/v223 или оставить ее на месте;
  • В зависимости от выбранного действия над tags/v223 нужно или портировать в нее изменения из trunk, или создать на основе trunk новую метку, назвав ее, например, tags/v223_patched1 с комментарием о сути патча.

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

Портирование изменений осуществляет консольная команда svn merge или ее представитель TortoiseSVN→Merge…. Команда принимает адреса сравниваемых деревьев и рабочую копию, к которой разница между деревьями будет применена в виде локальных (!!!) изменений. Потом их можно будет закрепить или отменить.

Ветви, которые участвуют в портировании изменений, дадут адреса деревьев, а направление ответит на вопрос к рабочей копии какой ветки применять разницу. В нашем примере from — http://127.0.0.1/svn/branches/v230/www/wp-config.php, to — также http://127.0.0.1/svn/branches/v230/www/wp-config.php, а рабочая копия для ветки trunk.

обратите внимание:

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

Адреса деревьев будут не полными без указания номеров правок. Это уточняющая координата. Обратимся к логике: мы условились, что исправления, которые мы должны портировать, закреплены в правке №44, а значит, в правке №43 их не было. Следует вывод, что диапазон номеров правок будет 43-44. Это все необходимые данные.

чтобы не ошибиться:

Рекомендуется запускать тестовое слияние посредством кнопки «dry run» перед тем как запустить его по-настоящему.

Теперь исправление ошибки находится в ветке trunk под правкой №45, но оно никак не отражено в метке tags/v223. Логично хранить все изменения в одном месте, значит, ничего удалять не будем, а портируем только что портированные в trunk изменения и туда. В этом случае адрес дерева from будет таким же, как и в предыдущем портировании, to — аналогично. Диапазон номеров правок также останется без изменений. Изменится лишь рабочая копия, которая должна будет отражать состояние tags/v223. В этом нам поможет уже известная нам TortoiseSVN→Switch…. Теперь все логически верно: все состояния, на которые можно откатиться при неудачном обновлении WordPress до v2.3.0, у нас находятся в tags/v223, причем с возможностью выбора момента отката — до исправления ошибки (правка №36) и после ее портирования (правка №46).

Все изменения, произошедшие с хранилищем, можно отобразить следующей диаграммой, где жирная линия означает портирование изменение с последующим закреплением:

Состояние хранилища после портирования части изменений

Обратите внимание на жирную линию со штрихом. Это альтернативный способ портирования изменений в tags/v223 достигаемый при параметрах from и to равными http://127.0.0.1/svn/trunk/www/wp-config.php, с диапазоном правок 35-45. Аналогичный результат даст команда и для правок 35-44 при неизменном адресе сравниваемых деревьев. Это обусловлено тождественностью правок 44 и 45. Как видите, результата можно достигнуть множеством путей. Однакое необходимо заметить, что эти способы работают только в конкретном случае примера, когда между правками 35 и 37 не было изменений trunk, что помешало бы использованию альтернативных способов, но об этом ниже.

краткий итог:

При слиянии необходимо всегда руководствоваться логикой и запомнить, что merge выполняется для двух деревьев хранилища заданных адресами и диапазоном правок, а разница всегда применяется к рабочей копии или части ее, если команда была выбрана не для всей копии. Никакие изменения в хранилище при этом не вносятся — вам нужно будет сделать это самостоятельно позже обычным commit, если потребуется. Сломать что-либо слиянием очень сложно, хотя запутаться есть где. Затем и логика.

Слияние веток полностью

Мы только что закончили все операции по обновлению WordPress до v2.3.0 и остались довольными качеством работы. Время за слиянием функциональной ветки branches/v230 в основную линию разработки trunk.

Опять прибегаем к логике: необходимо сравнить trunk@35 и branches/v230@44 чтобы получить разницу которую надо применить к trunk. Для нашего конкретного случая это сработает, все верно. Но это ошибочный путь, не думайте в таком ключе. Представьте себе, что кто-то из ваших товарищей все это время работал над trunk и вносил туда изменения. Тогда при попытке вычислить разницу между trunk@35 и branches/v230@44 вы засечете и его изменения, которые, пересекаясь с вашими, могут дать непредсказуемые результаты. Гораздо легче и безопаснее сравнивать branches/v230@37 и branches/v230@44, что оправдано и логически: взять все изменения в ветке branches/v230 с момента ее создания до последней правки.

Самое «сложное» — определить начальную правку ветки. Делается это посредством просмотра лога ветки при выбранной опции «stop on copy/rename», что в свою очередь достигается через контекстное меню нужной ветки в repo-browser.

Остальные моменты слияния ничем не отличаются от уже делавшихся нами в предыдущей части этой статьи.

для внимательных:

Можно заметить, что для trunk часть изменений применяется дважды, что объясняется неспособностью автора придумать более элегантный пример. Однако ничего страшного в этом двойном применении нет т. к. второго, по сути, не произойдет — в этом месте «дельта» изменений и рабочей копии будет равна нулю.

Теперь ветку branches/v230 можно удалить или оставить и создать метку tags/v230, что уже не является сложной задачей.

Подводные камни и нюансы

  1. Ветки — простые копии;
  2. Можно переключать с ветки на ветку не только всю рабочую копию, но и один единственный файл или директорию. Все, что необходимо — знать, что только с ними будет вестись работа. Также имеется возможность переключаться и на любую правку ветки;
  3. Обязательно комментируйте портирование изменений вплоть до номеров правок — это поможет вам в ряде случаев. Например, предположим, что branches/v230 не была удалена после полного слияния с trunk и в ней продолжилась работа, которая привела к появлению правок 48-55. Пусть необходимо также портировать эти изменения в trunk. В этом случае по комментариям в логе хранилища легче всего определить, что первая волна изменений охватывала правки 37-44 и была закреплена в правке 47. Значит, вторая волна должна охватывать правки 48-55 и никак не 37-50, что приведет к захвату изменений первой волны дважды;
  4. Никто не обязывает вас делать хранилище со структурой, о которой говорилось в этой статье. Организация хранилища – личное дело каждого. Однако стоит помнить, что вещи проверенные временем заслуживают внимания;
  5. Совсем не обязательно иметь на локальной машине такую же структуру, как и у хранилища. Может случиться, что у вас на компьютере в один момент времени будет лишь одна рабочая копия, расположенная в неком DocumentRoot некоего сайта и вы будете интенсивно использовать svn switch для доступа к иным местам хранилища. Экономьте свое место на диске и время.

Заключение

Ветки — это просто. Все, что необходимо в работе с ними — логика и четкое понимание происходящего. Ну и практика, конечно. Не опасайтесь их, просто используйте.

4 комментария на «Ветвление в Subversion»

  1. Алексей,

    Благодарю за полезный материал. Простите если не туда, но как с автором сайта связаться?

  2. wd,

    Ответил Вам на почту.

  3. woto,

    Хорошая статья, правда сложная, где-то на merge начал спотыкаться :), надо на практике попроверять.

  4. wd,

    Без практики и checkout — сложный :)

Оставить отзыв

 

Февраль 2008
Пн Вт Ср Чт Пт Сб Вс
    Март »
 123
45678910
11121314151617
18192021222324
2526272829