Slider Background

октября 2015

Подрепозитории

Принцип DRY в действии

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

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

Какие у нас альтернативы

Использовать общий код в проектах можно в скомпилированном виде или в виде исходных файлов.

  • Для подключения общего кода в виде dll удобно использовать менеджер пакетов Nuget. Так же с его помощью можно поставлять отладочную информацию. Но он работает только для .NET, а в нашей фирме есть проекты на других платформах. 
  • Использование исходного кода позволяет производить отладку интерактивно с возможностью модификации. И у нас часто возникает необходимость изменения общего кода при работе в рамках проекта.
    Эти способы не исключают друг друга. Всегда можно одну и ту же общую сборку использовать в одних проектах одним способов, а в других - другим. Все определяется описанными различиями.

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

    Как это работает

    Идея - главный репозиторий хранит в себе ссылки на  используемые подрепозитории и на нужные их версии.

    Давайте рассмотрим механизм работы подрепозиториев. За работу с ними отвечает всего 2 файла: .hgsub и .ghsubstate, которые так же хранятся с основном репозитории. Пример файла .hgsub:
    Commons/Common = http://src/Common
    Commons/CommonSys = http://src/CommonSys
    Слева относительный путь до подрепозиториев на диске, справа - адреса их родителей. Файл .hgsubstate хранит состояние подрепозиториев, а именно - ревизии, на которую обновлены подрепозитории. Вручную обычно не редактируется. Выглядит она примерно так:
    a33509b3d9464c9a99b5c17759d6fb23e307ccc6 Commons/Common
    2c38e78a41aa2f1e2825bb0bbae624a61363e2ba Commons/Common
    При работе с основным репозиторием мы всегда видим изменения в подрепозиториях.


    Рис. 1 Отображение изменений в подрепозитории.

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

    Схема использования

    Вот преимущества использования подрепозиториев:
    • Каждый репозиторий может быстро переключаться между версиями подрепозиториев;
    • Мы всегда имеем непосредственный доступ к исходникам общего кода, которые можно напрямую включить в наш проект;
    • При затягивании репозитория с сервера вместе с ним затягиваются все подрепозитории (в т.ч. и подрепозитории подрепозиториев, если такие есть)
    Общая схема выглядит так:

    Но что, если “Общий код 1” использует “Общий код?”



    На клиентской машине налицо дублирование кода, ни к чему хорошему не приведёт. Поэтому мы можем убрать ссылку из “Нашего проекта” на “Общий код” и перенастроить ссылки на проекты “Общего кода”, находящихся в составе “Общего кода 1”:


    В конечном итоге получается довольно большая вложенность подрепозиториев. При внесении изменений и последующем использовании Rebase в подрепозиториях возникают проблемы с появлением ссылок на несуществующие ревизии [более подробное описание в статье по Rebase], а также поддержка такой вложенности непроста.
    Разработчики Mercurial рекомендует использование тонких репозиториев, которые служат только для хранения остальных репозиториев в качестве подрепозиториев:

    Тонкий репозиторий всего лишь хранит информацию о связи разных версий подрепозиториев.
     

    Создаём свой первый подрепозиторий

    Чтобы Mercurial правильно определил текущую ревизию подрепозиториев, давайте определим последовательность действий:
    1. Копируем репозитории, которые хотим сделать подрепозиториями, в нужное место каталога основного репозитория.
    2. Добавляем папку через меню в черепахе (см. рисунок 2).
    3. В создавшемся файле .hgsub путь родительского репозитория нового подрепозитория будет совпадать с относительным путём до него, исправляем на действительный путь до родителя. 
    4. Пункты 2 и 3 можно сделать через командную строку или просто добавлением файла .hgsub, тогда не придётся его исправлять, сразу создаём правильный:
      $ echo "ПодРепа1 = http://src/Subrepo1" > .hgsub
      $ hg add .hgsub
    5. Всё, можно фиксировать изменения.

    Рис. 2 Добавление подрепозитория.

    Особенности использования
    1. Создание ветки в основном репозитории никак не повлияет на подрепозитории и наоборот.
    2. Если кто-то другой внесёт изменения в подрепозитории, версия нашего подрепозитория не изменится, если мы не обновимся в нём на новую версию или если кто-то не сделает это за нас и не внесёт изменения  в файл .hgsubstate в основном репозитории.

    Чего мы лишаемся без подрепозиториев?

    Можно задаться вопросом, зачем нам именно такая технология? Что нам мешает просто клонировать репозитории общего кода рядом с репозиторием проекта и не использовать подрепозитории? Мы так же будем выбирать нужную нам версию. Чего мы лишаемся:
    • Никто кроме нас не будет знать, какую версию общего кода мы используем;
    • Не сможем централизованно следить за незафиксированными изменениями;
    • Новый сотрудник должен будет предварительно узнать, какие репозитории необходимо получить и на какие ревизии обновиться, чтобы начать работу с проектом.
    • В конфигурации билд-сервера необходимо настраивать работу со всеми репозиториями, вместо одного.
    В любом случае нет универсального решения. Выбор нужно осуществлять, исходя из ваших потребностей. Надеюсь, описанный способ поможет сделать правильный выбор.
    Подробнее