本手册用于描述 Subversion 1.4。如果您使用的是更新版本的 Subversion,我们强烈建议您访问 https://svnbooks.subversion.org.cn/ 并查阅适合您 Subversion 版本的书籍。
有时,构建由多个不同检出内容组成的工作副本很有用。例如,您可能希望不同的子目录来自存储库中的不同位置,或者甚至来自完全不同的存储库。当然,您可以通过手动方式设置这样的场景——使用 svn checkout 创建您想要实现的嵌套工作副本结构。但是,如果这种布局对于使用您的存储库的每个人都很重要,那么其他每个用户都需要执行与您相同的检出操作。
幸运的是,Subversion 提供了对 外部定义的支持。外部定义是将本地目录映射到版本化目录的 URL(理想情况下还包括特定修订版)的映射。在 Subversion 中,您使用 svn:externals
属性在组中声明外部定义。您可以使用 svn propset 或 svn propedit 创建或修改此属性(请参阅 名为“操作属性”的部分)。它可以设置在任何版本化的目录上,其值为子目录(相对于设置属性的版本化目录)、可选的修订版标志和完全限定的、绝对的 Subversion 存储库 URL 的多行表。
$ svn propget svn:externals calc third-party/sounds http://sounds.red-bean.com/repos third-party/skins http://skins.red-bean.com/repositories/skinproj third-party/skins/toolkit -r21 http://svn.red-bean.com/repos/skin-maker
svn:externals
属性的便捷之处在于,一旦它被设置在版本化的目录上,所有检出带有该目录的工作副本的人也将受益于外部定义。换句话说,一旦有人努力定义这些嵌套的工作副本检出,其他人就不必再费心——Subversion 将在检出原始工作副本时,也检出外部工作副本。
外部定义的相对目标子目录必须不存在于您或其他用户的系统上——Subversion 将在检出外部工作副本时创建它们。
请注意前面的外部定义示例。当有人检出一个 calc
目录的工作副本时,Subversion 还会继续检出其外部定义中找到的项目。
$ svn checkout http://svn.example.com/repos/calc A calc A calc/Makefile A calc/integer.c A calc/button.c Checked out revision 148. Fetching external item into calc/third-party/sounds A calc/third-party/sounds/ding.ogg A calc/third-party/sounds/dong.ogg A calc/third-party/sounds/clang.ogg … A calc/third-party/sounds/bang.ogg A calc/third-party/sounds/twang.ogg Checked out revision 14. Fetching external item into calc/third-party/skins …
如果您需要更改外部定义,可以使用常规的属性修改子命令来执行。当您提交对 svn:externals
属性的更改时,Subversion 将在您下次运行 svn update 时将检出的项目与更改后的外部定义同步。当其他人更新他们的工作副本并收到您对外部定义的更改时,也会发生相同的情况。
由于 svn:externals
属性具有多行值,我们强烈建议您使用 svn propedit 而不是 svn propset.
您应该认真考虑在所有外部定义中使用显式的修订版号。这样做意味着您可以决定何时拉取不同的外部信息快照,以及拉取哪个确切的快照。除了避免意外获取可能无法控制的第三方存储库的更改之外,使用显式的修订版号还意味着,当您将工作副本回溯到之前的修订版时,您的外部定义也会还原到之前修订版时的样子,这反过来意味着外部工作副本将更新为与它们在您的存储库处于该之前修订版时所处状态匹配。对于软件项目来说,这可能是成功构建和失败构建复杂代码库的旧快照之间的区别。
svn status 命令也识别外部定义,对外部检出的不相交子目录显示状态代码 X
,然后递归进入这些子目录以显示外部项目的自身状态。
Subversion 中对外部定义的支持并不理想。首先,外部定义只能指向目录,不能指向文件。其次,外部定义不能指向相对路径(例如 ../../skins/myskin
这样的路径)。第三,通过外部定义支持创建的工作副本仍然与主工作副本断开连接(svn:externals
属性实际设置在它的版本化目录上)。Subversion 仍然只真正操作非不相交的工作副本。因此,例如,如果您想提交对一个或多个外部工作副本中所做的更改,您必须在这些工作副本上明确运行 svn commit——在主工作副本上提交不会递归到任何外部工作副本。
此外,由于定义本身使用绝对 URL,因此移动或复制附加了它们的目录不会影响检出为外部的内容(尽管相对本地目标子目录当然会随着重命名目录一起移动)。这在某些情况下会令人困惑,甚至让人沮丧。例如,假设您有一个名为 my-project
的顶级目录,并且您在其子目录之一 (my-project/some-dir
) 上创建了一个外部定义,该定义跟踪其另一个子目录 (my-project/external-dir
) 的最新修订版。
$ svn checkout http://svn.example.com/projects . A my-project A my-project/some-dir A my-project/external-dir … Fetching external item into 'my-project/some-dir/subdir' Checked out external at revision 11. Checked out revision 11. $ svn propget svn:externals my-project/some-dir subdir http://svn.example.com/projects/my-project/external-dir $
现在,您使用 svn move 重命名 my-project
目录。此时,您的外部定义仍然会引用 my-project
目录下的路径,即使该目录不再存在。
$ svn move -q my-project renamed-project $ svn commit -m "Rename my-project to renamed-project." Deleting my-project Adding my-renamed-project Committed revision 12. $ svn update Fetching external item into 'renamed-project/some-dir/subdir' svn: Target path does not exist $
此外,外部定义使用的绝对 URL 可能会导致通过多个 URL 方案可用的存储库出现问题。例如,如果您的 Subversion 服务器配置为允许每个人通过 http://
或 https://
检出存储库,但只允许通过 https://
提交,那么您就遇到了一个有趣的问题。如果您的外部定义使用存储库 URL 的 http://
形式,您将无法从这些外部创建的工作副本提交任何内容。另一方面,如果它们使用 URL 的 https://
形式,任何可能通过 http://
检出的人(因为他们的客户端不支持 https://
)将无法获取外部项目。还要注意,如果您需要重新设置工作副本的父级(使用 svn switch --relocate),外部定义不会也被重新设置父级。
最后,可能有些时候您希望 svn 子命令不识别或不操作外部工作副本。在这些情况下,您可以将 --ignore-externals
选项传递给子命令。