Pull to refresh

Практические аспекты использования svn:externals

Reading time6 min
Views17K
Несмотря на то, что распределенные системы контроля версий (Git, Mercurial, Bazaar) набирают все большую популярность, старый добрый Subversion по-прежнему широко используется. В данной статье я рассмотрю плюсы и минусы использования на практике внешних зависимостей (svn:externals) в SVN репозиториях.

Рассмотрим на примере, что из себя представляют svn:externals. Предположим, у нас есть проект, в котором используется сторонний open-source код, например, известная PDF библиотека iText. Обычно мы полностью копируем весь необходимый код этой библиотеки к себе в репозиторий. Впоследствии, при выходе обновлений iText, вручную заменяем старые файлы на новые.

svn:externals предоставляют более удобный способ. Выбираем некоторую папку нашего репозитория и задаем для нее свойство svn:externals в таком виде (как это сделать c помощью TortoiseSVN):

iText https://itext.svn.sourceforge.net/svnroot/itext/trunk

Первый параметр определяет, куда скачивать изменения, второй – откуда. Теперь каждый раз, когда мы забираем свежие обновления из репозитория, автоматически скачивается актуальная версия iText. Удобно.

О плюсах


Рассмотрим преимущества, которые дает нам использование svn:externals.

Избегаем дублирования

Принцип DRY (Don’t Repeat Yourself) – краеугольный камень в разработке качественного ПО — распространяется и на управление исходным кодом. Если одни и те же файлы дублируются в нескольких репозиториях – это не очень хорошо.

Например, в сложившихся командах, как правило, формируется база общего кода, который постоянно используется в различных проектах. Все проекты необязательно будут в одном большом репозитории. Скорее всего – в разных. В этом случае без svn:externals мы обречены на копирование одних и тех же файлов между разными репозиториями, а так же на ручное их обновление везде в случае изменений.

Не занимаем место в репозитории

Прямое следствие предыдущего пункта – исходный код хранится только в оригинальном репозитории, мы лишь загружаем его оттуда к себе в рабочую копию. Размер репозитория может быть очень критичным, если вы пользуетесь платным SVN хостингом с ограничением на общий размер репозитория (-ев).

Легче структурировать проекты и модули

Разумно выглядит разделять проекты по правилу “1 проект <-> 1 репозиторий”. При таком подходе использование svn:externals помогает упростить разделение зависимых проектов.

Разумеется, мы можем работать над всеми проектами/модулями в пределах одного репозитория, но такой подход не лишен недостатков. Например, все разработчики могут загружать к себе в рабочие копии много ненужных изменений. Не получится ограничить доступ разработчикам к тем проектам, к которым у них доступа быть не должно. Рядом оказываются несвязанные проекты. Да и вообще держать всё в куче не выглядит правильным подходом.

Впрочем, если мы все же работаем в одном большом репозитории, svn:externals также могут быть полезны. Пример: пусть в нашем репозитории (в trunk) следующая структура папок: /Common/C# и /OurCustomer/Windows/HelloCSharp/. Если в проекте HelloCSharp мы хотим использовать код из Common/C#, то у нас три пути:
  1. копировать нужный код в HelloCSharp
  2. использовать относительные пути
  3. использовать svn:externals для загрузки кода из our_repository/trunk/Common/C# в HelloCSharp/Common, например.
Первый путь очевидно плох – ручной труд и дублирование часто порождают ошибки.
Второй – нормален, но с небольшими недостатками. Например, при переносе папки Common/C# будем вынуждены менять все относительные пути в HelloCSharp. Или – чтобы посмотреть на общие файлы придется походить по папкам в репозитории.
Третий же путь через svn:externals выглядит наиболее идеологически правильным. Мы просто подгружаем common код в указанное место, по сути – просто ссылаемся на него. Своего рода символьная ссылка в рамках SVN.

Проще использовать open-source код в нашем репозитории

То, о чем рассказывалось в самом первом примере. Если мы хотим залить весь boost к себе в репозиторий – не стоит. Использовать для этой цели svn:externals будет более уместным.

О минусах


Плюсы выглядят очень убедительно, однако не стоит спешить с повсеместным внедрением svn:externals в свои репозитории. Оценим проблемы, которые приносит svn:externals:

Медленные обновления (svn update)

Если мы используем внешние зависимости, то можно забыть о прежних быстрых обновлениях. Каждая зависимость – это как минимум одно дополнительное HTTP соединение во время обновления. Даже если во внешнем репозитории ничего не поменялось – мы все равно будем ждать. При реальной работе складывается ощущение, что дело не ограничивается только дополнительными соединениями, так как время обновления растет очень ощутимо.

svn:externals – это действительно медленно. По крайней мере, в текущей реализации Subversion.

Код может перестать работать сам собой

Если использовать svn:externals в таком виде:

iText https://itext.svn.sourceforge.net/svnroot/itext/trunk

то никогда нельзя гарантировать, что наш ранее работавший код по-прежнему работает. Любое изменение во внешнем репозитории может сломать его. Код может перестать компилироваться, либо появятся скрытые ошибки, причем без какого-либо нашего участия.

