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

    Несмотря на то, что распределенные системы контроля версий (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, это также упростит жизнь при избавлении от внешних зависимостей.
    Поделиться публикацией

    Комментарии 27

      +8
      Сейчас кто-нибудь напишет «используйте GIT!» :)
      Кстати, существует ли в git аналог svn:externals? Как решается проблема подключения общих библиотек?
    • НЛО прилетело и опубликовало эту надпись здесь
        –2
        Разумеется, мы можем работать над всеми проектами/модулями в пределах одного репозитория, но такой подход не лишен недостатков. Например, все разработчики могут загружать к себе в рабочие копии много ненужных изменений. Не получится ограничить доступ разработчикам к тем проектам, к которым у них доступа быть не должно.

        /path/to/rep/conf/authz
        и настройте для каждого пути свой список доступа.

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

        svn switch --relocate
          0
          /path/to/rep/conf/authz
          и настройте для каждого пути свой список доступа.


          а как это сделать на одном из онлайновых svn-репозиториев? очевидно, никак. к тому же у одного репозитория на все есть другие недостатки, помимо прав доступа.

          svn switch --relocate

          речь шла о том, что в истории останется невалидная ссылка на внешний ресурс. то есть совсем о другом речь шла.
            0
            я про другие недостатки ничего и не писал.
            но процитированное недостатком не является.
            судя по тому, что дальше рекомендуют использовать svnadmin dump и подобное, речь шла не об онлайновых репозиториях :)

            речь шла о том, что в истории останется невалидная ссылка на внешний ресурс. то есть совсем о другом речь шла.

            это для миграции рабочей копии, чтобы checkout заново не делать.
            для изменения свойства в предыдущих ревизиях есть svnadmin setrevprop, та черная магия с переписыванием дампа вручную все равно не нужна.
              0
              Здравствуйте!

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

              При предложенном мной подходе все изменения делаются по ходу просмотра файла дампа. Количество байт не нужно считать глазами, текстовый редактор с этим отлично справляется либо можно просто отнять/прибавить разницу в длине между старым и новым адресом.
          0
          Хм, интересно, как себя поведет svn, если задать все svn:externals для trunk, а потом сделать branch (под рукой нет, на чем проверить).

          Ссылки на конкретные ревизии во внешнем репозитории — очень хорошо, о проблеме задумывался, но не знал, что так можно делать.

          Немного печально, что svn как-то не очень развивавется по моим субъективным впечатлениям (может это просто проходит мимо меня? И почему тогда все массово переходят на альтернативные системы контроля версий?). Недостатки то реально есть, а переходить на что-то другое не вижу смысла, т. к. слишком много кода, используемого мной хранится именно в SVN репозиториях.

          Спасибо за статью, познавательно.
            0
            В branch получится пустая папка со свойствами svn:externals. По опыту могу сказать, что внешние включения надо использовать в меру и явно осознавая, что за этим последует.
              0
              В версии 1.6.17 (на которой находится мой проект) происходит так: создаешь бранч, делаешь апдейт бранча — и в рабочей копии появляются директории и файлы, подгруженные из svn:externals. То есть, все ок вполне — в рабочей копии branch получается тот же контент, что и в trunk (со всеми externals либами).

              Но что в trunk, что в branch не видно эти включения через svn list или repo browser. Чтобы не теряться, в статье дан отличный совет — использовать свойства только для корневой папки.
            0
            Вообще-то Mercurial умеет такую фичу, как subrepository — более-менее примерный аналог svn:externals. Более того, поддерживаются и репозитории Subversion.
              0
              Субрепозиторий — это полностью репозиторий со всеми плюшками, а вот достать из меркуриала пределенную директорию, не таща за собой весь репозиторий, довольно проблематично(в силу организации самого репозитория в меркуриал). В этом свн выигрывает.
              Внешние репозитории поддерживаются, но надо было плясать с бубном и ставить дополнительные модули. Когда решал данный вопрос, наткнулся на модуль(кажется форест), но завести его нормально не удалось. Кстати, сейчас свн поддерживается из коробки или все еще через плагины?
              Впрочем проблемы в равной степени возникают и при попытке интегрировать между собой что-то в обратную сторону, т.е. забирать чт-то из репозитория меркуриал в свн.
                0
                достать из меркуриала пределенную директорию, не таща за собой весь репозиторий, довольно проблематично
                да. но в случае с исходниками это не играет существенной роли. в то же время, возможность вытащить не только исходники, но и всё остальное, включая, скажем, тесты, довольно полезна. например, при допиливании либы.
                Внешние репозитории поддерживаются, но
                нет, это фича искаропки. по ссылке разве не написано?
                сейчас свн поддерживается из коробки или все еще через плагины
                subrepository — искаропки. остальное — плагином.
                  0
                  Возможно я неясно выразился, вопрс касался поддержки репозиториев других систем контроля версий. Про то, что субрепозитории поддерживаются из коробки я знал и так(а это значит что и внешние репозитории меркуриал).
                  По поводу выташить, я предпочитаю сам контролировать, что, куда и в каком объеме тащить :).
                    0
                    а. ну да, плагином. странно было бы ожидать обратного :) как и git.
                    0
                    Вот сейчас бьюсь над одной задачей в mercurial, которую довольно просто решал в subversion через externals. Есть внешний проект в репозитории, для которого я разрабатываю модуль и удаляю существующие в нём модули, плюс конфиги. В svn я помещал корень проекта в свой репозиторий и из него ссылался на подкаталоги внешнего проекта: мой модуль и мои конфиги работали с моим репозиторием, а по необходимости обновлял внешние каталоги. Как разрулить эту ситуацию в mercurial пока не представляю.
                0
                Мы использовали externals для интеграции модулей, разработанных сторонними разработчиками. В принципе получилось неплохо — у разработчиков были отдельные директории в общем репозитории. Те каталоги в основном дереве проекта, в которых должны были находиться эти модули были привязаны через externals к тем местам, куда разработчики выкладывали свои сборки (сборка их модулей не включалась в основную сборку). Кроме того, был еще один проект — тестовая система, позволяющая тестировать эти модули по отдельности. Она тоже была привязана через externals к модулям.

                Правда, у externals есть еще один недостаток — не видно, какие папки используют externals, а какие — нет. Корме того, практика показала, что люди, не очень хорошо разбирающиеся в SVN, вообще не понимают, что такое externals, что иногда затрудняет работу.
                  0
                  iText –r 247 itext.svn.sourceforge.net/svnroot/itext/trunk


                  Ничего не слышали про теги?

                  Вот посмотрите: itext.svn.sourceforge.net/svnroot/itext/tags/
                    0
                    Кстати, я аналогично предпочитаю ссылаться на тэги.
                      0
                      Согласен, теги — отличная альтернатива конкретным ревизиям, если, конечно, они правильно используются и используются вообще во внешнем репозитории. Не вспомнил об этом при написании статьи.
                      0
                      >Мы изначально работали без внешних зависимостей, затем при декомпозиции проектов по нескольким репозиториям начали активно использовать svn:externals, однако со временем поняли, что недостатки перевешивают достоинства.

                      А каковы, с Вашей точки зрения, преимущества такой декомпозиции?

                      Если все хранить в одном репозитории в виде

                      -проект1
                      --trunk
                      --tags
                      --branches
                      -проект2
                      --trunk
                      --tags
                      --branches
                      -проект3
                      --trunk
                      --tags
                      --branches

                      и т.д., то преимущества таковы:
                      -Простота администрирования. Новый проект — это просто новая папка. Доступ пользователей описан в одном файле. Полный backup делается одним махом.
                      -Не нужно делать «externals». Можно попросту копировать в проект1 и проект3 нужные подпапки из нужных tags проекта2. Это аналог «нужно ссылаться на конкретную ревизию во внешнем репозитории»

                      Смысл разделять на несколько репозиториев?
                        +1
                        Преимущества декомпозиции:

                        — Если хранить все в одном репозитории, то получаются неудобства с нумерацией версий конкретных проектов (если завязывать нумерацию версий на номер ревизии). Например:
                        Работаем только над «проект1», сделали 300 коммитов, выпустили версию «проект1» 1.0.300
                        Затем переключаемся на «проект2», после 400 коммитов выпускаем версию «проект2» 1.0.600 (уже не совсем верно)
                        Возвращаемся к «проект1», делаем 1 коммит (небольшой фикс) и выпускаем версию — получается номер 1.0.601 (совсем некорректно)

                        — На начальном этапе это не заметно, но со временем размер репозитория сильно увеличивается. В истории накапливается куча ненужного. Например, представим ситуацию, когда какой-то проект больше ну совершенно не нужен — можно его удалить. Простое удаление из репозитория не уменьшит общий размер, ибо вся история останется.
                        Размер репозитория до поры-до времени будет не критичен, однако когда он вырастает до нескольких Гб — готовьтесь, что как только потребуется осуществлять какие-то действия с самим репозиторием — перенос на новый сервер, какую-то обработку дампа, миграцию в другую VCS — столкнетесь с проблемами.

                        — Декомпозиция дает большую гибкость в управлении репозиториями
                        Частичное повторение предыдущего пункта. Если захотите что-то сделать с репозиторием — выкинуть какую-то ненужную часть истории, сделать бэкап только одного проекта, перейти на Git — с декомпозированными репозиториями будет проще.

                        К слову, при декомпозиции никто не мешает также использовать копирование вместо externals, так что Ваше второе преимущество не совсем преимущество.

                        /////

                        Вообще, как показывает практика, и в других областях декомпозиция в большинстве случаев лучше. Ибо, обычно, делает вещи проще, например:
                        — Вся эволюция императивных языков программирования направлена на упрощение разработки и, соответственно, кода. И декомпозиция — главный инструмент для этого. Сейчас мы пришли к тому, что грамотно спроектированный проект:
                        1) Разделен на модули/компоненты, каждый из которых решает конкретную задачу;
                        2) Каждый компонент разделен на классы, каждый из которых в соответствии с хорошими практиками ООП решает 1 задачу;
                        3) Каждый класс разделен на методы, каждый из которых в идеале решает 1 задачу;
                        4) В каждом методе используются переменные и, в идеале, каждая переменная должна использоваться только для одной цели.

                        — Управление требованиями к проекту. Как проще — когда все требования по проекту в куче в одном документе или же аккуратно разнесены по разным задачам в трекере? Практика показывает, что второе.
                          0
                          >Если завязывать нумерацию версий на номер ревизии

                          Если завязывать, тогда да. Но, к примеру, у нас не завязано. Вы пишете: «перейти на Git — с декомпозированными репозиториями будет проще». А в git нет номера ревизии…

                          >На начальном этапе это не заметно, но со временем размер репозитория сильно увеличивается.

                          Ну, если проекты занимают десятки гигабайт, тогда, конечно, этот фактор имеет значение. Но не является ли такой размер признаком хранения в VCS того, чего там хранить не надо? :)

                          >миграцию в другую VCS — столкнетесь с проблемами

                          Хотелось бы конкретики. Если мигрировать на что-то типа mercurial/git, то мигрировать будет не весь репозиторий, а только какой-то проект или даже его часть.

                          >К слову, при декомпозиции никто не мешает также использовать копирование вместо externals

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

                          >Вообще, как показывает практика, и в других областях декомпозиция в большинстве случаев лучше.

                          С этим согласен. Однако тут мы имеем дело с декомпозицией двух видов — так сказать, логической и физической. Логически проекты и так можно хорошо декомпозировать, даже если они в одном репозитории. Вынос проекта в другой репозиторий с точки зрения заказчика/аналитика/проектировщика/разработчика ничего нового не привносит, просто меняется url проекта.
                            0
                            Не мог пройти мимо посылки, что ООП средство декомпозиции. Наоборот, ООП средство композиции, собирающее все связанные данные и методы/сигналы работы с ними под одну «крышу», усиливающее, а не ослабляющее внутренние связи. Хорошие практики ООП заключаются в том, чтобы вынести то, что можно вынести без нарушения внутренней целостности, но куда главнее не забыть внести то, что нужно внести, без введения излишних внешних связей. А если следовать вашим рассуждением, то идеалом проекта будет его реализация на процедурных языках, а то и на простейшем «ассемблере», поддерживающем из структур управления лишь условный переход.

                            Напротив, эволюция языков императивного программирования основана на всё более многоуровневой композиции (составлении) новых элементов из существовавших до этого (как правило, со скрытием и недоступностью для разработчика деталей реализации нового элемента). Сначала были метки и условные переходы, а потом появились циклы. Сначала были процедуры/функции, а потом появились объекты и методы.

                            Необходимость декомпозиции появляется когда мы вольно или невольно, случайно или закономерно пришли к излишней композиции (слишком много слабосвязанных методов/переменных в классе, слишком много слабосвязанных каталогов/файлов в проекте). Равно как и необходимость коспозиции появляется в случае слишком слабой «физической» связи элементов, явно «логически» связанных.

                            У оптыного разработчика, имхо, композиция появляется намного чаще декомпозиции при разработке снизу вверх, а декомпозиция чаще композиции при разработке сверху вниз. Идёт разработка релиза на ЯП или формирование структуры каталогов/репозиториев — не важно.

                            Если разработчик достаточно опытен
                              0
                              Ваш посыл (ООП — средство композиции) не менее спорен. :-)
                              С моей точки зрения, программирование (как процесс создания программы) — это композиция. А разбиение задачи на составные части (максимально независимые друг от друга) и реализация объектов для решения мелких задач — это декомпозиция.

                              То есть создание класса, как по мне, это декомпозиция, так как часть функционала и данных выделяется из общего пространства имен или общих данных в отдельную сущность.
                          0
                          для удобного испольхования чужого кода мы сделали две ветки trunk и upstream. заливаем в upstream копию из чужого репозитория, комитим, копируем в trunk, в trunk делаем наши правки, иногда обновляем upstream и делаем слияние в trunk
                            +1
                            Если внешний репозиторий тоже под SVN, то самым приятным вариантом мне кажется хранение копии исходников и периодический честный мерж из внешнего репозитория. Я, например, в самом репозитории храню файл merge.sh такого вида:

                            REV1=4351
                            REV2=4375
                            EXT_REPO_PATH=svn://...
                            DIR=ext_lib_dir
                            USER=username
                            mv merge.log merge.log.bak
                            svn merge -r $REV1:$REV2 $EXT_REPO_PATH DIR --username $USER >merge.log


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

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

                            Самое читаемое