Для ветвления и команды svn merge существует множество применений, в этом раздели описаны наиболее типичные из тех с которыми вы можете столкнуться.
Полное объединение двух веток
Что бы завершить наш текущий пример, заглянем немного в перед. Предположим, прошло несколько дней и как в главную линию разработки так и вашу личную ветку было внесено множество изменений. Допустим, что работу над своей веткой вы завершили; добавление функциональности или исправление ошибок наконец закончено и теперь вы хотите объединить все изменения из своей ветки с главной линией разработки.
Как же в этом случае нужно использовать svn merge? Помните о том, что эта команда сравнивает два дерева и применяет различия к рабочей копии. Поэтому, для того, что бы было к чему применять изменения, необходимо иметь рабочую копию главной линии разработки. Будем считать, что-либо у вас под рукой имеется такая (полностью обновленная) копия, либо вы только что создали новую рабочую копию /calc/trunk.
А какие именно два дерева должны сравниваться? На первый взгляд ответ очевиден: сравнивать последнее дерево главной линии разработки с последним деревом вашей ветки. Однако будьте осторожны — такое предположение является ошибочным, многие новые пользователи ошибаются подобным образом! Учитывая то, что svn merge работает так же как svn diff, сравнение последние версии главной линии разработки и вашей ветки покажет изменения сделанные не только в вашей ветке. Такое сравнение покажет слишком много изменений: будут показано не только то, что добавлялось в вашей ветке, но и то, что удалялось в главной линии разработки и не удалялось в вашей ветке.
Для выделения только тех изменений, которые были сделаны в вашей ветке, нужно сравнивать начальное и конечное состояния ветки. Воспользовавшись svn log для ветки, можно узнать, что она была создана в правке 341. А для определения конечного состояния ветки можно просто использовать правку HEAD. Это значит, что вам нужно сравнить правки 341 и HEAD директории с веткой и применить различия к рабочей копии главной линии разработки.
Подсказка
Удобно для определения правки, в которой ветка была создана («базовой» правки ветки) использовать параметр --stop-on-copy при запуске svn log. При обычном запуске, эта команда показывает все изменения сделанные в ветке, включая те, которые были сделаны до создания ветки. Поэтому, при таком запуске вы увидите и историю главной линии разработки. Параметр --stop-on-copy остановит вывод лог сообщений как только svn log определит, что целевой объект был скопирован или переименован.
$ svn log --verbose --stop-on-copy \
http://svn.example.com/repos/calc/branches/my-calc-branch
…
------------------------------------------------------------------------
r341 | user | 2002-11-03 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines
Changed paths:
A /calc/branches/my-calc-branch (from /calc/trunk:340)
$
Как и ожидалось, последняя правка выведенная этой командой будет правка, в которой директория my-calc-branch была создана путем копированием.
Вот так выглядит завершение объединения:
$ cd calc/trunk
$ svn update
At revision 405.
$ svn merge -r 341:405 http://svn.example.com/repos/calc/branches/my-calc-branch
U integer.c
U button.c
U Makefile
$ svn status
M integer.c
M button.c
M Makefile
# ...examine the diffs, compile, test, etc...
$ svn commit -m "Merged my-calc-branch changes r341:405 into the trunk."
Sending integer.c
Sending button.c
Sending Makefile
Transmitting file data ...
Committed revision 406.
Еще раз обратите внимание, на то, что в лог сообщении фиксации очень точно указан диапазон правок, которые были объединены с главной линией разработки. Никогда не забывайте этого делать, потому что это очень важная информация, которая понадобиться вам позже.
Например, предположим, что на следующей неделе вы решите продолжить работу над веткой, для завершения расширения функциональности или исправления ошибки. После этого, правка HEAD хранилища будет имеет номер 480 и вы готовы выполнить еще одно объединение своей личной копии с главной линией разработки. Однако, как уже было сказано в разделе «Как правильнее всего использовать слияние», нет необходимости объединять изменения которые уже были объединены раньше; нужно объединить только «новые» изменения, появившиеся с момента последнего объединения. Сложность в том, что бы выделить эти новые изменения.
Первым шагом является запуск svn log для главной линии разработки, для того, что бы увидеть сообщение о времени последнего объединения с веткой:
$ cd calc/trunk
$ svn log
…
------------------------------------------------------------------------
r406 | user | 2004-02-08 11:17:26 -0600 (Sun, 08 Feb 2004) | 1 line
Merged my-calc-branch changes r341:405 into the trunk.
------------------------------------------------------------------------
…
Ага! Так как все изменения в ветке, которые делались между правками 341 и 405 уже были объединены с главной линией разработки в правке 406, то теперь вы знаете, что необходимо брать только те изменения ветки, которые были выполнены после этого — сравнивая правки 406 и HEAD.
$ cd calc/trunk
$ svn update
At revision 480.
# We notice that HEAD is currently 480, so we use it to do the merge:
$ svn merge -r 406:480 http://svn.example.com/repos/calc/branches/my-calc-branch
U integer.c
U button.c
U Makefile
$ svn commit -m "Merged my-calc-branch changes r406:480 into the trunk."
Sending integer.c
Sending button.c
Sending Makefile
Transmitting file data ...
Committed revision 481.
Теперь главная линия разработки полностью содержит вторую волну изменений, сделанных в ветке. С этого момента можно либо удалить ветку (об этом мы поговорим позже), либо продолжать работать над веткой, с последующим объединением изменений.
Отмена изменений
Еще одним типичным применением для svn merge является откат изменений, которые уже были зафиксированы. Предположим вы спокойно работаете в рабочей копии /calc/trunk и выясняете, что изменения сделанные в правке 303, которые изменили integer.c, полностью ошибочны. Вы можете воспользоваться командой svn merge для «отмены» изменений в своей рабочей копии, после чего зафиксировать локальные изменения в хранилище. Все, что нужно сделать, это указать обратные отличия:
$ svn merge -r 303:302 http://svn.example.com/repos/calc/trunk
U integer.c
$ svn status
M integer.c
$ svn diff
…
# verify that the change is removed
…
$ svn commit -m "Undoing change committed in r303."
Sending integer.c
Transmitting file data .
Committed revision 350.
Одним из взглядов на правку хранилища является представление ее в виде сгруппированных изменений (некоторые системы управления версиями называют это набором изменений). Используя параметр -r можно попросить svn merge применить к рабочей копии набор изменений или целый диапазон наборов изменений. В нашем случае с отменой изменений, мы просим svn merge применить к рабочей копии набор изменений #303 в обратном направлении.
Обратите внимание, что откат изменений подобным образом ничем не отличается от любых других операций, выполненных с помощью svn merge, поэтому необходимо использовать svn status и svn diff для того, что бы убедится, что ваша работа находится в том состоянии в котором вам нужно, а затем используя svn commit отправить финальную версию в хранилище. После фиксации, этот конкретный набор изменений больше не будет отражен в правке HEAD.
Но наряду с этим, вы можете подумать: однако же на самом деле фиксация не отменяется, не так ли? Изменения продолжают существовать в правке 303. И если кто-то создаст рабочую копию версии проекта calc между правками 303 и 349, он все равно получит ошибочные изменения, верно?
Да, это так. Когда мы говорим об «удалении» изменений, имеется в виду их удаление из HEAD. Первоначальные изменения продолжают существовать в истории хранилища. Для большинства ситуаций это является положительным моментом. В любом случае, большинство пользователей интересует только HEAD проекта. Однако, возможны ситуации, когда действительно необходимо удалить последствия фиксации. (Возможно, кто-то случайно зафиксировал конфиденциальный документ.) Сделать это будет не так просто, так как Subversion спроектирована специально таким образом, что бы исключить возможность потери информации. Правки представляют собой не меняющиеся деревья файлов, основывающиеся одно на другом. Удаление правки из хранилища может вызвать эффект домино, создавая беспорядок во всех последующих правках и возможно разрушая все рабочие копии. [15]
Восстановление удаленных элементов
Отличным свойством системы контроля версия является то, что информация никогда не теряется. При удалении файла или директории, элемент исчезает из правки HEAD но продолжает существовать в более ранних правках. Одним из наиболее частых вопросов, задаваемых новыми пользователями, является такой: «Как мне вернуть назад свой файл или директорию?»
Первым шагом является определение того, какой именно элемент вы пытаетесь восстановить. Неплохой метафорой является представление каждого объекта в хранилище существующим в двухмерной системе координат. Первой координатой является отдельное дерево правок, второй координатой является путь в этом дереве. Таким образом каждая версия файла или директории может быть представлена конкретной парой координат.
Subversion, в отличие от CVS, не имеет директории Attic[16] поэтому для определения необходимой при восстановлении пары координат нужно воспользоваться командой svn log. Лучше всего запустить svn log --verbose в директории, которая содержала удаленный элемент. Параметр --verbose покажет для каждой правки список измененных элементов; все, что вам остается сделать, это найти правку, в которой файл или директория были удалены. Сделать это можно визуально или воспользоваться для обработки вывода каким-то инструментом (grep или может быть последовательным поиском в редакторе).
$ cd parent-dir
$ svn log --verbose
…
------------------------------------------------------------------------
r808 | joe | 2003-12-26 14:29:40 -0600 (Fri, 26 Dec 2003) | 3 lines
Changed paths:
D /calc/trunk/real.c
M /calc/trunk/integer.c
Added fast fourier transform functions to integer.c.
Removed real.c because code now in double.c.
…
В примере предполагается, что вы ищите удаленный файл real.c. Просмотрев логи родительской директории вы определите, что этот файл был удален в правке 808. Следовательно, последняя существовавшая версия файла была в правке, предшествующей этой. Вывод: необходимо из правки 807 восстановить путь /calc/trunk/real.c.
Поиск был сложной задачей. Теперь, когда известно, что нужно восстановить, есть две возможности.
Одним из вариантов является использование svn merge для применения правки 808 «в обратном направлении». (Как отменять изменения мы уже рассматривали, см. «Отмена изменений».) Это приведет к эффекту повторного добавления фала real.c в виде локальных изменений. Файл будет запланирован для добавления и после фиксации будет опять присутствовать в HEAD.
Однако в этом, отдельно взятом примере, это не самое лучшее решение. Повторное применение правки 808 не только добавит файл real.c; лог сообщение показывает, что будут отменены некоторые изменения в integer.c, чего вы не хотите. Конечно, можно выполнить обратное объединение с правкой 808, а затем отменить (svn revert) локальные изменения integer.c, однако такой подход плохо масштабируется. Что если в правке 808 было изменено 90 файлов?
При втором, более целевом методе, svn merge вообще не используется, а вместо этого применяется команда svn copy. Просто скопируете определенные «парой координат» правку и путь из хранилища в рабочую копию:
$ svn copy --revision 807 \
http://svn.example.com/repos/calc/trunk/real.c ./real.c
$ svn status
A + real.c
$ svn commit -m "Resurrected real.c from revision 807, /calc/trunk/real.c."
Adding real.c
Transmitting file data .
Committed revision 1390.
Знак плюс в статусе показывает, что элемент не просто запланирован для добавления, а запланирован для добавления «с историей». Subversion запоминает откуда он был скопирован. В будущем, запуск svn log для этого файла будет пересекать восстановление файла и всю историю, предшествующую правке 807. Другими словами, новый файл real.c на самом деле не является новым; он является прямым наследником оригинального, удаленного файла.
Хотя наш пример показывает как восстанавливать файл, обратите внимание, на то что этот подход работает также и для восстановления удаленных директорий.
Типовые приемы при использовании веток
Управление версиями чаще всего используется при разработке программного обеспечения, поэтому здесь мы вкратце рассмотрим два, наиболее часто используемые командами программистов, приема ветвления/слияния. Если вы не используете Subversion для разработки программного обеспечения, можете пропустить этот раздел. Если вы разработчик программного обеспечения использующий контроль версий впервые, внимательно присмотритесь, поскольку опытные разработчики считают использование этих приемов хорошим стилем работы. Такие приемы не являются специфичными для Subversion; они применимы к любой системе управления версиями. Тем более, что это поможет увидеть их описание в терминах Subversion.
Ветки релизов
Большинство программного обеспечения имеет типовой жизненный цикл: написание кода, тестирование, выпуск, повторный цикл. При таком подходе возникают две проблемы. Во-первых, разработчикам необходимо продолжать расширять функциональность в то время, как группа проверки качества будет заниматься тестированием предположительно стабильных версий программы. Во время тестирования не должна останавливаться разработка. Во-вторых, как правило, требуется поддерживать старые, уже выпущенные версии программы; если найдена ошибка в последней версии кода, то скорее всего она присутствует и в уже выпущенных версиях, поэтому пользователи захотят, чтобы эта ошибка была исправлена не дожидаясь выхода новой версии программы.
Здесь-то и может помочь контроль версий. Типичная процедура выглядит примерно так:
-
Разработчики фиксируют все новое в главную линию разработки. Каждодневные изменения фиксируются в /trunk: новая функциональность, исправление ошибок и тому подобное.
-
Главная линия разработки копируется в ветку «релиза». Когда команда разработчиков решает, что программа готова к выпуску (скажем, к релизу 1.0), тогда /trunk копируется, например, в /branches/1.0.
-
Группы продолжают работать параллельно. Одна группа начинает всестороннее тестирование ветки релиза, в то время как вторая группа продолжает работу (скажем, над версией 2.0) в /trunk. Если находятся ошибки в какой-либо из версий, исправления портируются по необходимости в одну или другую сторону. В какой-то момент этот процесс останавливается. Ветка «замораживается» для окончательной проверки перед релизом.
-
На основе ветки создается метка и выпускается релиз. Когда тестирование завершено, /branches/1.0 копируется в /tags/1.0.0 как справочный снимок. Метка запаковывается и отправляется пользователям.
-
Ветка продолжает поддерживаться По мере продвижения работы над /trunk для версии 2.0, исправления ошибок продолжают портироваться из /trunk в /branches/1.0. Когда будет накоплено определенной количество исправлений, руководство может решить сделать релиз 1.0.1: /branches/1.0 копируется в /tags/1.0.1, метка пакуется и выпускается.
По мере развития программы эти этапы повторяются: когда работа над 2.0 будет завершена, создается новая ветка релиза 2.0, тестируется, создается метка и в последствии выпускается релиз. После нескольких лет в хранилище будет находиться определенное количество веток релизов, находящихся в режиме «сопровождения» и определенное количество меток, отражающих последние выпущенные ветки.
Функциональные ветки
Функциональная ветка является доминирующим примером в этой главе, над такой веткой вы работаете пока Салли работает над /trunk. Это временная ветка, которая создается для работы над комплексным изменением без пересечения со стабильной линией разработки /trunk. В отличие от веток релизов (которые могут поддерживаться вечно), функциональные ветки создаются, используются, внедряются обратно в главную линию разработки, после чего полностью удаляются. Они имеют ограниченный срок использования.
Опять же, правила проекта относительно определения момента, когда требуется создание функциональной ветки могут быть разными. Некоторые проекты вообще никогда не используют функциональные ветки: все фиксируется в /trunk. Преимущества такой системы в ее простоте — никому не нужно учиться делать ветки или объединения. Недостатком является то, что главная линия разработки часто не стабильна или не пригодна к использованию. В других проектах ветки используют по-другому: ни одного изменения не фиксируют в главной линии разработки напрямую. Даже для самых простых изменений создается краткосрочная ветка, внимательно анализируется и объединяется с главной линией. После чего ветка удаляется. Ценой огромных накладных расходов, такая система гарантирует исключительную стабильность и пригодность к использованию главной линии разработки в любой момент времени.
Большинство проектов использует что-то среднее. Как правило, все время контролируя, что /trunk компилируется и проходит регрессивные тесты. Функциональная ветка требуется только тогда, когда изменение требует большого количества дестабилизирующих фиксаций. Хорошим способом проверки является постановка такого вопроса: если разработчик работал несколько дней изолировано, а затем за один раз зафиксировал большое изменение (притом, что /trunk не будет дестабилизирован) будет ли сложно отследить это изменение? Если ответ на этот вопрос «да», то тогда изменение должно разрабатываться в функциональной ветке. По мере того, как разработчик последовательно фиксирует изменения в ветку, они могут легко отслеживаться другими участниками.
Напоследок, рекомендация, как по ходу работы лучше всего сохранять функциональную ветку «синхронизированной» с главной линией разработки. Как мы уже говорили, рискованно работать над веткой в течение недель или месяцев; изменения в главной линии будут продолжаться, и настанет момента, когда две линии разработки станут отличаться так сильно, что попытка объединить ветку обратно с главной линией разработки может стать ночным кошмаром.
Этой ситуации не возникнет, если регулярно объединять ветку с изменениями в главной линии. Возьмите за правило один раз в неделю объединять с веткой значимые изменения в главной линии разработки за прошедшую неделю. Делайте это аккуратно; за объединением необходим ручной контроль для того, что бы исключить проблему повторных объединений (как это описано в разделе «Ручной контроль слияния»). Необходимо внимательно записывать лог сообщение, указывая какой именно диапазон правок был объединен (как показано в разделе «Полное объединение двух веток»). Возможно это звучит устрашающе, но на самом деле это сделать очень легко.
Начиная с какого-то момента вы будете готовы объединить «синхронизированную» функциональную ветку с главной линией разработки. Для этого начните с завершающего объединения последних изменений из главной линии разработки с веткой. После чего, последняя версия ветки и главной линии будут абсолютно идентичны, за исключением ваших изменений в ветке. Теперь объединение заключается в сравнении ветки с главной линией разработки:
$ cd trunk-working-copy
$ svn update
At revision 1910.
$ svn merge http://svn.example.com/repos/calc/trunk@1910 \
http://svn.example.com/repos/calc/branches/mybranch@1910
U real.c
U integer.c
A newdirectory
A newdirectory/newfile
…
Сравнивая правку HEAD главной линии разработки и правку HEAD ветки, определяется дельта, которая представляет собой только изменения сделанные в ветке; обе линии разработки уже содержат все изменения из главной линии.
Другим способом представления этого приема является то, что еженедельная синхронизация ветки аналогична запуску svn update в рабочей копии, в то время как окончательное объединение аналогично запуску из рабочей копии svn commit. В конце концов, что же такое рабочая копия если не миниатюрная личная ветка? Эта такая ветка которая способна хранить одно изменение в каждый момент времени.
Достарыңызбен бөлісу: |