Управление версиями в Subversion Для Subversion 3 (в редакции 2345) Бен Коллинз-Сассман


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



бет14/34
Дата04.03.2016
өлшемі2.13 Mb.
#40691
түріРеферат
1   ...   10   11   12   13   14   15   16   17   ...   34

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


Сейчас вы и Салли работаете над параллельными ветками проекта: вы работаете над своей собственной веткой, а Салли работает над главной линией разработки (trunk).

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

Положительным моментом является то, что вы и Салли не пересекаетесь друг с другом. Отрицательный момент в том, что вы можете разойтись слишком далеко друг относительно друга. Помните, что одной из проблем такой «сходящейся к тупику» стратегии является то, что к моменту, когда вы закончите работу со своей веткой может быть практически невозможно снова объединить ваши изменения с главной линией без огромного количества конфликтов.

Вместо этого вы и Салли можете продолжать делиться изменениями по ходу работы. Вы можете решать вплоть до отдельного изменения, стоит ли им делиться; Subversion предоставляет возможность выборочного «копирования» изменений между ветками. А тогда, когда ваша ветка будет полностью закончена, полный набор изменений ветки может быть скопирован обратно в основную ветку.


Копирование отдельных изменений


В предыдущем пункте мы указали, что и вы и Салли, в месте, в разных ветках вносите изменения в integer.c. Если посмотреть на лог сообщение Салли для правки 344, вы увидите, что она исправила несколько орфографических ошибок. Конечно же, в вашей копии этого файла эти ошибки остались. Возможно, что ваши будущие изменения для этого файла коснутся областей которые содержат орфографические ошибки и таким образом вы получите несколько потенциальных конфликтов при последующем объединении вашей ветки. Поэтому, лучше получить изменения Салли сейчас, до того, как вы начнете вплотную работать в этих областях файла.

Настал момент воспользоваться командой svn merge. Эта команда, оказывается, является очень близким родственником команды svn diff (о которой вы читали Главе 3). Обе эти команды способны сравнивать любые два объекта в хранилище и показывать изменения. Например, вы можете попросить svn diff показать все изменения сделанные Салли в правке 344:

$ svn diff -r 343:344 http://svn.example.com/repos/calc/trunk
Index: integer.c

===================================================================

--- integer.c (revision 343)

+++ integer.c (revision 344)

@@ -147,7 +147,7 @@

case 6: sprintf(info->operating_system, "HPFS (OS/2 or NT)"); break;

case 7: sprintf(info->operating_system, "Macintosh"); break;

case 8: sprintf(info->operating_system, "Z-System"); break;

- case 9: sprintf(info->operating_system, "CPM"); break;

+ case 9: sprintf(info->operating_system, "CP/M"); break;

case 10: sprintf(info->operating_system, "TOPS-20"); break;

case 11: sprintf(info->operating_system, "NTFS (Windows NT)"); break;

case 12: sprintf(info->operating_system, "QDOS"); break;

@@ -164,7 +164,7 @@

low = (unsigned short) read_byte(gzfile); /* read LSB */

high = (unsigned short) read_byte(gzfile); /* read MSB */

high = high << 8; /* interpret MSB correctly */

- total = low + high; /* add them togethe for correct total */

+ total = low + high; /* add them together for correct total */
info->extra_header = (unsigned char *) my_malloc(total);

fread(info->extra_header, total, 1, gzfile);

@@ -241,7 +241,7 @@

Store the offset with ftell() ! */


if ((info->data_offset = ftell(gzfile))== -1) {

- printf("error: ftell() retturned -1.\n");

+ printf("error: ftell() returned -1.\n");

exit(1);


}
@@ -249,7 +249,7 @@

printf("I believe start of compressed data is %u\n", info->data_offset);

#endif
- /* Set postion eight bytes from the end of the file. */

+ /* Set position eight bytes from the end of the file. */