Такой подход вряд ли можно рекомендовать для использования вообще. Даже если externals в пределах одного репозитория. Даже если мы полностью контролируем внешний репозиторий. При таком подходе история репозитория перестает быть неизменной. Откатившись к некоторой ревизии мы получим непредсказуемый результат, так как код, загружаемый через svn:externals, может измениться за время, прошедшее с момента создания ревизии. Очень вероятно, что ранее беспроблемный код попросту не скомпилируется.

Однако не стоит прямо сейчас ставить крест на svn:externals, решение этой проблемы есть – нужно ссылаться на конкретную ревизию во внешнем репозитории, вот так:

iText –r 247 https://itext.svn.sourceforge.net/svnroot/itext/trunk

Теперь мы всегда будем получать конкретную версию кода для данной ревизии. Когда хотим получить обновления – обновляем номер ревизии на подходящий и скачиваем обновления. Проверяем, что наш код компилируется и работает как раньше (прогоняем тесты). Если все нормально – коммитим изменения для svn:externals.

Если сервер, на который мы ссылаемся, недоступен – не сможем обновиться

Если внешний сервер (например, itext.svn.sourceforge.net) недоступен, то мы не сможем обновиться ни на свежую, ни на предыдущие ревизии (если там также используются externals).

Если сервер, на который мы ссылаемся, переехал – надо полностью модифицировать репозиторий

Если мы, конечно, хотим, чтобы все старые ревизии остались работоспособными, в противном случае можно ограничиться только изменениями для текущей ревизии. Но это, конечно, пагубный путь.

В данном случае полностью модифицировать репозиторий не очень сложно, надо:
  1. Сделать дамп репозитория с помощью “svnadmin dump"
  2. В полученном дампе найти все строки подобного вида:
    V 66
    iText –r 247 https://itext.svn.sourceforge.net/svnroot/itext/trunk
    
    PROPS-END
  3. Заменить в них путь к репозиторию и обновить число в первой строке – это количество байт в описании свойства, например:
    V 123
    iText -r 155 http://new_server.com/svnroot/itext/trunk/
    iTextSharp -r 155 http://new_server.com/svnroot/itextsharp/trunk/
    
    PROPS-END
    Здесь я для наглядности добавил вторую строку, чтобы было понятней, как вычисляется число V.
  4. Удалить наш старый репозиторий и накатить новый дамп с помощью “svnadmin load”
Если сервер, на который мы ссылаемся, сменил Subversion на что-то иное или просто перестал работать – все пропало

Оба случая влекут за собой крайне негативные последствия – наш репозиторий станет полностью неработоспособным.

Update to revision может перестать работать

Пример: у нас в репозитории полностью лежит папка iText. Затем, прочитав одну умную статью, мы решаем внедрить вместо нее svn:externals. Успешно внедряем, работает, все счастливы. Однако если теперь мы захотим откатиться с текущей ревизии на какую-нибудь более раннюю – не получится.

Это происходит из-за того, что при откате установки svn:externals целевая папка (iText) не удаляется физически. И когда нужно ее заменить на обычную tracked папку в репозитории – получается конфликт, который текущая версия SVN не умеет разрешать.

В данном случае решение только одно – делать полный чекаут от нулевой ревизии к нужной.

Невозможность корректной миграции на другую систему контроля версий

Если мы решим перенести код из SVN репозитория в репозиторий, например, на Git или Mercurial, то svn:externals – серьезная помеха. Решения тут три: либо не переходить вовсе, либо проигнорировать эту проблему и перенести как получится (в этом случае получатся неработоспособные старые ревизии), либо полностью избавиться от svn:externals руками.

Последний путь тернист, но результат того стоит – мы получим полностью независимый репозиторий, который можем импортировать куда угодно. Как конкретно избавляться от svn:externals — тема для будущей статьи.

Заключение


Использовать svn:externals или нет – каждый решает сам. Мы изначально работали без внешних зависимостей, затем при декомпозиции проектов по нескольким репозиториям начали активно использовать svn:externals, однако со временем поняли, что недостатки перевешивают достоинства. В конце концов, мы вообще решили мигрировать на Mercurial, и для этого пришлось полностью избавиться от svn:externals.

И напоследок несколько советов по использованию svn:externals:
  • Используйте только ссылки на конкретные ревизии во внешнем репозитории;
  • Старайтесь не использовать слишком много внешних зависимостей, иначе обновления будут длиться очень долго;
  • Используйте svn:externals только для одной корневой папки (например, trunk), а не устанавливайте это свойство для нескольких папок, разбросанных по всему репозиторию. Поскольку целевую папку можно задавать с использованием относительных путей (например, Common/Java/iText), то определять все зависимости в одном месте гораздо удобней, чем в нескольких;
  • Если устанавливаете/обновляете svn:externals для папки – не смешивайте эти изменения с изменениями кода, перемещением файлов и прочими действиями. Лучше делать отдельный коммит для одного только изменения свойства svn:externals. Это сильно упростит жизнь, если когда-нибудь вы решите полностью избавится от svn:externals;
  • Старайтесь слишком часто не менять svn:externals, это также упростит жизнь при избавлении от внешних зависимостей.
Tags:
Hubs:
Total votes 24: ↑24 and ↓0+24
Comments27

Articles