Запись была отнесена к рубрике «Техники» 20.02.2008, Ср в 01:48. Вы можете следить за общением по теме этой записи с помощью RSS 2.0 ленты. Вы можете оставить отзыв, или trackback с Вашего сайта.
Рано или поздно руки любого кто однажды притронулся к 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».
Таким образом, начальная картина хранилища и ее временная диаграмма будут иметь вид:

Каковы будут действия для выполнения поставленной задачи:
Создание ветки осуществляется обычным копированием того, что будет ответвлено (обычно это 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 мы можем поступить по-разному:
Первый способ в большинстве случаев будет удобен пользователям denwer ввиду возможности автосоздания виртуальных хостов — очень удобно произвести выгрузку ветки в отдельную директорию в home комплекса, перезагрузить его и работать в отдельной ветке с возможностью всегда изменить и копию соответствующую trunk.
Мы исходим из web-ориентированности повествования. У нас тут всякие Apache и прочие www.
Третий вариант является частным случаем второго, который, в свою очередь, будет работать максимально эффективно при следующих условиях:
Первый пункт обуславливает меньшее количество телодвижений — не надо ничего создавать, выгружать, конфигурировать, перезагружать (если вы используете не 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.
Следует поступить по следующей схеме:
Теперь у нас полный порядок в логике и хоть сказанное выше звучит устрашающе, но на практике все очень просто.
Портирование изменений осуществляет консольная команда 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, что уже не является сложной задачей.
Ветки — это просто. Все, что необходимо в работе с ними — логика и четкое понимание происходящего. Ну и практика, конечно. Не опасайтесь их, просто используйте.
Благодарю за полезный материал. Простите если не туда, но как с автором сайта связаться?
Ответил Вам на почту.
Хорошая статья, правда сложная, где-то на merge начал спотыкаться :), надо на практике попроверять.
Без практики и checkout — сложный :)