- •Лабораторная работа№4 «Система версионного контроля Subversion»
- •1. Фундаментальные понятия
- •1.1 Хранилище
- •1.2 Модели версионирования
- •1.2.1 Проблема разделения файлов
- •1.2.2 Модель Блокирование-Изменение-Разблокирование
- •1.2.3 Модель Копирование-Изменение-Слияние
- •1.3 Subversion в действии
- •1.3.1 Url хранилища в Subversion
- •1.3.2 Рабочие копии
- •1.3.3 Url хранилища
- •1.3.4 Правки
- •1.3.5 Как рабочие копии отслеживают хранилище
- •1.3.6 Смешивание правок в рабочих копиях
- •1.3.7 Обновления и фиксации отделены друг от друга
- •1.3.8 Смешивание правок
- •2. Импорт
- •2.1 Путешествие во времени вместе с Subversion
- •2.2 Создание рабочей копии
- •2.3 Простейший рабочий цикл
- •2.4 Обновление рабочей копии
- •2.5 Внесение изменений в рабочую копию
- •2.6 Анализ изменений
- •2.6.1 Svn status
- •2.6.3 Svn diff
- •2.6.4 Svn revert
- •2.7 Разрешение конфликтов (при слиянии с чужими изменениями)
- •2.8 Слияние конфликтов вручную
- •2.9 Копирование файла поверх вашего рабочего файла
- •2.10 Использование svn revert
- •2.11 Фиксация изменений
- •2.12 Анализ истории
- •Svn log
- •Svn diff
- •Анализ локальных изменений
- •Сравнение рабочей копии с хранилищем
- •Сравнение хранилища с хранилищем
- •Svn cat
- •Svn list
- •Заключительное слово об истории
- •3. Ветвление и слияние
- •3.1 Что такое ветка?
- •3.2 Использование веток
- •3.3 Создание ветки
- •3.4 Работа с веткой
- •3.5 Ключевые идеи, стоящие за ветками
- •3.6 Копирование изменений между ветками
- •3.7 Копирование отдельных изменений
- •3.8 Ключевые идеи, стоящие за слиянием
- •3.9 Как правильнее всего использовать слияние
- •3.9.1 Ручной контроль слияния
- •3.9.2 Предварительный просмотр результатов слияния
- •3.9.3 Конфликты при слиянии
- •3.9.4 Учитывать или игнорировать происхождение
- •3.10 Типовые примеры
- •3.10.1 Полное объединение двух веток
- •3.10.2 Отмена изменений
- •3.10.3 Восстановление удаленных элементов
- •3.11 Типовые приемы использования веток
- •3.11.1 Ветки релизов
- •3.11.2 Функциональные ветки
- •3.11.3 Переключение рабочей копии
- •3.12 Метки
- •3.12.1 Создание простой метки
- •3.12.2 Создание комплексной метки
- •3.13 Поддержка веток
- •3.13.1 Структура хранилища
- •3.13.2 Продолжительность жизни информации
3.9.4 Учитывать или игнорировать происхождение
Общаясь с разработчиками, использующими Subversion, очень часто можно услышать термин происхождение. Это слово используется для описания отношений между двумя объектами хранилища: если между ними есть связь, то говорят, что один объект происходит от другого.
Предположим, что фиксируется правка 100, в которой изменяется файл foo.c. В этом случае файл foo.c@99 является предком файла foo.c@100. С другой стороны, можно допустить, что в правке 101 вы фиксируете удаление foo.c, а затем в правке 102 добавляете новый файл с таким же именем. В таком случае может показаться, что файлы foo.c@99 и foo.c@102 имеют отношение друг к другу (у них одинаковый путь), однако на самом деле они являются полностью независимыми объектами хранилища. Они не имеют ни общей истории, ни общих «предков».
Мы обращаем на это ваше внимание, чтобы указать на важные различия между svn diff и svn merge. Первая команда игнорирует происхождение, в то время как вторая его учитывает. Например, если попросить svn diff сравнить правки 99 и 102 файла foo.c вы увидите построчное сравнение; команда diffслепо сравнивает два пути. А вот если вы попросите svn merge сравнить те же объекты, то Subversion предупредит вас о том, что они не связаны друг с другом и сначала попытается удалить старый файл, а затем добавить новый; вывод команды покажет удаление с последующим добавлением:
D foo.c
A foo.c
В большинстве случаев при слиянии сравниваются деревья, имеющие родственную связь и по умолчанию svn merge рассчитывает на это. Однако иногда вам будет нужно, чтобы команда merge сравнила два независимых дерева файлов. Например, вы могли импортировать два дерева, содержащие исходный код релизов программных проектов сторонних поставщиков (см. «Vendor branches»). Если попросить svn merge сравнить два эти дерева, вы увидите, что первое дерево будет полностью удалено, а затем будет полностью добавлено второе!
В подобных ситуациях нужно, чтобы команда svn merge выполняла сравнение, учитывая только пути, и не обращала внимание на отношения между файлами и каталогами. Добавьте опцию --ignore-ancestry при запуске команды слияния, после чего она будет вести себя аналогично svn diff. (И наоборот, опция --notice-ancestry заставит svn diff работать подобно команде merge.)
3.10 Типовые примеры
Ветвлением и слиянием можно пользоваться по-разному. В этом разделе описываются наиболее типичные примеры, с которыми вам придется иметь дело.
3.10.1 Полное объединение двух веток
Чтобы закончить с последним примером, заглянем немного вперед. Предположим, что прошло несколько дней. И в главную линию разработки, и вашу личную ветку за это время было внесено множество изменений. Допустим, что работу над своей веткой вы завершили. Новый функционал добавлен, ошибки исправлены, и теперь вы хотите объединить все изменения из своей ветки с главной линией разработки.
Как применить в этом случае 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.
Теперь главная линия разработки полностью содержит вторую волну изменений, сделанных в ветке. Теперь можно либо удалить ветку (об этом мы поговорим позже), либо продолжить работу над ней, периодически повторяя при этом процедуру объединения.
