This text is a work in progress—highly subject to change—and may not accurately describe any released version of the Apache™ Subversion® software. Bookmarking or otherwise referring others to this page is probably not such a smart idea. Please visit http://www.svnbook.com/ for stable versions of this book.

限定版本号与实施版本号

我们经常在自己的系统中对文件和目录进行复制, 移动, 重命名和替换, 但是版本控制系统不能按照相同的方式操作它所管理的文件与目录. Subversion 的文件管理非常灵活, 几乎和操作未版本化的文化一样灵活. 但是这种灵活性也意味着在仓库的生命周期中, 一个版本 控制对象可能会有多个路径, 而一个路径在不同时间可能表示完全不同的版本控制 对象, 当用户同这些路径与对象交互时会产生一定的复杂度.

如果对象的版本历史里包含了 "地址上的变化", Subversion 自己就会注意 到这点. 比如说用户要求查看上周被重命名的一个文件的版本历史, Subversion 会提供全部的相关日志—重命名发生时的版本号, 再加上重命名前与重命名后 的相关版本号. 所以说在大部分情况下, 用户都不需要考虑对象的地址变化可能 带来的影响, 但是在少数情况下, Subversion 需要你的帮助来消除歧义.

最简单的一种场景是一个文件或目录从仓库中被删除后, 又有一个同名的 文件或目录被添加到仓库中, 被删除的对象和新增的对象之间毫无关系, 只是 碰巧路径相同, 假设都是 /trunk/object, 那么向 Subversion 询问 /trunk/object 的历史是表示什么 意思? 是在问当前对象的历史, 还是那个被删除的对象的历史? 或者是在问 该路径上存在过的 所有 对象的操作历史? 为了得到 自己想要的信息, Subversion 需要一些提示.

由于移动操作, 对象的历史变得更加复杂. 比如说你有一个目录叫作 concept, 它包含了几个初期的软件项目. 慢慢地, 软件开始成型, 你开始考虑为项目取一个名字[10]. 假设你要取的软件名字是 Frabnaggilywort, 把目录重命名成 软件的名字是很合理的操作, 于是 concept 被重命名 为 frabnaggilywort. 项目接着进行, Frabnaggilywort 发布了 1.0 版, 很多用户都下载了并在日常工作中使用它.

故事听起来还不错, 但是还没结束. 企业家的脑子里经常会有新想法出现, 于是你又创建了一个新目录 concept, 循环再次开始. 实际上, 在几年内循环会重复进行多次, 每一次都以创建 concept 开始, 如果想法逐渐地明朗起来, concept 很可能会被重新命名; 如果想法被否定了, concept 就会被删除. 更有甚者, 用户还有可能把 concept 改名一段时间后, 又改回到 concept.

在这种场景下, 指挥 Subversion 操作这些重复使用的路径就好像在指挥 一个摩托车手, 从芝加哥的 West Suburbs 向东行驶到 Roosevelt Road, 再向 左驶入主街. 在短短的 20 分钟里, 你会穿过 Wheaton, Glen Ellyn 和 Lombard 的 主街, 但它们并非是同一个地方, 我们的 摩托车手—也就是 Subversion—需要更多的细节才能把事情做对.

幸运的是, Subversion 允许用户精确地指定他想去的是哪一个主街, 其中 用到的特性是 限定版本号 (peg revision), 它的目的是确定一条唯一的历史线. 因为在任意一个给定的时刻 (或者说给定的版本号) 一条路径上至多只能有一个版 本控制对象, 所以说结合使用路径与限定版本号就可以明确地识别一条特定的 历史线. 限定版本号使用 at 语法 (at syntax) 在 Subversion 的命令行客户端工具上 指定, 之所以叫作 at 语法 是因为指定版本号的方式是在路径 的末尾加上符号 @, 然后再写上版本号.

但是本书多次提到的 --revision (-r) 到底 是什么? 这个版本号或版本号集合叫作 实施版本号 (operative revision) 或 实施版本号范围 (operative revision range ). 一旦用路径和限定版本号确定一条特定的历史线, Subversion 就 对实施版本号执行用户请求的操作. 用芝加哥的道路进行类比, 如果我们要去 Wheaton 的 606 N. 主街[11], 可以把 主街 看成路径, 把 Wheaton 看成限定 版本号, 这两项信息确定了一条唯一的路径, 避免我们走弯路. 现在我们把 606 N. 作为实施版本号, 最终我们得到了一个精确的目的地.

比如说用户在很久以前就创建了仓库, 在版本号 1 添加了第一个目录 concept, 用户后来在目录里放了一个介绍概念的文件 IDEA. 几次提交后, 项目的代码逐渐成型, 在版本号 20 用户把 concept 重命名为 frabnaggilywort . 在版本号 27, 用户又有了一个新主意, 所以在项目根目录下又 创建了目录 concept, 里面也放了一个描述概念的文件 IDEA. 然后又过了 5 年, 期间提交了几千次修改.

