Subversion: cлияние переименований файлов

    — Зачем ты, Белка, летишь за мной, Кабаном?
    — Не знаю, Кабан! Приказ Хорька. Как понял? Приём.
    — Ни хера не понял! Какого Хорька, Белка? Я Кабан. Кто такой Хорёк? Кто это? Приём.
    — Кабан, ты дятел! Как понял? Приём.
    — Понял тебя, Белка. Я — Дятел. Повторяю вопрос про хорька. Кто это?
    — Кабан, сука, ты всех заманал, лети вперёд молча! Конец связи.
    Виктор Шендерович

    Как известно, 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. Облегчить её могут небольшие скрипты, написание которых автор оставляет в качестве домашнего задания.

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

    Ссылки по теме

    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 21
    • +7
      Однажды мне ваш топик должен очень помочь. Спасибо, в закладочки. :)
      • +1
        Спасибо. Нет ли информации о том как поддерживают такие действия визуальные средства TortoiseSVN или SubClipse? Возможно, в них уже встроено такое поведение…
        • +4
          Полезно, хотя я все больше отказывюсь от свн в пользу git
          • +2
            и это верно. «Рассмотрим способы их решения.»:
            1) Способ первый — использовать git, hg, bzr
            2) Способ второй — Берем howto и начинаем фиксить проблемы дизайна svn.
            • +1
              Насколько я знаю и насколько видно из письма Линуса, git не умеет мёржить переименованные и изменённые файлы. Возможно, я ошибаюсь, поэтому и прошу в заметке писать содержательные комментарии о решении данной задачи в других системах управления версиями, а не просто отсылать к ним.
              • 0
                GIT хорошо поддерживает то, о чем вы написали — мердж происходит автоматически.
                Конфликт может возникнуть только при стандартной ситуации типа «одна и та же часть файла изменилась в разных бранчах», но к переименованию это никакого отношения не имеет.
                • 0
                  Линус в своём письме утверждает обратное:
                  For example, let’s say that you have the common commit A, and file “x”, and two paths (B and C) where B has renamed the file “x” to “y”, and C has modified file “x”. You end up with the schenario that our trivial merge fails to handle, and right now we give up, and don’t help the user very much at all.
                  • 0
                    Насколько я понимаю, на момент написания этого письма (5 сентября 2005 г.) так оно и было.
                    Но на текущий момент в нашем проекте никаких проблем с мерджингом переименованных файлов не наблюдается.
                    В теоретические нюансы алгоритма не углублялся — и так ведь работает.
                    • 0
                      Спасибо, тогда это действительно ещё один повод перейти на git.
                      • 0
                        Переходите, не пожалеете!
                        Нам сначала было весьма непривычно, но как только мозг освобождается от стереотипов SVN/CVS, приходит осознание того, насколько же это классная штука :)
                        • 0
                          Можно ли перевести на GIT большой проект вместе с историей? Т.к. за жизнь проекта похожие случаи наблюдались неоднократно… Можете посоветовать какие-либо полезные утилиты? Спасибо.
                          • 0
                            На момент миграции в нашем репозитории было примерно 4500 ревизий.
                            Миграцию проводил не я, но, насколько я знаю, она прошла абсолютно гладко.
                            Стандартная программка git-svn отлично с этим справляется.
                            В инете хватает материалов на эту тему, например вот.
          • 0
            Занес в закладки ;-)
            • –2
              После таких статей начинать пользоваться системами контроля версий как-то ссыкотно. Полный бекап кода рулит без альтернатив :).
              • НЛО прилетело и опубликовало эту надпись здесь
                • 0
                  Системы контроля версий как раз и позволяют вам иметь полный бэкап кода за любую дату и в любой ветке разработки. Плюс в помощь предоставляется автоматическое сравнение правок с подсветкой различий между ними и лог коммитов-сохранений с вашими комментариями, по желанию подробными, также привязываемыми к каждой правке.
                  Присмотритесь попристальней, поймете, что это благо.
                  P.S. А архивчики с бэкапами проекта вы наверное датами нумеруете?
                  • 0
                    > Subversion не смогла применить изменения, т. к. не нашла файл foo.txt в рабочей копии.

                    > Subversion удалила файл foo.txt и добавила копию файла bar.txt из ветки $source. При этом все изменения в файле foo.txt, произведённые в текущей ветке, оказались утеряны.

                    Вопрос: если сразу не замечаешь эти нестыковки, кусок кода будет утерян чтоли? Особенно радует, что изменения в текущей ветке оказались утеряны. То есть, коммит прошел, и привет — пиши заново? Если, конечно, сразу внимание обратишь. А то можно и через неделю обнаружить, и вспоминать — а что там было то?
                    • 0
                      Имеется в виду, что изменения файла в текущей ветке не попадут в коммит. Но благодаря тому, что любая система управления версиями хранит все предыдущие состояния файла, любой код можно восстановить, просто отменив коммит.
                      • 0
                        Ну все-таки не «отменить коммит», а вернуться к прежней правке ;)
                        > Особенно радует, что изменения в текущей ветке оказались утеряны.
                        Еще раз… Никакие изменения не теряются. Все что вы редактируете, мержите (проводите слияние веток) — эти операции выполняются в вашей рабочей копии проекта. В любой момент времени с помощью svn status, svn diff вы можете с точностью до замененного символа просмотреть, чем отличается ваша раб. копия от крайней HEAD или любой другой правки в хранилище.
                        Смысл слияния, вроде, прозрачен и понятен всем — например, объединить изменения кода, накопленные во временных ветках, разрабатываемых разными программистами в одну главную ветку, из которой будет собран релиз. Но почему-то, наткнувшись, на проблемы, подобные сабжу, забывается, что команда svn merge — это всего лишь инструмент, помогающий провести объединение кода из разных мест наиболее быстрым образом, поскольку эта команда старается отслеживать всю историю изменений в указанном для слияния диапазоне правок. Конечное слово перед коммитом остается за программистом.
                        Наивно предполагать, что коммит получится провести сразу после выполнения svn merge, скорее всего, часть файлов войдет в конфликтующее состояние, требующее обязательного «ручного» анализа с принятием/удалением/редактированием своих/чужих вариантов отдельных строк кода, которые в спорных случаях довольно удобным образом помечаются, плюс рядышком в рабочей копии merge подкладывает пару файликов .left, .right с содержимым, взятым из «левой» и «правой» сравниваемых версий соответственно.
                        Задача сводится к устранению конфликтов в нескольких файлах после merge или update, а не поиску и перепроверке всех измененных мест в проекте.
                        Автор статьи указал случай, за что ему огромное спасибо (взял на заметку), когда такого конфликта SVN не подсказывает, и теперь на переименования файлов в проекте буду обращать отдельное внимание.
                        К сожалению, не настолько силен, чтобы дописать какой-нибудь триггер на переименование файлов для отслеживания таких граблей, но статья определенно пошла на пользу.
                        • 0
                          Триггер написать вряд ли получится, т. к. нельзя отличить move от copy + delete :-(
                • 0
                  Спасибо за статью :) Только сегодня ощутили все прелести неотслеживания svn-ом переименований при merge, обошлось правда малой кровью.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.