本文本尚在编写中,内容可能会发生重大变化,可能无法准确描述 Apache™ Subversion® 软件的任何已发布版本。将本页面添加书签或推荐给其他人可能不是明智之举。请访问 https://svnbook.subversion.org.cn/ 获取本书的稳定版本。

外部定义

有时,构建一个由多个不同检出组成的工作副本很有用。例如,您可能希望不同的子目录来自存储库中的不同位置,或者甚至来自完全不同的存储库。当然,您可以通过手动操作来设置这样的场景,使用 svn checkout 来创建您想要实现的嵌套工作副本结构。但是,如果这种布局对于所有使用您的存储库的人来说都很重要,那么其他用户都需要执行与您相同的检出操作。

幸运的是,Subversion 提供了对 外部定义 的支持。外部定义将本地目录映射到版本化目录的 URL(理想情况下,还包括特定版本)。在 Subversion 中,您使用 svn:externals 属性以组的方式声明外部定义。您可以使用 svn propsetsvn propedit 创建或修改此属性(参见 名为“操作属性”的部分)。它可以设置在任何版本化目录上,其值描述了外部存储库位置以及应该检出该位置的客户端目录。

svn:externals 属性的便利之处在于,一旦它被设置在一个版本化目录上,所有检出包含该目录的工作副本的人都会享受到外部定义的益处。换句话说,一旦一个人完成了定义嵌套工作副本结构的努力,其他人就不需要再费心了——Subversion 将在检出原始工作副本之后,自动检出外部工作副本。

[Warning] 警告

外部定义的相对目标子目录 不能 已经存在于您的系统或其他用户的系统上——Subversion 将在检出外部工作副本时创建它们。

您还将在外部定义设计中获得 Subversion 属性的所有常规优势。这些定义是版本化的。如果您需要更改外部定义,可以使用常规的属性修改子命令来执行此操作。当您提交对 svn:externals 属性的更改时,Subversion 将在您下次运行 svn update 时,将检出的项目与更改后的外部定义同步。当其他人更新他们的工作副本并接收您对外部定义的更改时,也会发生同样的事情。

[Tip] 提示

由于 svn:externals 属性具有多行值,我们强烈建议您使用 svn propedit 而不是 svn propset

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
$

或者,使用挂钩版本语法(我们在 名为“挂钩版本和操作版本”的部分 中详细描述),它可能显示为

$ 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
$
[Tip] 提示

您应该认真考虑在所有外部定义中使用显式版本号。这样做意味着您可以决定何时提取外部信息的另一个快照,以及要提取哪个快照。除了避免意外获取您可能无法控制的第三方存储库的更改之外,使用显式版本号还意味着,当您将工作副本回溯到以前的版本时,您的外部定义也将还原到以前版本的样子,这反过来意味着外部工作副本将更新为与 它们 在您的存储库处于该以前版本时的样子相匹配。对于软件项目来说,这可能是成功构建和失败构建复杂代码库的旧快照之间的区别。

对于大多数存储库来说,这三种格式化外部定义的方式最终具有相同的效果。它们都带来了相同的益处。不幸的是,它们也带来了相同的麻烦。由于显示的定义使用了绝对 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 表示它们。

[Warning] 警告

虽然目录外部可以将外部目录放置在任何深度,并且任何缺失的中间目录都会被创建,但文件外部必须放置到已经检出的工作副本中。

使用 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
Relative URL: ^/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 格式如何改进它。但在使用新格式时要小心,不要无意中引入新的问题。例如,虽然最新的客户端将继续识别和支持原始外部引用定义格式,但 1.5 之前的客户端不能正确解析新格式。如果您将所有外部引用定义更改为新格式,您实际上迫使所有使用这些外部引用的人将他们的 Subversion 客户端升级到能够解析它们的版本。此外,请务必小心,不要天真地重新定位定义中的-rNNN 部分——旧格式使用该修订版作为基准修订版,但新格式使用它作为有效修订版(基准修订版为HEAD,除非另有指定;有关区别的完整解释,请参阅名为“Peg and Operative Revisions”的部分)。

[Warning] 警告

外部工作副本仍然是完全独立的工作副本。您可以像对待任何其他工作副本一样直接对其进行操作。这可能是一个方便的功能,允许您独立于任何svn:externals 属性导致其实例化的主工作副本来检查外部工作副本。但请注意,不要无意中以微妙的方式修改您的外部工作副本,从而导致问题。例如,虽然外部引用定义可能会指定外部工作副本应该保持在特定的修订版号,但如果您在外部工作副本上直接运行svn update,Subversion 会照办,现在您的外部工作副本与主工作副本中的声明不一致了。使用svn switch 直接将外部工作副本(或其部分)切换到另一个 URL 可能会导致类似的问题,如果主工作副本的内容正在期望外部内容中的特定内容。

除了svn checkoutsvn updatesvn switchsvn export 命令实际上管理外部引用被签出的不相交(或断开连接的)子目录外,svn status 命令也识别外部引用定义。它显示不相交外部子目录的X 状态代码,然后递归进入这些子目录以显示外部项本身的状态。您可以将--ignore-externals 选项传递给任何这些子命令以禁用外部引用定义处理。

TortoiseSVN 官方中文版 1.14.7 发布