Pull to refresh

Рефакторинг проекта в SVN с помощью ANT

Reading time 5 min
Views 2K
В статье описывается способ разделения логики и реализации логики в ant-скриптах, примененный для решения одной практической задаче по рефакторингу большого проекта в SVN-репозитории.

Предыстория

Имеется проект в SVN из 15 000 файлов и 5 000 папок. Проекту почти 10 лет, на нем поработало несколько поколений разработчиков разной квалификации. В какой-то момент, пару-тройку лет назад, а может и раньше, архитектура проекта «потекла». Разные модули и слои стали писаться в разных стандартах организации кода, возникли циклические зависимости между модулями. В итоге в SVN за долгие года образовалась свалка. Проект собирается, но совершенно шаманским способом.

Задача

Привести код к единому формату хранения. При этом сохранить историю изменений по каждому файлу и не останавливать процесс разработки.

Сложности

Сохранить историю по одному файлу или папке в SVN довольно просто с помощью команды svn copy. При небольшом количестве файлов все можно сделать вручную.
С разбором большого проекта сложно. Пока будешь вручную разбирать 15 000 файлов, разработчики накоммитят новых изменений и их тоже нужно будет копировать. Замкнутый круг.
Нужна автоматизация. Скриптик, который раз! — и переводит проект в новую структуру.

Результат

Задача была выполнена, а побочным продуктом стал подход к написанию ANT-скриптов, который в большом программировании называется инкапсуляция. Хочу поделиться полученным подходом.


Исходные коды (значительно укороченные, без названий компании и продукта) доступны на гуглокоде.
  • mapping.xml — маппинг, откуда и куда копировать, по какому правилу
  • old2new.properties — глобальный конфиг.
  • macros.ant.xml — подключаемый ant-файл с реализацией копирования в файловой системе
  • macros.svn.xml — подключаемый ant-файл с реализацией копирования с svn copy.


История того, как я пришел к таким скриптам.

Составил план действий:
  1. придумать новую структуру
  2. написать скрипт для копирования в файловой системе проекта из старой структуры в новую
  3. отладиться на новой структуре, написать скрипты сборки и т.п. — отладка может занять много времени и десятков (если не сотен) запусков операций копирования в файловой системе
  4. написать скрипт, который готовит скрипт с svn copy
  5. отладиться на новой структуре в svn
  6. объявить час Х, прогнать скрипт и перевести всех разработчиков на новую структуру.


Реализация плана действий:

Описание новой структуры из пункта один выходит за рамки данной статьи.
Скрипт для копирования можно писать на bat, bash.
Поскольку у нас используется средство сборки ANT, то я начал писать его на нем.
В моем случае для написания ANT-скрипта и получения первого успешного результата потребовалось пару недель.

Скрипт состоял из команд такого вида:
       <mkdir dir="@{to}"/>
            <copy todir="@{to}" overwrite="false">
                <fileset dir="@{from}">
                    <include name="@{include}"/>
					<include name="@{include}"/>
					<exclude name="@{include}"/>
                </fileset>
            </copy>


Он получился очень длинным, плохо читаемым и модифицируемым.
У него было одно достоинство — он работал. Но предстояло писать подобный файл для svn copy… А это несколько напрягало.
После напряженных раздумий, решил, что надо писать по-другому. Избавляться от copy-paste, выносить в отдельный файл маппинг и прятать реализацию в подключаемых файлах. Но как это сделать в ANT? С помощью макросов для ANT!

C помощью директивы macrodef определяем новый таск в ant.macros

    <macrodef name="copy_by_pattern">
        <attribute name="from"/>
        <attribute name="to"/>
        <attribute name="include"/>
        <sequential>
            <echo message="copy from dir @{from} to dir @{to} by pattern @{include}"/>
            <mkdir dir="@{to}"/>
            <copy todir="@{to}" overwrite="false">
                <fileset dir="@{from}">
                    <include name="@{include}"/>
                </fileset>
            </copy>
        </sequential>
    </macrodef>


Таск copy_by_pattern копирует файлы по маске include.

Код выше преобразуется в

       <copy_by_pattern from="${dir.input.base}/module1"
                         to="${dir.output.base}"
                         include="**"/>
        <copy_by_pattern from="${dir.input.base}/module2"
                         to="${dir.output.base}"
                         include="**"/>
        <copy_by_pattern from="${dir.input.base}/module3"
                         to="${dir.output.base}"
                         include="**"/>


Добиваемся того, чтобы он работал как и до этого.
А потом пишем реализацию copy_by_pattern для svn copy в svn.macros

    <macrodef name="copy_by_pattern">
        <attribute name="from"/>
        <attribute name="to"/>
        <attribute name="include"/>
        <sequential>
            <fileset id="localfs" dir="@{from}">
                <include name="@{include}"/>
            </fileset>
            <local name="out.script"/>
            <pathconvert property="out.script" refid="localfs" pathsep="${line.separator}">
                <chainedmapper>
                    <identitymapper/>
                    <regexpmapper from="@{from}/(.*)"
                                  to="${cli.command} @{from}/\1       @{to}/\1"
                                  handledirsep="yes"/>
                </chainedmapper>
            </pathconvert>
            <echo file="${script.filename}"
                  message="${out.script}${line.separator}"
                  append="true"/>
        </sequential>
    </macrodef>	


Этот таск получает файлсет и конвертирует его локальную переменную (появились в ANT 1.8) out.script с помощью pathconvertи regexpmapper. Затем выводит фрагмент скрипта с svn copy в файл ${script.filename}

Примечание: в ANT до версии 1.8 не было локальных переменных. Записав в переменную значение, его потом нельзя изменить.

Пример фрагмента скрипта с svn copy, получаемого на выходе:

svn copy --parents D:\path_old\app.xml D:\path_new\app.xml
svn copy --parents D:\path_old\Cls.java D:\path_new\Cls.java
svn copy --parents D:\path_old\page.jsp D:\path_new\page.jsp
svn copy --parents D:\path_old\js.js D:\path_new\js.js

ключ --parents создаст все необходимые новые директории

Замечание о команде svn copy

У нее есть 4 варианта копирования:
WC -> WC: copy and schedule for addition (with history)
WC -> URL: immediately commit a copy of WC to URL
URL -> WC: check out URL into WC, schedule for addition
URL -> URL: complete server-side copy; used to branch and tag


Наиболее удобен вариант WC -> WC:, потому что все остальные выполняют немедленный коммит в репозиторий. Перетасовка 15 000 файлов проекта создаст 15 000 коммитов. Вариант WC -> WC позволяет делать один отложенный коммит.

Заключение

Пересмотрите свои ANT-скрипты.
Если вы видите в своих ANT-скриптах много повторяющихся фрагментов,
их можно объединить в новый таск с помощью macrodef.
А заодно можете разделить логику от реализации.
Tags:
Hubs:
+15
Comments 4
Comments Comments 4

Articles