本文档旨在描述 Apache™ Subversion® 的 1.7.x 系列。如果您运行的是其他版本的 Subversion,强烈建议您访问 https://svnbook.subversion.org.cn/ 并查阅适合您 Subversion 版本的文档。
有时,构建由多个不同检出组成的工作副本很有用。例如,您可能希望不同的子目录来自存储库中的不同位置,或者来自完全不同的存储库。您当然可以手动设置这样的场景——使用 svn checkout 来创建您试图实现的嵌套工作副本结构。但是,如果此布局对所有使用您的存储库的人都很重要,那么其他每个用户都需要执行与您相同的检出操作。
幸运的是,Subversion 提供了对 外部定义 的支持。外部定义是本地目录到版本化目录的 URL(理想情况下是特定修订版)的映射。在 Subversion 中,您使用 svn:externals
属性以组的形式声明外部定义。您可以使用 svn propset 或 svn propedit 创建或修改此属性(请参阅 名为“操作属性”的部分)。它可以设置在任何版本化目录上,其值描述了外部存储库位置以及应检出该位置的客户端目录。
svn:externals
属性的便利之处在于,一旦它被设置在版本化目录上,所有检出包含该目录的工作副本的人也将受益于外部定义。换句话说,一旦一个人努力定义嵌套的工作副本结构,其他人就不必再费心——Subversion 将在检出原始工作副本后,自动检出外部工作副本。
警告 | |
---|---|
外部定义的相对目标子目录不能已经存在于您的系统或其他用户的系统中 - Subversion 将在检出外部工作副本时创建它们。 |
您还可以在外部定义设计中获得 Subversion 属性的所有常规好处。定义是版本化的。如果您需要更改外部定义,可以使用常规的属性修改子命令来执行此操作。当您提交对svn:externals
属性的更改时,Subversion 将在您下次运行svn update
时,根据更改的外部定义同步检出的项目。当其他人更新他们的工作副本并接收您对外部定义的更改时,也会发生同样的事情。
提示 | |
---|---|
由于 |
1.5 之前的 Subversion 版本支持一种外部定义格式,该格式是子目录(相对于设置属性的版本化目录)、可选修订标志和完全限定的绝对 Subversion 存储库 URL 的多行表格。以下是一个示例
$ svn propget svn:externals calc # Resources versioned elsewhere. third-party/sounds http://svn.example.com/repos/sounds third-party/skins -r148 http://svn.example.com/skinproj third-party/skins/toolkit -r21 http://svn.example.com/skin-maker $
在前面的示例中,定义了三个外部。第一个是“未固定” - 它实际上引用了其目标的 HEAD 修订版。另外两个声明了特定的修订版。还演示了以哈希 (#
) 开头的行被视为注释,并被外部定义解析逻辑忽略。
当有人检出前面示例中提到的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 …
但是,从 Subversion 1.5 开始,支持svn:externals
属性的新格式。外部定义仍然是多行的,但各种信息的部分的顺序和格式已更改。新语法更类似于您可能传递给svn checkout的参数顺序:可选修订标志首先出现,然后是外部 Subversion 存储库 URL,最后是相对本地子目录。但是请注意,这次我们没有说“完全限定的绝对 Subversion 存储库 URL。” 这是因为新格式支持相对 URL 和带有挂钩修订版的 URL。前面外部定义的示例可能在 Subversion 1.5 中如下所示
$ svn propget svn:externals calc # Resources versioned elsewhere. http://svn.example.com/repos/sounds third-party/sounds -r148 http://svn.example.com/skinproj third-party/skins -r21 http://svn.example.com/skin-maker third-party/skins/toolkit $
或者,利用 peg 修订语法(我们在名为“Peg 和操作修订”的部分中详细描述),它可能显示为
$ svn propget svn:externals calc # Resources versioned elsewhere. http://svn.example.com/repos/sounds third-party/sounds http://svn.example.com/skinproj@148 third-party/skins http://svn.example.com/skin-maker@21 third-party/skins/toolkit $
提示 | |
---|---|
您应该认真考虑在所有外部定义中使用显式修订号。这样做意味着您可以决定何时拉取外部信息的另一个快照,以及拉取哪个快照。除了避免意外获取您可能无法控制的第三方存储库的更改之外,使用显式修订号还意味着,当您将工作副本回溯到以前的修订版时,您的外部定义也将恢复到它们在该先前修订版中的外观,这反过来意味着外部工作副本将更新以匹配它们在您的存储库处于该先前修订版时的外观。对于软件项目,这可能是成功构建和失败构建复杂代码库的旧快照之间的区别。 |
对于大多数存储库,这三种格式化外部定义的方式具有相同的最终效果。它们都带来了相同的好处。不幸的是,它们也带来了相同的烦恼。由于显示的定义使用绝对 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 renamed-project Committed revision 12. $ svn update Updating '.': svn: warning: W200000: Error handling externals definition for 'renamed-projec t/some-dir/subdir': svn: warning: W170000: URL 'http://svn.example.com/projects/my-project/externa l-dir' at revision 12 doesn't exist At revision 12. svn: E205011: Failure occurred processing one or more externals definitions $
此外,绝对 URL 会导致可通过多个 URL 方案访问的存储库出现问题。例如,如果您的 Subversion 服务器配置为允许每个人通过http://
或https://
检出存储库,但只允许提交通过https://
进入,那么您就遇到了一个有趣的问题。如果您的外部定义使用存储库 URL 的http://
形式,您将无法从这些外部创建的工作副本中提交任何内容。另一方面,如果它们使用 URL 的https://
形式,那么任何可能通过http://
检出的人(因为他的客户端不支持https://
)将无法获取外部项目。还要注意,如果您需要重新定位您的工作副本(使用svn relocate),外部定义不会也被重新定位。
Subversion 1.5 在缓解这些挫折方面迈出了巨大的一步。如前所述,新外部定义格式中使用的 URL 可以是相对的,Subversion 提供了用于指定多种 URL 相对性的语法魔法。
../
相对于设置 svn:externals
属性的目录的 URL
^/
相对于版本化 svn:externals
属性的存储库的根目录
//
相对于设置 svn:externals
属性的目录的 URL 方案
/
相对于版本化 svn:externals
属性的服务器的根 URL
^/../REPO-NAME
相对于与定义 svn:externals
的存储库位于相同 SVNParentPath
位置下的同级存储库。
因此,第四次查看我们之前的外部定义示例,并以各种方式使用新的绝对 URL 语法,我们现在可能会看到
$ svn propget svn:externals calc # Resources versioned elsewhere. ^/sounds third-party/sounds /skinproj@148 third-party/skins //svn.example.com/skin-maker@21 third-party/skins/toolkit $
Subversion 1.6 为外部定义带来了两个改进。首先,它在语法中添加了引用和转义机制,以便外部工作副本的路径可以包含空格。当然,这在以前是有问题的,因为空格用于分隔外部定义中的字段。现在,您只需要将此类路径规范括在双引号 ("
) 字符中,或者使用反斜杠 (\
) 字符转义路径中的问题字符。当然,如果外部定义的 URL 部分包含空格,则应使用标准 URI 编码机制来表示这些空格。
$ svn propget svn:externals paint http://svn.thirdparty.com/repos/My%20Project "My Project" http://svn.thirdparty.com/repos/%22Quotes%20Too%22 \"Quotes\ Too\" $
Subversion 1.6 还引入了对文件外部定义的支持。 文件外部 的配置方式与目录外部相同,并在工作副本中显示为一个版本化的文件。
例如,假设您的存储库中有一个文件 /trunk/bikeshed/blue.html
,并且您希望该文件(如修订版 40 中所示)出现在您的 /trunk/www/
工作副本中,名为 green.html
。
实现此目标所需的外部定义现在应该很熟悉了
$ svn propget svn:externals www/ ^/trunk/bikeshed/blue.html@40 green.html $ svn update Updating '.': Fetching external item into 'www' E www/green.html Updated external to revision 40. Update to revision 103. $ svn status X www/green.html $
如您在之前的输出中看到的,Subversion 在将文件外部获取到工作副本时用字母 E
表示,在显示工作副本状态时用字母 X
表示。
警告 | |
---|---|
虽然目录外部可以将外部目录放置在任何深度,并且任何缺少的中间目录都将被创建,但文件外部必须放置在已签出的工作副本中。 |
使用 svn info 检查文件外部时,您可以看到外部的来源 URL 和修订版
$ svn info www/green.html Path: www/green.html Name: green.html Working Copy Root Path: /home/harry/projects/my-project URL: http://svn.example.com/projects/my-project/trunk/bikeshed/blue.html Repository Root: http://svn.example.com/projects/my-project Repository UUID: b2a368dc-7564-11de-bb2b-113435390e17 Revision: 40 Node kind: file Schedule: normal Last Changed Author: harry Last Changed Rev: 40 Last Changed Date: 2009-07-20 20:38:20 +0100 (Mon, 20 Jul 2009) Text Last Updated: 2009-07-20 23:22:36 +0100 (Mon, 20 Jul 2009) Checksum: 01a58b04617b92492d99662c3837b33b $
由于文件外部在工作副本中显示为版本控制文件,因此如果它们引用 HEAD 修订版的某个文件,则可以修改甚至提交它们。提交的更改将出现在外部以及外部引用的文件中。但是,在我们的示例中,我们将外部固定到较旧的修订版,因此尝试提交外部会失败。
$ svn status M X www/green.html $ svn commit -m "change the color" www/green.html Sending www/green.html svn: E155011: Commit failed (details follow): svn: E155011: File '/trunk/bikeshed/blue.html' is out of date $
在定义文件外部时请牢记这一点。如果您需要外部引用文件的特定修订版,则将无法修改外部。如果您想修改外部,则不能指定除 HEAD
修订版以外的任何修订版,如果没有指定修订版,则隐含该修订版。
不幸的是,Subversion 中对外部定义的支持仍然不尽如人意。文件外部和目录外部都存在缺陷。对于任何类型的外部,定义的本地子目录部分都不能包含 ..
父目录指示符(例如 ../../skins/myskin
)。文件外部不能引用来自其他存储库的文件。文件外部的 URL 必须始终与文件外部将插入到的 URL 位于同一个存储库中。此外,文件外部不能移动或删除。必须修改 svn:externals
属性。但是,可以复制文件外部。
也许最令人失望的是,通过外部定义支持创建的工作副本仍然与主工作副本(在其实际设置了 svn:externals
属性的版本控制目录上)断开连接。而且 Subversion 仍然只真正操作非不相交的工作副本。因此,例如,如果您想提交对一个或多个外部工作副本所做的更改,则必须在这些工作副本上显式运行 svn commit——在主工作副本上提交不会递归到任何外部工作副本中。
我们已经提到了旧的 svn:externals
格式的一些额外缺点,以及新版 Subversion 1.5 格式是如何改进的。但是,在使用新格式时要小心,不要无意中引入新的问题。例如,虽然最新的客户端会继续识别并支持原始的 externals 定义格式,但 1.5 之前的客户端 无法 正确解析新格式。如果你将所有 externals 定义更改为新格式,你实际上强制所有使用这些 externals 的用户将他们的 Subversion 客户端升级到能够解析它们的版本。此外,要小心避免天真地重新定位定义中的 -r
部分——旧格式使用该修订版本作为挂钩修订版本,而新格式使用它作为操作修订版本(除非另有指定,否则挂钩修订版本为 NNN
HEAD
;有关区别的完整解释,请参阅 名为“挂钩修订版本和操作修订版本”的部分)。
警告 | |
---|---|
外部工作副本仍然是完全自包含的工作副本。你可以像操作任何其他工作副本一样直接操作它们。这可能是一个方便的功能,允许你独立于任何导致其实例化的主工作副本的 |
除了实际管理外部检出的分离(或断开连接)子目录的svn checkout、svn update、svn switch和svn export命令外,svn status命令也识别外部定义。它为分离的外部子目录显示状态代码X
,然后递归进入这些子目录以显示外部项目本身的状态。您可以将--ignore-externals
选项传递给这些子命令中的任何一个,以禁用外部定义处理。