几年后, 用户想知道文件 IDEA 在版本号 1 中是什么 样子, 但是 Subversion 需要知道用户是在询问 当前 文件 在版本号 1 时的内容, 还是在问版本号 1 中文件 concept/IDEA 的内容. 当然这两个问题的答案是不一样的, 利用限定版本号, 用户 就可以向 Subversion 说明他想问的是哪一个问题. 为了确定当前的 IDEA 在版本号 1 时的内容, 用户执行了:

$ svn cat -r 1 concept/IDEA 
svn: E195012: Unable to find repository location for 'concept/IDEA' in revision 1

当然, 在这个例子里, 当前的文件 IDEA 在版本号 1 时并不存在, 于是 Subversion 报了一个错误. 上面的命令实际上是以下显式 指定持勾版本号命令的简写形式:

$ svn cat -r 1 concept/IDEA@BASE
svn: E195012: Unable to find repository location for 'concept/IDEA' in revision 1

命令的执行结果是预料之中的.

敏锐的读者可能想知道是否是限定版本号的语法导致了问题, 因为工作副本 路径或 URL 本身可能就带有符号 @, 毕竟 svn 怎么知道 news@11 是表示一个目录的普通名字, 还是表示 news 的版本号 11? 谢天谢地, svn 总是当成后一种情况, 方法是在路径的末尾添加一 个 @ 符号, 例如 news@11@. svn 只关心参数中的最后一个 @, 即使省略了 @ 后面的版本号也是合法的. 这个方法也适用 于以 @ 结尾的路径—你可以用 filename@@ 表示一个名为 filename@ 的文件.

再考虑另一个问题—在版本号 1 中, 占用路径 concept/IDEA 的文件的内容是什么? 我们可以用一个 带有显式限定版本号的命令来回答这个问题.

$ svn cat concept/IDEA@1
The idea behind this project is to come up with a piece of software
that can frab a naggily wort.  Frabbing naggily worts is tricky
business, and doing it incorrectly can have serious ramifications, so
we need to employ over-the-top input validation and data verification
mechanisms.

注意在上面的命令中我们并没有提供实施版本号, 这是因为如果没有指定 实施版本号, Subversion 默认使用限定版本号作为实施版本号.

命令的执行结果看来是正确的, 输出的文本甚至提到了 frab a naggily wort, 所以它描述的软件应该就是现在的 Frabnaggilywort, 实际上我们还可以通过组合显式的限定版本号和显式的实施版本号来验证这一点. 我们已经知道在 HEAD 里, 项目 Frabnaggilywort 位于目录 frabnaggilywort, 于是我们希望看到 HEAD frabnaggilywort/IDEA 在版本号 1 中 的内容.

$ svn cat -r 1 frabnaggilywort/IDEA@HEAD
The idea behind this project is to come up with a piece of software
that can frab a naggily wort.  Frabbing naggily worts is tricky
business, and doing it incorrectly can have serious ramifications, so
we need to employ over-the-top input validation and data verification
mechanisms.

限定版本号和实施版本号也不需要如此琐碎, 举例来说, frabnaggilywort 已经从 HEAD 删除, 但是我们知道它在版本号 20 时还是存在的, 而且我们想知道其中存放的 IDEA 在版本号 4 和版本号 10 之间的差异, 可以使用 限定版本号 20, 结合上文件 IDEA 的版本号 20 的 URL, 然后使用 4 和 10 作为实施版本号范围.

$ svn diff -r 4:10 http://svn.red-bean.com/projects/frabnaggilywort/IDEA@20
Index: frabnaggilywort/IDEA
===================================================================
--- frabnaggilywort/IDEA	(revision 4)
+++ frabnaggilywort/IDEA	(revision 10)
@@ -1,5 +1,5 @@
-The idea behind this project is to come up with a piece of software
-that can frab a naggily wort.  Frabbing naggily worts is tricky
-business, and doing it incorrectly can have serious ramifications, so
-we need to employ over-the-top input validation and data verification
-mechanisms.
+The idea behind this project is to come up with a piece of
+client-server software that can remotely frab a naggily wort.
+Frabbing naggily worts is tricky business, and doing it incorrectly
+can have serious ramifications, so we need to employ over-the-top
+input validation and data verification mechanisms.

幸运的是, 大多数人都不会碰到这么复杂的情况, 但是如果遇到了, 记住 限定版本号可以帮助 Subversion 消除歧义.



[10] 你不应该给它取名字, 因为一旦取了名字, 你就开始喜欢它了. —Mike Wazowski

[11] 把 606 N. 主街作为 Wheaton 的历史中心应该是比较恰当的.