— Зачем ты, Белка, летишь за мной, Кабаном?
— Не знаю, Кабан! Приказ Хорька. Как понял? Приём.
— Ни хера не понял! Какого Хорька, Белка? Я Кабан. Кто такой Хорёк? Кто это? Приём.
— Кабан, ты дятел! Как понял? Приём.
— Понял тебя, Белка. Я — Дятел. Повторяю вопрос про хорька. Кто это?
— Кабан, сука, ты всех заманал, лети вперёд молча! Конец связи.
Виктор Шендерович
Как известно, Subversion не умеет отслеживать переименования файлов. Согласно документации, команда
svn move
равносильна svn copy
с последующим svn delete
. Такое поведение вызывает большие проблемы при слиянии веток. Рассмотрим способы их решения.В примерах вместо полного пути к ветке используется переменная
$source
, которую можно создать так:export source=https://example.com/svn/trunk
Файл переименован в текущей ветке
Пусть в текущей ветке файл
foo.txt
был переименован в bar.txt
. Попробуем перенести изменения из ветки $source
, где файл всё ещё называется foo.txt
:svn merge $source -r 10:20
Skipped missing target: 'foo.txt'
Subversion не смогла применить изменения, т. к. не нашла файл
foo.txt
в рабочей копии. Что делать? Явно указать новое имя файла:svn merge $source/foo.txt -r 10:20 bar.txt
U bar.txt
Subversion взяла изменения, произошедшие в файле
foo.txt
с 10 по 20 ревизию, и применила к файлу bar.txt
, чего мы и добивались.Файл переименован в исходной ветке
Пусть в ветке
$source
файл foo.txt
был переименован в bar.txt
. В текущей ветке файл по-прежнему называется foo.txt
. В обеих ветках у файлов поменялось содержимое. Попробуем склеить изменения:svn merge $source -r 10:20
A bar.txt
D foo.txt
Что произошло? Subversion удалила файл
foo.txt
и добавила копию файла bar.txt
из ветки $source
. При этом все изменения в файле foo.txt
, произведённые в текущей ветке, оказались утеряны.Что делать? Во-первых, отменить разрушительные действия
svn merge
:svn revert foo.txt bar.txt
Reverted 'foo.txt'
Reverted 'bar.txt'
rm bar.txt
Во-вторых, локально переименовать файл
foo.txt
в bar.txt
:svn move foo.txt bar.txt
A bar.txt
D foo.txt
В-третьих, наложить на него изменения, произошедшие с файлом в ветке
$source
:svn merge $source/foo.txt@10 $source/bar.txt@20 bar.txt
U bar.txt
Обратите внимание: мы берём разницу между файлом
foo.txt
десятой ревизии и файлом bar.txt
двадцатой ревизии и накладываем её на локальный bar.txt
.Результат: файл переименован и содержит изменения из обеих веток.
Файл переименован в обеих ветках
Пусть в ветке
$source
файл foo.txt
получил название bar.txt
, а в текущей — baz.txt
. В обеих ветках у файлов поменялось содержимое. Попробуем склеить изменения:svn merge $source -r 10:20
A bar.txt
Skipped missing target: 'foo.txt'
Subversion скопировала файл
bar.txt
из ветки $source
, а вот удалить foo.txt
не смогла. Как и в предыдущем случае, возвращаем всё назад:svn revert bar.txt
Reverted 'bar.txt'
rm bar.txt
Дальнейшие действия зависят от того, какое имя мы считаем правильным: текущее (
baz.txt
) или приехавшее из ветки $source
(bar.txt
). В первом случае достаточно наложить разницу между foo.txt
и bar.txt
на baz.txt
:svn merge $source/foo.txt@10 $source/bar.txt@20 baz.txt
U baz.txt
Во втором случае необходимо сначала переименовать
baz.txt
в bar.txt
и уже потом накладывать разницу:svn move baz.txt bar.txt
A bar.txt
D baz.txt
svn merge $source/foo.txt@10 $source/bar.txt@20 bar.txt
U bar.txt
Заключение
Как видно, слияние переименований файлов — непростая задача в Subversion. Облегчить её могут небольшие скрипты, написание которых автор оставляет в качестве домашнего задания.
Предваряя холивар на тему «Какая система управления версиями лучше», автор будет благодарен за содержательные комментарии о том, как задача отслеживания переименованных файлов решается в других системах.