if (fseek(gzfile, -8, SEEK_END)) {

printf("error: fseek() returned non-zero\n");

Команда svn merge ведет себя практически полностью идентично. Но вместо вывода различий на терминал, применяет их к рабочей копии в виде локальных изменений:

$ svn merge -r 343:344 http://svn.example.com/repos/calc/trunk

U integer.c
$ svn status

M integer.c

Вывод команды svn merge показывает, что к вашей копии integer.c был применен патч. Теперь она содержит изменения Салли — изменения Салли были «скопированы» из главной линии разработки в вашу рабочую копию, вашей личной ветки и теперь существуют в виде локальных изменений. С этого момента вы можете просмотреть локальные изменения и убедиться в том, что они корректно работают.

По другому сценарию, возможно, что не все будет так хорошо и integer.c может оказаться в состоянии конфликта. Вам необходимо будет при помощи стандартной процедуры (см. Главу 3) решить конфликт, либо если вы прейдете к мнению, что объединение было плохой идеей, просто отказаться от него, отменив локальные изменения командой svn revert.

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

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

$ svn commit -m "integer.c: ported r344 (spelling fixes) from trunk."

Sending integer.c

Transmitting file data .

Committed revision 360.

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

Почему бы не использовать вместо этого патчи?

Вопрос, который может крутиться у вас в голове, особенно если вы пользователь Unix: зачем вообще связываться с svn merge? Почему просто не использовать команду операционной системы patch для выполнения этой работы? Например:

$ svn diff -r 343:344 http://svn.example.com/repos/calc/trunk > patchfile

$ patch -p0 < patchfile

Patching file integer.c using Plan A...

Hunk #1 succeeded at 147.

Hunk #2 succeeded at 164.

Hunk #3 succeeded at 241.

Hunk #4 succeeded at 249.

done


Для этого конкретного случая, да, действительно разницы нет. Однако, svn merge имеет специфические функции благодаря которым превосходит программу patch. Формат файлов, используемый программой patch довольно таки ограниченный; он способен передавать только изменения содержимого файлов. Он не имеет способа для представления изменений дерева файлов, таких, как добавление, удаление или переименование файлов и директорий. Если скажем, в результате изменений Салли, добавилась новая директория то в выводе svn diff упоминания об этом не будет. Вывод svn diff представляет собой только ограниченный патч-формат, по этому некоторые понятия он просто не может передать. [14] Команда svn merge, напротив, может передавать изменения в структуре файлов и свойств, непосредственно применяя их к рабочей копии.

Небольшое предупреждение: несмотря на то, что svn diff и svn merge очень похожи в основе, в большинстве случаев они имеют разные правила записи. Обязательно прочтите об этом в Главе 9, или спросите у svn help. Например, svn merge требует в качестве целевого объекта путь в рабочей копии, т. е. место, где ей нужно применить изменения структуры файлов. Если целевой объект не указан, предполагается, что делается попытка выполнить одну из следующих операций:



  1. Вы хотите объединить изменения директории с вашей текущей рабочей директорией.

  2. Вы хотите объединить изменения в конкретном файле с файлом имеющим тоже имя в текущей рабочей директории.

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

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

$ svn merge -r 343:344 http://svn.example.com/repos/calc/trunk my-calc-branch

U my-calc-branch/integer.c


Ключевые понятия, стоящие за слиянием


Вы увидели примеры использования svn merge, продолжим рассмотрение. Если вы чувствуете не уверенность в том как собственно работает слияние, то в этом вы не одиноки. Многие пользователи (особенно те, для которых управление версиями в новинку) поначалу путаются в правильности записи этой команды и в том, как и когда эту функцию следует использовать. Отбросьте страх, на самом деле эта команда намного проще чем вы думаете! Очень просто понять механизм того, как именно ведет себя svn merge.

В замешательство приводит, главным образом название команды. Термин «слияние» как бы указывает на то, что ветки соединяются вместе, или происходит какое-то волшебное смешивание данных. На самом деле это не так. Лучшим названием для этой команды могло быть svn diff-and-apply потому что это все, что происходит: сравниваются два файловых дерева хранилища, а различия переносятся в рабочую копию.

Команда принимает три аргумента:


  1. Начальное дерево хранилища (как правило, называемое левой частью при сравнении),

  2. Конечное дерево хранилища (как правило называемое правой частью при сравнении),

  3. Рабочую копию для применения отличий, в виде локальных изменений (как правило, называемую целью слияния).

Когда эти три аргумента указаны, сравниваются два дерева и результирующие различия применяются к целевой рабочей копии в виде локальных изменений. После того, как команда выполнена, результат не будет отличаться он того как если бы вы вручную редактировали файлы или многократно выполняли команды svn add или svn delete самостоятельно. Если результат вас устраивает, его можно зафиксировать. Если результат вас не устраивает, просто отмените (svn revert) все сделанные изменения.

Правила записи svn merge позволяют указывать три необходимых аргумента довольно гибко. Вот несколько примеров:

$ svn merge http://svn.example.com/repos/branch1@150 \

http://svn.example.com/repos/branch2@212 \

my-working-copy
$ svn merge -r 100:200 http://svn.example.com/repos/trunk my-working-copy
$ svn merge -r 100:200 http://svn.example.com/repos/trunk

В первом примере записи все три аргумента явно указаны, указываются деревья, в форме URL@REV и указывается целевая рабочая копия. Второй пример записи может использоваться для краткости записи, в ситуациях, когда сравниваются две разных правки по одному и тому же URL. Последний пример демонстрирует возможность не указывать целевую рабочую копию, при этом по умолчанию используется текущая директория.


Как правильнее всего использовать слияние

Ручной контроль слияния


На первый взгляд объединить изменения просто, однако на практике могут возникнуть трудности. Проблема заключается в том, что при многократном объединении изменений одной ветки с другой можно непреднамеренно сделать объединение одних и тех же изменений дважды. Иногда, когда это случается, проблем это не вызывает. При внесении изменений в файл, как правило Subversion предупреждает о том что файл уже содержит изменения и в этом случае не выполняет никаких действий. Однако если уже присутствующие изменения были модифицированы, то возникнет конфликт.

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

К сожалению, Subversion не такая система. Как и CVS, Subversion пока не сохраняет ни какой информации об операциях объединения. При фиксации локальных изменений хранилище понятия не имеет, являются ли эти изменения результатом выполнения команды svn merge или результатом обычного ручного редактирования файлов.

Что это означает для вас, как пользователя? Это означает, что до того момента, пока у Subversion не появится этой функции, вам придется контролировать слияние информации самостоятельно. Лучшим местом для этого является лог-сообщение. Как было показано в предыдущих примерах, рекомендуется, что бы в лог-сообщении был указан конкретный номер правки (или диапазон правок) которые были слиты в вашу ветку. После этого, для того, что бы просмотреть какие изменения ваша ветка уже содержит, вы можете запустить команду svn log. Это позволит быть аккуратнее при выполнении команды svn merge, что бы не пересечься с уже портированными изменениями.

В следующем разделе мы на примерах рассмотрим эту технику в действии.

Предварительные просмотр при объединении


Учитывая, что результатом слияния являются локальные модификации, такая операция не является опасной. Если в начале, при выполнении объединения вы ошиблись, просто отмените изменения (svn revert) и попробуйте еще раз.

Однако, возможна ситуация, когда рабочая копия уже содержит локальные изменения. Изменения, примененные при слиянии будут смешаны с уже существующими и svn revert запустить будет нельзя. Если нельзя будет разделить два набора изменений.

В такой ситуации лучше будет попробовать спрогнозировать или проверить объединения до того, как они произойдут. Самым простым вариантом является запуск svn diff с теми же аргументами, что и для svn merge, это мы уже показывали в первом примере объединения. Другим вариантом предпросмотра является передача команде объединения опции --dry-run:

$ svn merge --dry-run -r 343:344 http://svn.example.com/repos/calc/trunk

U integer.c
$ svn status

# nothing printed, working copy is still unchanged.

Опция --dry-run не вносит локальные изменения в рабочую копию. Показываются только коды статуса которые будут выведены при настоящем объединении. Это полезно для получения «обобщенной» информации об объединении для тех случаев, когда запуск svn diff выдает слишком детальную информацию.

Subversion и наборы изменений

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

В Subversion глобальный номер правки N относится к дереву в хранилище: так выглядит хранилище после фиксации N. Кроме того, это имя для неявного набора изменений: если сравнить дерево N с деревом N-1 вы получите полный патч того, что было зафиксировано. В этом смысле, о «правке N» можно думать не как о дереве файлов, а как о наборе изменений. Если вы пользуетесь системой отслеживания релизов для управления ошибками, вы можете использовать номера правок для того, что бы ссылаться на конкретные патчи, которые исправляют ошибку — например, «этот релиз был исправлен правкой 9238.». После этого, для того, что бы прочитать о том наборе изменений, который исправляет ошибку можно выполнить svn log -r9238, а для того, что бы увидеть сам патч, выполнить svn diff -r9237:9238. В Subversion команда merge тоже использует номера правок. Можно объединить набор изменений из одной ветки в другую указав его в качестве аргумента: команда svn merge -r9237:9238 внедрит набор изменений #9238 в вашу рабочую копию.

Конфликты при объединении


Так же как и команда svn update, svn merge внедряет изменения в рабочую копию. А следовательно тоже может создавать конфликты. Однако конфликты, создаваемые svn merge иногда отличаются и эти отличия рассмотрены в этом разделе.

В начале будем считать, что рабочая копия не имеет локальных изменений. При обновлении (svn update) до конкретной правки, изменения, отправляемые сервером, будут всегда «без проблем» внедрятся в рабочую копию. Сервер создает дельту сравнивая два дерева: виртуальный снимок рабочей копии и дерево файлов, которое вас интересует. Учитывая то, что левая часть сравнения полностью эквивалентна тому, что вы уже имеете, дельта гарантированно правильно конвертирует рабочую копию в правую часть сравнения.

Однако svn merge не может этого гарантировать и может вести себя более хаотично: пользователь может запросить сервер сравнить любые два дерева файлов, даже такие, которые не имеют отношения к рабочей копии! Из этого следует большое количество потенциальных человеческих ошибок. Пользователи иногда будут сравнивать два ошибочных дерева создавая дельту которая не сможет правильно внедриться. svn merge будет пытаться внедрить по возможности больше различий, но иногда это будет не возможно. Так же как команда patch в Unix иногда жалуется на «неудачные попытки» объединения, svn merge будет жаловаться на «пропущенные цели»:

$ svn merge -r 1288:1351 http://svn.example.com/repos/branch

U foo.c

U bar.c


Skipped missing target: 'baz.c'

U glub.c


C glorb.h
$

Возможно, что в предыдущем примере файл baz.c существует в обоих сравниваемых снимках ветки и Subversion пытается применить результирующую дельту для того, чтобы изменить содержимое файла, однако в рабочей копии файл отсутствует. В любом случае сообщение «skipped» означает, что скорее всего пользователь ошибся при указании деревьев для сравнения; классическая ошибка оператора. Если это произошло, то проще всего рекурсивно отменить все изменения, сделанные при слиянии (svn revert --recursive), сразу же после этого удалить все не версионированные файлы и директории и повторно запустить svn merge с другими параметрами.

Обратите внимание на то, что в предыдущем примере в файле glorb.h возник конфликт. Ранее мы договорились, что рабочая копия не имеет локальных изменений: откуда же взялся конфликт? Опять же, так как пользователь мог запустить svn merge для выделения и применения к рабочей копии какой то старой дельты, в результате, такая дельта может содержать изменения, которые не смогут внедриться в рабочий файл без появления проблем, даже если он не имеет локальных изменений.

Еще одно небольшое отличием между svn update и svn merge заключается в названиях файлов, создаваемых при возникновении конфликта. В разделе «Решение конфликтов (при объединении с чужими изменениями)» мы говорили о том, что при обновлении создаются файлы с названиями filename.mine, filename.rOLDREV, и filename.rNEWREV. А svn merge в конфликтной ситуации создает три файла с названиями filename.working, filename.left и filename.right. Здесь, термины «left» и «right» указывают на две стороны сравнения, то есть на используемые при сравнении деревья. Это разделение используемых названий поможет вам отличать конфликты возникшие в результате обновления от конфликтов, возникших в результате объединения.


Учитывать или игнорировать происхождение


При общении разработчиков, использующих 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.)



Достарыңызбен бөлісу:
1   ...   10   11   12   13   14   15   16   17   ...   34




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет