Эта история началась много-много ревизий назад – тогда SVN Репозиторий был девственно чист, и ни один баг еще не осквернил его своим присутствием. Первые коммиты, первые откаты, просмотры лога – все это было так захватывающе, так ново. И разве мог Репозиторий тогда предполагать, что эти первые, такие приятные шаги впоследствии приведут его на хирургический стол?
Репозиторий рос, креп, матерел. Со временем привык к коммитам, появились первые тэги, и даже мечты о ветках перестали казаться несбыточными. Репозиторий познакомился с другими SVN репозиториями, а с некоторыми даже стал обмениваться файлами. Порой он подолгу выкачивал изменения у своих новых друзей, по ходу процесса наслаждаясь анализом диффов.
Первые тучи над горизонтом стали появляться, когда Репозиторий начал все чаще слышать в разговорах разработчиков незнакомые слова “Git” и “DVCS”. Он пробовал расспрашивать об этом своих друзей-репозиториев, но те лишь стыдливо отводили глаза…
Со временем эти тревоги улеглись, однако жизнь уже не была такой как прежде. Репозиторий погрузнел, стал больше задумываться о смысле жизни. Старые связи уже не доставляли удовольствия, а превратились, скорее, в обременительные зависимости. Все чаще стали сниться ночные кошмары про неразрешимые конфликты при мержах.
Жизнь текла размеренно, когда вдруг прекратились коммиты. Тревожные предчувствия вмиг возобновились. Репозиторий, конечно, успокаивал себя мыслями о том, что разработчики, должно быть, в отпуске, и скоро все вернется в привычное русло, но ощущение чего-то плохого не покидало.
Однажды вечером в дверь постучали. “Наконец-то, svn commit!” – радостно подумал Репозиторий и не по годам ловко подскочил к дверям. “Кто там?” – спросил он, но в ответ услышал лишь напряженное сопение за дверью. Гадко засосало под ложечкой. Через мгновение за дверью пробасили: “Открывайте, svnadmin dump”. Дрожащими руками Репозиторий отворил дверь… и пал без чувств.
Коммиты мчались перед глазами один за одним, они то появлялись, то исчезали, и так повторялось вновь и вновь. “svnadmin load, svnadmin dump, svnadmin load, svnadmin dump” – Репозиторий не мог понять, что происходит, он то приходил в сознание, то снова проваливался в забытье. И лишь маленький, мерцающий огонек вдалеке вселял надежду…
Оставим нашего героя ненадолго и попробуем разобраться, что же произошло.
Проницательный читатель может предположить, что ситуация, в которой оказался Репозиторий, как-то связана с распределенными системами контроля версий. Именно так и произошло – разработчики прониклись всеми преимуществами Mercurial и решили мигрировать весь свой код туда. Переход с Subversion на Mercurial, однако, можно осуществить достаточно безболезненно, так зачем же потребовалось мучить Репозиторий?
Дело в том, что ранее разработчики допустили две ошибки, которые теперь проявили себя во всей красе:
Для модификации репозитория нам в первую очередь потребуется утилита svnadmin, которая позволяет делать полный дамп SVN репозитория, дамп для конкретных ревизий или диапазона ревизий, а также накатывать дампы на уже существующий репозиторий. Небольшое отступление для Windows пользователей – данная утилита не является частью TortoiseSVN, но можно установить дополнительный Subversion клиент, например Slik SVN, в котором svnadmin присутствует.
Общая идея модификации репозитория такова: мы должны воссоздать репозиторий с нуля, скопировав “правильные” коммиты без изменений и руками заменив “неправильные”. Под правильностью в нашем случае понимается ситуация, когда коммит не содержит упоминаний об svn:externals и все файлы находятся в trunk. После воссоздания нужно поправить для измененных коммитов дату и время на исходные.
Для того, чтобы понять, как работает метод, рассмотрим модификацию реального репозитория. Я подготовил небольшой тестовый репозиторий, в который присутствуют все указанные выше проблемы. Скачать его дамп (demo_repo.dmp) вместе с остальными дампами, упоминающимися в статье, можно тут.
Репозиторий содержит 8 ревизий, вот описание каждой из них:
Проблема с ревизиями 1, 2, 3 в том, что файлы находятся вне trunk. Чтобы это исправить мы вставим перед ними один коммит, в котором создадим папки branches, tags, trunk; коммиты 1 и 2 модифицируем, чтобы файлы добавлялись в trunk; коммит 3 вовсе удалим.
Ревизии 4 и 5 связаны с svn:externals, мы должны заменить внешние зависимости непосредственно файлами, загружаемыми из внешнего репозитория. Ревизия 7, внешне безобидная, также косвенно связана с svn:externals, подробнее далее.
Итак, начнем. Я буду модифицировать репозиторий под Windows, однако ничто не мешает проделать в точности те же шаги, например, под Linux.
Не стоит сразу пытаться и переместить все коммиты в trunk, и избавиться от svn:externals. Лучше выполнять эти задачи последовательно. Мы начнем со второй задачи.
Сначала мы должны определить все проблемные ревизии, которые потребуется заменить вручную. Открываем полный дамп нашего репозитория в текстовом редакторе (я использую Notepad++) и последовательно ищем вхождения фразы “svn:externals”. Их может быть значительно больше, чем нужно, нас интересуют только такие вхождения:
Для каждого такого места нам нужно найти строку “Revision-number:” выше. Она и содержит нужный нам номер проблемной ревизии.
Когда мы дойдем до конца дампа для нашего тестового репозитория, то должны получить следующие номера проблемных ревизий: 4, 5 и 7.
Теперь нам нужно подготовить частичные дампы для ревизий, не требующих изменений. Для этого:
Теперь необходимо исправить 4-й коммит. Для этого нам потребуется сделать checkout нашего нового репозитория. Для это можно воспользоваться TortoiseSVN либо продолжить работать из командной строки, например так:
Проверяем, что все идет по нашему плану – переходим в папку result_checkout и проверяем, что загружено 3 ревизии:
Теперь мы должны исполнить “правильный” 4-й коммит. Для этого нам потребуется состояние оригинального репозитория на 4-й ревизии. Поэтому делаем checkout и для него, но сразу на ревизию №4:
Последняя команда может немного затянуться, поскольку будут выгружаться файлы с внешнего репозитория.
Сверим часы. Наша рабочая папка выглядит так:
А лог для full_checkout в TortoiseSVN – так:
В логе смотрим, что же изменилось в 4-й ревизии. А изменилось следующее – в trunk была добавлена папка “3rd party”, да не простая, а с выставленным свойством svn:externals:
Поэтому, чтобы поправить 4-й коммит, мы должны в исправляемый репозиторий добавить папку /trunk/3rd party/iTextSharp и закоммитить:
Последствия ошибки могут быть очень неприятными – запросто можем испортить воссоздаваемый репозиторий, и придется начинать все сначала. Поэтому необходимо сразу озаботиться проблемой бэкапа для промежуточных результатов. Сохранять необходимо как минимум репозиторий (result_repo), а также checkout (result_checkout), если его восстановление с нуля занимает существенное время.
Организуем бэкап так – будем сжимать необходимые папки в zip-архив и копировать в некоторую папку. Для этого воспользуемся архиватором 7-zip. Установим его и добавим путь к 7z.exe в переменную окружения PATH. Теперь можно делать бэкап промежуточных состояний репозитория так:
После того, как мы сохранили промежуточный результат, самое время двигаться дальше – нужно сделать вручную коммит №5. Для этого:
После того, как 5-й коммит добавлен, накатываем demo6.dmp:
Делаем бэкап и переходим к последнему серьезному пункту нашей программы – модификации коммита №7. Обновляем full_checkout репозиторий до 7-й ревизии и смотрим список изменений:
Причина, по которой этот коммит требуется модифицировать, ясна не сразу, ведь никаких изменений, касающихся svn:externals не делалось. Для папки trunk было изменено свойство svn:ignore, может в этом причина? Так и есть. Дело в том, что в SVN дампе для ревизии 7 хранится полное описание свойства папки trunk, вместе с svn:externals:
Итак, за дело. Есть 2 способа сделать 7-й коммит: хитрый и прямолинейный.
Первый заключается в том, чтобы взять из оригинального репозитория дамп для 7-й ревизии, удалить из свойства папки trunk упоминание об svn:externals (открываем в текстовом редакторе, удаляем, вычитаем количество удаленных байт из свойств “Prop-content-length” и “Content-length”. Или же пользуемся командой “svnadmin setrevprop”) и накатить такой, уже “правильный”, дамп в новый репозиторий. В данном случае этот подход является наилучшим решением.
Однако интересней рассмотреть второй способ, поскольку в большинстве случаев для реальных репозиториев придется пользоваться именно им. Этот способ заключается в том, чтобы примерить на себе шкуру TortoiseSVN и проделать все изменения 7 коммита самостоятельно.
То, насколько аккуратно мы будем повторять действия SVN клиента, полностью зависит от нашего терпения и внимательности. Не стоит обрабатывать каждое изменение индивидуально, главное добиться, чтобы наборы файлов в исходном и новом репозиториях совпадали. Единственное –перемещения файлов/папки в SVN желательно повторить в точности, чтобы не потерять историю объекта до перемещения. Кстати, на этом этапе также можно удалить файл Fictive, созданный ранее в trunk.
После того, как все изменения произведены, стоит проверить, что затронутые проекты по-прежнему компилируются и тесты проходят. Впрочем, если есть полная уверенность в своих действиях в качестве Subversion клиента, то можно и не проверять.
Далее коммитим наши изменения и выходим на финишную прямую. Накатываем последний дамп demo8.dmp. Обновляем обе рабочие копии result_checkout и full_checkout до последней ревизии и сравниваем их. Если все было проделано правильно, то они должны быть полностью идентичны по набору файлов.
Получаем дамп для измененного репозитория:
Данный дамп полностью избавлен от внешних зависимостей, однако от нас еще потребуются некоторые изменения. Дело в том, что для коммитов, сделанных вручную, в дампе прописано свежее время коммита, а также неверное имя пользователя (обычно совпадает с именем пользователя операционной системы в Windows). Надо исправлять.
Открываем оригинальный и новый дампы в текстовом редакторе и последовательно проходим по модифицированным ревизиям (4, 5, 7 в нашем примере). Выглядеть это будет примерно так:
Теперь нужно аккуратно заменить соответствующие даты в новом дампе на оригинальные. Для этого можно снова воспользоваться утилитой svnadmin и ее опцией “setrevprop”, однако, на мой взгляд, быстрее это сделать руками в текстовом редакторе.
Замена даты является безобидной операцией, а вот замена автора коммита потребует дополнительных усилий. Как можно заметить, значения свойств “Prop-content-length” и “Content-length”, а также число над именем автора коммита для исходного и обновленного дампов не совпадают. Это происходит из-за того, что длина имени автора отличается от исходной. Поэтому сначала меняем автора коммита, а затем обновляем соответствующие значения. Notepad++ предоставляет удобный способ проверить, что все было сделано правильно:
После того, как свойства всех модифицированных коммитов будут исправлены, можно считать этап избавления от svn:externals завершенным. Однако, рекомендую после этого проверить, что полученный дамп полностью корректно загружается в локальный репозиторий:
В ходе данной проверки могут автоматически выявиться ошибки, допущенные в ходе модификации репозитория. В этом случае загрузка дампа прервется в месте ошибки, и мы сможем выяснить причины. Обычно это происходит, если мы испортили содержимое некоторого файла или не обновили какой-то файл. Сообщение об ошибке будет сигнализировать о несовпадении контрольных сумм.
С навыками, полученными на предыдущем этапе, перенести все коммиты в trunk не составит труда. Итак, мы имеем репозиторий without_externals.dmp, в котором первые два коммита сделаны в корень репозитория, а в третьем коммите создаются папки trunk, tags, branches, и все файлы переносятся туда.
План действий:
Разбиваем дамп для репозитория без внешних зависимостей на два, чтобы исключить ненужный коммит.
Открываем дамп final0_2.dmp и заменяем все строки “-path: ” на “ -path: trunk/”. Важно – заменяем в точности так (а не только “Node-path: ”), поскольку пути могут использоваться в свойствах “Node-path” и “Node-copyfrom-path”.
Накатываем final0_2.dmp и final4_8.dmp на новый репозиторий:
Проверям SVN лог для final_checkout, все коммиты должны оперировать файлами только в trunk. Получаем обновленный дамп:
Модифицируем время и автора для 1-й ревизии, а также время 0-й ревизии. За основу берем время 0-й ревизии в without_externals.dmp, используем его для 0-й ревизии в новом дампе, затем прибавляем к нему несколько секунд и устанавливаем для 1-й ревизии.
Проверяем, что полученный дамп в порядке, загружая его в репозиторий:
Если в ходе загрузки возникла ошибка о несовпадении контрольных сумм, вероятней всего строка “-path:” встречалась где-то внутри файлов, и мы ее случайно заменили (в реальных репозиториях мы не можем глазами просматривать все места, требующие замены, поскольку их может быть очень много). При этом бывает, что строка встречается точно в таком виде, как и в свойствах ревизии (например, “Node-path: …”), поэтому ужесточение шаблона замены не поможет.
В нашем примере ошибки не происходит, а это значит, что мы получили полностью пригодный для миграции в Mercurial репозиторий.
Мы рассмотрели ряд приемов, применяемых для модификации SVN репозиториев. Основные моменты:
Репозиторий приходил в себя. Все смешалось в его некогда светлой голове – коммиты, логи, svnadmin. Пелена наркоза еще не рассеялась окончательно, но Репозиторий уже почувствовал – что-то внутри него переменилось.
Открыв глаза, Репозиторий первым делом осмотрелся по сторонам – комната была просторная, но интерьер совершенно незнакомый. На месте дубового шифоньера с коммитами высилась небольшая этажерка, а вместо коробок с тэгами на стене был прикреплен небольшой стикер с малопонятными строками вроде “6fc1d7a7ae346b0a09be5647e94c1561764b8619”. Однако, на удивление, общее убранство комнаты Репозиторию понравилось, всё было оформлено с чувством и по фэн-шую.
Репозиторий не успел еще толком освоиться, как в дверь позвонили. “Здравствуйте! Доставка коммитов” – протараторил невысокий человек в кепке с надписью “Mercurial”. Репозиторий не успел открыть рта, как незнакомец быстро вручил ему два буклета с надписью “hg commit” на обложке и был таков.
Репозиторий вспомнил, что ему уже доводилось испытывать подобное. Давным-давно, в самом начале, незнакомый человек затащил в его квартиру самый первый “svn commit”. Неужели сейчас история повторяется? Репозиторию захватило дух от восторга – мало кому посчастливится вернуться в детство вместе со всем накопленным опытом и знаниями, а с ним, похоже, произошло именно это! И зря он перестал верить в Деда Мороза – не исключено, что это чудо – именно его рук дело…
Репозиторий не знал, что ждет его впереди. Одно он понимал четко – у него началась новая жизнь – интересная, непредсказуемая, с приключениями. Жизнь на планете Mercurial.
Репозиторий рос, креп, матерел. Со временем привык к коммитам, появились первые тэги, и даже мечты о ветках перестали казаться несбыточными. Репозиторий познакомился с другими SVN репозиториями, а с некоторыми даже стал обмениваться файлами. Порой он подолгу выкачивал изменения у своих новых друзей, по ходу процесса наслаждаясь анализом диффов.
Первые тучи над горизонтом стали появляться, когда Репозиторий начал все чаще слышать в разговорах разработчиков незнакомые слова “Git” и “DVCS”. Он пробовал расспрашивать об этом своих друзей-репозиториев, но те лишь стыдливо отводили глаза…
Со временем эти тревоги улеглись, однако жизнь уже не была такой как прежде. Репозиторий погрузнел, стал больше задумываться о смысле жизни. Старые связи уже не доставляли удовольствия, а превратились, скорее, в обременительные зависимости. Все чаще стали сниться ночные кошмары про неразрешимые конфликты при мержах.
Жизнь текла размеренно, когда вдруг прекратились коммиты. Тревожные предчувствия вмиг возобновились. Репозиторий, конечно, успокаивал себя мыслями о том, что разработчики, должно быть, в отпуске, и скоро все вернется в привычное русло, но ощущение чего-то плохого не покидало.
Однажды вечером в дверь постучали. “Наконец-то, svn commit!” – радостно подумал Репозиторий и не по годам ловко подскочил к дверям. “Кто там?” – спросил он, но в ответ услышал лишь напряженное сопение за дверью. Гадко засосало под ложечкой. Через мгновение за дверью пробасили: “Открывайте, svnadmin dump”. Дрожащими руками Репозиторий отворил дверь… и пал без чувств.
Коммиты мчались перед глазами один за одним, они то появлялись, то исчезали, и так повторялось вновь и вновь. “svnadmin load, svnadmin dump, svnadmin load, svnadmin dump” – Репозиторий не мог понять, что происходит, он то приходил в сознание, то снова проваливался в забытье. И лишь маленький, мерцающий огонек вдалеке вселял надежду…
Оставим нашего героя ненадолго и попробуем разобраться, что же произошло.
Глазами разработчиков
Проницательный читатель может предположить, что ситуация, в которой оказался Репозиторий, как-то связана с распределенными системами контроля версий. Именно так и произошло – разработчики прониклись всеми преимуществами Mercurial и решили мигрировать весь свой код туда. Переход с Subversion на Mercurial, однако, можно осуществить достаточно безболезненно, так зачем же потребовалось мучить Репозиторий?
Дело в том, что ранее разработчики допустили две ошибки, которые теперь проявили себя во всей красе:
- Изначально, вопреки всем best practices, стали коммитить в корень репозитория, не удосужившись создать традиционные папки trunk, branches и tags. Значительно позже эти папки были созданы и файлы перенесены, но, как известно, из репозитория коммитов не выкинешь. Теперь, если оставить все без изменений, то при миграции на Mercurial коммиты вне trunk пропадут.
- Как мы помним, наш герой Репозиторий погряз в “обременительных зависимостях”, и речь здесь, конечно, идет об svn:externals. Ранее я рассказывал о противоречивости данной технологии. При миграции на Mercurial на данный момент не существует простого способа перенести svn:externals.
Репозиторий под скальпелем
Для модификации репозитория нам в первую очередь потребуется утилита svnadmin, которая позволяет делать полный дамп SVN репозитория, дамп для конкретных ревизий или диапазона ревизий, а также накатывать дампы на уже существующий репозиторий. Небольшое отступление для Windows пользователей – данная утилита не является частью TortoiseSVN, но можно установить дополнительный Subversion клиент, например Slik SVN, в котором svnadmin присутствует.
Общая идея модификации репозитория такова: мы должны воссоздать репозиторий с нуля, скопировав “правильные” коммиты без изменений и руками заменив “неправильные”. Под правильностью в нашем случае понимается ситуация, когда коммит не содержит упоминаний об svn:externals и все файлы находятся в trunk. После воссоздания нужно поправить для измененных коммитов дату и время на исходные.
Для того, чтобы понять, как работает метод, рассмотрим модификацию реального репозитория. Я подготовил небольшой тестовый репозиторий, в который присутствуют все указанные выше проблемы. Скачать его дамп (demo_repo.dmp) вместе с остальными дампами, упоминающимися в статье, можно тут.
Репозиторий содержит 8 ревизий, вот описание каждой из них:
- В корень репозитория добавлен текстовый файл.
- В корень репозитория добавлен небольшой C# проект HelloWorld.
- Добавлены папки trunk, tags, branches. Все файлы из корня перенесены в trunk.
- Добавлена папка ‘3rd party’, и для нее установлено свойство svn:externals для скачивания файлов из внешнего репозитория в подпапку.
- Свойство svn:externals удалено для папки ‘3rd party’ и установлено на папке trunk.
- Удален текстовый файл из trunk.
- Для папки trunk установлено свойство svn:ignore. Модифицированы некоторые файлы в HelloWorld.
- Добавлен текстовый файл в trunk. Удалена подпапка в HelloWorld.
Проблема с ревизиями 1, 2, 3 в том, что файлы находятся вне trunk. Чтобы это исправить мы вставим перед ними один коммит, в котором создадим папки branches, tags, trunk; коммиты 1 и 2 модифицируем, чтобы файлы добавлялись в trunk; коммит 3 вовсе удалим.
Ревизии 4 и 5 связаны с svn:externals, мы должны заменить внешние зависимости непосредственно файлами, загружаемыми из внешнего репозитория. Ревизия 7, внешне безобидная, также косвенно связана с svn:externals, подробнее далее.
Итак, начнем. Я буду модифицировать репозиторий под Windows, однако ничто не мешает проделать в точности те же шаги, например, под Linux.
Избавляемся от svn:externals
Не стоит сразу пытаться и переместить все коммиты в trunk, и избавиться от svn:externals. Лучше выполнять эти задачи последовательно. Мы начнем со второй задачи.
Сначала мы должны определить все проблемные ревизии, которые потребуется заменить вручную. Открываем полный дамп нашего репозитория в текстовом редакторе (я использую Notepad++) и последовательно ищем вхождения фразы “svn:externals”. Их может быть значительно больше, чем нужно, нас интересуют только такие вхождения:
K 13 svn:externals
Для каждого такого места нам нужно найти строку “Revision-number:” выше. Она и содержит нужный нам номер проблемной ревизии.
Когда мы дойдем до конца дампа для нашего тестового репозитория, то должны получить следующие номера проблемных ревизий: 4, 5 и 7.
Теперь нам нужно подготовить частичные дампы для ревизий, не требующих изменений. Для этого:
- Загружаем наш репозиторий полностью в некоторую локальную папку, назовем ее full_repo. Это делается с помощью следующих вызовов в командной строке:
C:\Subversion> svnadmin create full_repo C:\Subversion> svnadmin load full_repo < demo_repo.dmp
- Далее подготавливаем дампы для тех ревизий репозитория, которые не потребуется менять (1-3, 6 и 8):
C:\Subversion> svnadmin dump full_repo -r 0:3 --incremental >demo0_3.dmp C:\Subversion> svnadmin dump full_repo -r 6 --incremental >demo6.dmp C:\Subversion> svnadmin dump full_repo -r 8 --incremental >demo8.dmp
- Проверяем, что полученные дампы не содержат вхождений строки “svn:externals”. Важно аккуратно проверить это сейчас, случайно допущенная ошибка на этом этапе может сильно осложнить жизнь в дальнейшем.
C:\Subversion> svnadmin create result_repo C:\Subversion> svnadmin load result_repo < demo0_3.dmp
Теперь необходимо исправить 4-й коммит. Для этого нам потребуется сделать checkout нашего нового репозитория. Для это можно воспользоваться TortoiseSVN либо продолжить работать из командной строки, например так:
C:\Subversion> svn co file:///C:/Subversion/result_repo result_checkout
Проверяем, что все идет по нашему плану – переходим в папку result_checkout и проверяем, что загружено 3 ревизии:
C:\Subversion> cd result_checkout C:\Subversion\result_checkout> svn log -l 1 ------------------------------------------------------------------------ r3 | shibaev | 2010-12-09 23:53:09 +0600 (Чт, 09 дек 2010) | 1 line Moved to trunk. ------------------------------------------------------------------------
Теперь мы должны исполнить “правильный” 4-й коммит. Для этого нам потребуется состояние оригинального репозитория на 4-й ревизии. Поэтому делаем checkout и для него, но сразу на ревизию №4:
C:\Subversion\result_checkout > cd .. C:\Subversion> svn co file:///C:/Subversion/full_repo full_checkout -r 4
Последняя команда может немного затянуться, поскольку будут выгружаться файлы с внешнего репозитория.
Сверим часы. Наша рабочая папка выглядит так:
А лог для full_checkout в TortoiseSVN – так:
В логе смотрим, что же изменилось в 4-й ревизии. А изменилось следующее – в trunk была добавлена папка “3rd party”, да не простая, а с выставленным свойством svn:externals:
iTextSharp https://itextsharp.svn.sourceforge.net/svnroot/itextsharp/tags/iTextSharp_5_0_5/iTextSharp/text/xml/simpleparser/
Поэтому, чтобы поправить 4-й коммит, мы должны в исправляемый репозиторий добавить папку /trunk/3rd party/iTextSharp и закоммитить:
- Переходим в result_checkout/trunk
- Создаем папку 3rd party
- Копируем в нее папку iTextSharp из full_checkout
- Удаляем из нее подпапку .svn
- Добавляем (svn add) в репозиторий result_checkout папку 3rd party со всем содержимым
- Делаем коммит, сохраняя оригинальную подпись, например так:
C:\Subversion\result_checkout> svn commit -m"Added external reference"
Последствия ошибки могут быть очень неприятными – запросто можем испортить воссоздаваемый репозиторий, и придется начинать все сначала. Поэтому необходимо сразу озаботиться проблемой бэкапа для промежуточных результатов. Сохранять необходимо как минимум репозиторий (result_repo), а также checkout (result_checkout), если его восстановление с нуля занимает существенное время.
Организуем бэкап так – будем сжимать необходимые папки в zip-архив и копировать в некоторую папку. Для этого воспользуемся архиватором 7-zip. Установим его и добавим путь к 7z.exe в переменную окружения PATH. Теперь можно делать бэкап промежуточных состояний репозитория так:
C:\Subversion> 7z a backups\result_repoX.zip result_repo,где X – номер ревизии (например, сейчас в нашем случае X == 4)
После того, как мы сохранили промежуточный результат, самое время двигаться дальше – нужно сделать вручную коммит №5. Для этого:
- обновляем full_checkout до ревизии №5:
C:\Subversion\full_checkout> svn up -r 5
- смотрим, какие изменения содержит пятый коммит
После того, как 5-й коммит добавлен, накатываем demo6.dmp:
C:\Subversion> svnadmin load result_repo < demo6.dmp
Делаем бэкап и переходим к последнему серьезному пункту нашей программы – модификации коммита №7. Обновляем full_checkout репозиторий до 7-й ревизии и смотрим список изменений:
Причина, по которой этот коммит требуется модифицировать, ясна не сразу, ведь никаких изменений, касающихся svn:externals не делалось. Для папки trunk было изменено свойство svn:ignore, может в этом причина? Так и есть. Дело в том, что в SVN дампе для ревизии 7 хранится полное описание свойства папки trunk, вместе с svn:externals:
Итак, за дело. Есть 2 способа сделать 7-й коммит: хитрый и прямолинейный.
Первый заключается в том, чтобы взять из оригинального репозитория дамп для 7-й ревизии, удалить из свойства папки trunk упоминание об svn:externals (открываем в текстовом редакторе, удаляем, вычитаем количество удаленных байт из свойств “Prop-content-length” и “Content-length”. Или же пользуемся командой “svnadmin setrevprop”) и накатить такой, уже “правильный”, дамп в новый репозиторий. В данном случае этот подход является наилучшим решением.
Однако интересней рассмотреть второй способ, поскольку в большинстве случаев для реальных репозиториев придется пользоваться именно им. Этот способ заключается в том, чтобы примерить на себе шкуру TortoiseSVN и проделать все изменения 7 коммита самостоятельно.
То, насколько аккуратно мы будем повторять действия SVN клиента, полностью зависит от нашего терпения и внимательности. Не стоит обрабатывать каждое изменение индивидуально, главное добиться, чтобы наборы файлов в исходном и новом репозиториях совпадали. Единственное –перемещения файлов/папки в SVN желательно повторить в точности, чтобы не потерять историю объекта до перемещения. Кстати, на этом этапе также можно удалить файл Fictive, созданный ранее в trunk.
После того, как все изменения произведены, стоит проверить, что затронутые проекты по-прежнему компилируются и тесты проходят. Впрочем, если есть полная уверенность в своих действиях в качестве Subversion клиента, то можно и не проверять.
Далее коммитим наши изменения и выходим на финишную прямую. Накатываем последний дамп demo8.dmp. Обновляем обе рабочие копии result_checkout и full_checkout до последней ревизии и сравниваем их. Если все было проделано правильно, то они должны быть полностью идентичны по набору файлов.
Получаем дамп для измененного репозитория:
C:\Subversion> svnadmin dump result_repo >without_externals.dmp
Данный дамп полностью избавлен от внешних зависимостей, однако от нас еще потребуются некоторые изменения. Дело в том, что для коммитов, сделанных вручную, в дампе прописано свежее время коммита, а также неверное имя пользователя (обычно совпадает с именем пользователя операционной системы в Windows). Надо исправлять.
Открываем оригинальный и новый дампы в текстовом редакторе и последовательно проходим по модифицированным ревизиям (4, 5, 7 в нашем примере). Выглядеть это будет примерно так:
Теперь нужно аккуратно заменить соответствующие даты в новом дампе на оригинальные. Для этого можно снова воспользоваться утилитой svnadmin и ее опцией “setrevprop”, однако, на мой взгляд, быстрее это сделать руками в текстовом редакторе.
Замена даты является безобидной операцией, а вот замена автора коммита потребует дополнительных усилий. Как можно заметить, значения свойств “Prop-content-length” и “Content-length”, а также число над именем автора коммита для исходного и обновленного дампов не совпадают. Это происходит из-за того, что длина имени автора отличается от исходной. Поэтому сначала меняем автора коммита, а затем обновляем соответствующие значения. Notepad++ предоставляет удобный способ проверить, что все было сделано правильно:
После того, как свойства всех модифицированных коммитов будут исправлены, можно считать этап избавления от svn:externals завершенным. Однако, рекомендую после этого проверить, что полученный дамп полностью корректно загружается в локальный репозиторий:
C:\Subversion> svnadmin create test_repo C:\Subversion> svnadmin load test_repo < without_externals.dmp
В ходе данной проверки могут автоматически выявиться ошибки, допущенные в ходе модификации репозитория. В этом случае загрузка дампа прервется в месте ошибки, и мы сможем выяснить причины. Обычно это происходит, если мы испортили содержимое некоторого файла или не обновили какой-то файл. Сообщение об ошибке будет сигнализировать о несовпадении контрольных сумм.
Переносим все коммиты в trunk
С навыками, полученными на предыдущем этапе, перенести все коммиты в trunk не составит труда. Итак, мы имеем репозиторий without_externals.dmp, в котором первые два коммита сделаны в корень репозитория, а в третьем коммите создаются папки trunk, tags, branches, и все файлы переносятся туда.
План действий:
- Создаем чистый репозиторий, в него коммитим пустые папки branches, tags, trunk
- Разбиваем дамп without_externals.dmp на два – ревизии с 0-й по 2-ю и ревизии с 4-й по 8-ю. Коммит №3 выкидывается!
- В первом дампе (с 0 по 2 ревизии) добавляем ко всем путям в файле префикс “trunk/”
- Последовательно накатываем оба дампа на наш новый репозиторий
- Проверяем, что не было допущено ошибок, и делаем полный дамп получившегося репозитория
- Модифицируем время и автора для первого коммита
C:\Subversion> svnadmin create final_repo C:\Subversion> svn co file:///C:/Subversion/final_repo final_checkout C:\Subversion> mkdir branches tags trunk C:\Subversion> svn add branches tags trunk C:\Subversion> svn commit -m"Prepared repository structure"
Разбиваем дамп для репозитория без внешних зависимостей на два, чтобы исключить ненужный коммит.
C:\Subversion> svnadmin create without_externals_repo C:\Subversion> svnadmin load without_externals_repo < without_externals.dmp C:\Subversion> svnadmin dump without_externals_repo -r 0:2 --incremental >final0_2.dmp C:\Subversion> svnadmin dump without_externals_repo -r 4:8 --incremental >final4_8.dmp
Открываем дамп final0_2.dmp и заменяем все строки “-path: ” на “ -path: trunk/”. Важно – заменяем в точности так (а не только “Node-path: ”), поскольку пути могут использоваться в свойствах “Node-path” и “Node-copyfrom-path”.
Накатываем final0_2.dmp и final4_8.dmp на новый репозиторий:
C:\Subversion> svnadmin load final_repo < final0_2.dmp C:\Subversion> svnadmin load final_repo < final4_8.dmp
Проверям SVN лог для final_checkout, все коммиты должны оперировать файлами только в trunk. Получаем обновленный дамп:
C:\Subversion> svnadmin dump final_repo > final.dmp
Модифицируем время и автора для 1-й ревизии, а также время 0-й ревизии. За основу берем время 0-й ревизии в without_externals.dmp, используем его для 0-й ревизии в новом дампе, затем прибавляем к нему несколько секунд и устанавливаем для 1-й ревизии.
Проверяем, что полученный дамп в порядке, загружая его в репозиторий:
C:\Subversion> svnadmin create test_final_repo C:\Subversion> svnadmin load test_final_repo < final.dmp
Если в ходе загрузки возникла ошибка о несовпадении контрольных сумм, вероятней всего строка “-path:” встречалась где-то внутри файлов, и мы ее случайно заменили (в реальных репозиториях мы не можем глазами просматривать все места, требующие замены, поскольку их может быть очень много). При этом бывает, что строка встречается точно в таком виде, как и в свойствах ревизии (например, “Node-path: …”), поэтому ужесточение шаблона замены не поможет.
В нашем примере ошибки не происходит, а это значит, что мы получили полностью пригодный для миграции в Mercurial репозиторий.
Подведем итоги
Мы рассмотрели ряд приемов, применяемых для модификации SVN репозиториев. Основные моменты:
- Утилита svnadmin – основной инструмент. Позволяет загрузить дамп в репозиторий, получить дамп для нужных ревизий.
- Можем чередовать в модифицируемом репозитории “ручные” коммиты и загрузку существующих дампов. Типичный прием – выкидываем из репозитория ненужные коммиты и заменяем (или просто пропускаем) их в новом репозитории.
- Делаем бэкап для промежуточных результатов как можно чаще.
- Для всех типовых операций (загрузка дампа, создание дампа, чекаут, бэкап) удобно использовать bat или bash скрипты.
- Когда модифицируем SVN дамп в текстовом редакторе – не забываем обновлять числовые значения – размеры соответствующего контента в байтах.
- Если наш SVN дамп большой (>700 Мб), то появляются проблемы с его редактированием, поскольку большинство текстовых редакторов под Windows не могут его нормально открыть. Notepad++ не исключение. Я пробовал множество разных вариантов, помог лишь EmEditor, нормально работающий даже на машине с небольшим количеством оперативной памяти.
- Если после модификации не удается загрузить репозиторий из полученного дампа – либо что-то напутали со смещениями, либо забыли обновить какой-то файл, либо испортили содержимое файла.
- Когда приходится самостоятельно повторять изменения в некотором коммите, бывает полезно рекурсивно удалить внутри какой-нибудь папки все подпапки .svn. Для этого можно воспользоваться таким скриптом (для Windows), который рекурсивно удаляет все подпапки в текущей директории:
FOR /F "tokens=*" %%G IN ('DIR /B /AD /S *.svn*') DO RMDIR /S /Q "%%G"
- Не стоит каким-либо образом модифицировать подписи к коммитам, поскольку это влияет на количество байт в описании ревизии в дампе. Из-за этого будет чуть сложнее обновлять имя автора коммита, поскольку придется учитывать еще и разницу в длине подписи к коммиту.
Заключение
Репозиторий приходил в себя. Все смешалось в его некогда светлой голове – коммиты, логи, svnadmin. Пелена наркоза еще не рассеялась окончательно, но Репозиторий уже почувствовал – что-то внутри него переменилось.
Открыв глаза, Репозиторий первым делом осмотрелся по сторонам – комната была просторная, но интерьер совершенно незнакомый. На месте дубового шифоньера с коммитами высилась небольшая этажерка, а вместо коробок с тэгами на стене был прикреплен небольшой стикер с малопонятными строками вроде “6fc1d7a7ae346b0a09be5647e94c1561764b8619”. Однако, на удивление, общее убранство комнаты Репозиторию понравилось, всё было оформлено с чувством и по фэн-шую.
Репозиторий не успел еще толком освоиться, как в дверь позвонили. “Здравствуйте! Доставка коммитов” – протараторил невысокий человек в кепке с надписью “Mercurial”. Репозиторий не успел открыть рта, как незнакомец быстро вручил ему два буклета с надписью “hg commit” на обложке и был таков.
Репозиторий вспомнил, что ему уже доводилось испытывать подобное. Давным-давно, в самом начале, незнакомый человек затащил в его квартиру самый первый “svn commit”. Неужели сейчас история повторяется? Репозиторию захватило дух от восторга – мало кому посчастливится вернуться в детство вместе со всем накопленным опытом и знаниями, а с ним, похоже, произошло именно это! И зря он перестал верить в Деда Мороза – не исключено, что это чудо – именно его рук дело…
Репозиторий не знал, что ждет его впереди. Одно он понимал четко – у него началась новая жизнь – интересная, непредсказуемая, с приключениями. Жизнь на планете Mercurial.