本手册是为描述 Subversion 1.1 而编写的。如果您运行的是更新版本的 Subversion,我们强烈建议您访问 https://svnbook.subversion.org.cn/ 并查阅适合您 Subversion 版本的书籍版本。
复制、移动和重命名文件和目录的能力;能够创建对象,然后删除它,然后在同一路径添加一个新的对象——这些操作是我们一直在计算机上对文件和目录执行的操作,也是我们往往认为理所当然的操作。Subversion 希望您认为它们是理所当然的。Subversion 的文件管理支持非常自由,为版本化文件提供了与您操作非版本化文件时所期望的几乎相同的灵活性。但这种灵活性意味着,在您的存储库的生命周期中,给定的版本化资源可能具有许多路径,而给定的路径可能代表几个完全不同的版本化资源。
Subversion 非常聪明,能够注意到当对象的版本历史包含这样的“地址变更”时。例如,如果您要求对上周被重命名的特定文件的全部日志进行查看,Subversion 会很乐意提供所有这些日志——重命名本身发生的修订版,以及重命名前后相关修订版的日志。所以,大多数情况下,您甚至不必考虑这些事情。但偶尔,Subversion 需要您的帮助来消除歧义。
最简单的例子是当一个目录或文件从版本控制中删除,然后用相同名称创建新的目录或文件并添加到版本控制中时。显然,您删除的内容和您后来添加的内容不是同一个内容,它们只是碰巧有相同的路径,我们将其称为/trunk/object。那么,询问 Subversion 关于/trunk/object的历史是什么意思?您是在询问当前位于该位置的内容,还是您从该位置删除的旧内容?您是在询问发生在该路径上的所有对象的全部操作吗?显然,Subversion 需要您提供一些提示,告诉它您到底在问什么。
由于移动,版本化资源历史甚至可以变得比这更复杂。例如,您可能有一个名为concept的目录,其中包含您一直在玩弄的一些新兴软件项目。不过,最终,该项目发展到足以让这个想法看起来确实有了一些翅膀的程度,所以您做出了不可想象的事情,决定给项目起个名字。[32] 假设您将软件命名为 Frabnaggilywort。此时,将目录重命名为反映项目的新名称是有意义的,因此concept被重命名为frabnaggilywort。生活继续,Frabnaggilywort 发布了 1.0 版本,并被成群结队想要改善生活的人们每天下载和使用。
这是一个很好的故事,但它并没有到此结束。您是一位企业家,您已经有了另一个想法。所以您创建了一个新目录,concept,并且循环再次开始。事实上,循环在数年间多次开始,每次都从旧的concept目录开始,然后有时看到目录在想法实现时被重命名,有时看到它在您放弃想法时被删除。或者,为了让人感到恶心,也许您将concept重命名为其他东西一段时间,但后来出于某种原因又将它重命名回concept。
当出现这些情况时,尝试指示 Subversion 使用这些重复使用的路径就像指示芝加哥西部郊区的一名驾驶员沿着罗斯福路向东行驶,然后左转进入梅恩街。仅仅 20 分钟,您就可以在惠顿、格伦埃林和朗巴德穿过“梅恩街”。而且,它们并不是同一条街。我们的驾驶员——以及我们的 Subversion——需要更多细节才能做正确的事情。
在 1.1 版本中,Subversion 引入了一种方法,让您可以告诉它您到底指的是哪条梅恩街。它被称为 钉住修订版,它是提供给 Subversion 的修订版,其唯一目的是标识一条唯一的历史线。因为在任何给定的时间——或者更准确地说,在任何一个修订版中——最多只有一个版本化资源可以占用一个路径,所以路径和钉住修订版的组合是引用特定历史线的全部内容。钉住修订版是使用 at 语法指定给 Subversion 命令行客户端的,之所以称为“at 语法”,是因为语法涉及将“at 符号” (@) 和钉住修订版附加到与该修订版关联的路径末尾。
但是,我们在这本书中已经讨论过很多次的--revision (-r)呢?该修订版(或修订版集)被称为 操作修订版(或 操作修订版范围)。一旦使用路径和钉住修订版标识了一条特定的历史线,Subversion 就会使用操作修订版执行请求的操作。为了将此映射到我们的芝加哥地区街道类比,如果我们被告知去惠顿的 606 号北梅恩街,[33] 我们可以将“梅恩街”视为我们的路径,将“惠顿”视为我们的钉住修订版。这两个信息标识了一条可以行驶的唯一路径(在梅恩街的南北方向),并将阻止我们在错误的梅恩街上上下行驶以寻找目的地。现在,我们将“606 号北”作为我们的操作修订版,这样我们就 确切地 知道该去哪里了。
Subversion 在使用钉住修订版和操作修订版查找要处理的实际内容时执行一个相当简单的算法。首先,在存储库的该修订版中找到与钉住修订版关联的路径。从那里开始,Subversion 开始步进位于该路径和钉住修订版的对象的各个历史前身。这些前身中的每一个都代表对象的早期版本,并且每一个都存储了它是在哪个修订版中创建的,以及在哪个路径创建的记录。因此,在遍历前身集时,Subversion 会注意到它们中的哪一个是作为某一个操作修订版的对象的最新版本,如果是的话,则将该操作修订版映射到该前身的创建路径/创建修订版对。算法在以下情况之一时终止:要么所有操作修订版都被映射到实际对象位置,要么不再有前身可供遍历,在这种情况下,任何未映射的操作修订版都将被标记为对正在操作的对象无效。
假设很久以前,我们创建了存储库,并在修订版 1 中添加了第一个concept目录,以及一个IDEA文件,该文件位于该目录中,描述了这个概念。经过多个修订版,在这些修订版中添加和调整了实际代码,我们在修订版 20 中将这个目录重命名为frabnaggilywort。在修订版 27 中,我们有了一个新的概念,一个新的concept目录来容纳它,以及一个新的IDEA文件来描述它。然后,五年和两万次修订版飞驰而过,就像在任何一部好的爱情故事中一样。
现在,多年后,我们想知道IDEA文件在修订版 1 中是什么样的。但是 Subversion 需要知道我们是在询问 当前文件在修订版 1 中是什么样的,还是我们在询问位于concepts/IDEA的任何文件在修订版 1 中的内容?当然,这些问题有不同的答案,由于钉住修订版的存在,您可以询问其中的任何一个问题。要找出当前IDEA文件在那个旧修订版中是什么样的,您可以运行
$ svn cat -r 1 concept/IDEA subversion/libsvn_client/ra.c:775: (apr_err=20014) svn: Unable to find repository location for 'concept/IDEA' in revision 1
当然,在这个例子中,当前IDEA文件在修订版 1 中还不存在,因此 Subversion 给出了错误。上面的命令是更长表示法的简写,该表示法显式地列出了钉住修订版。扩展表示法是
$ svn cat -r 1 concept/IDEA@BASE subversion/libsvn_client/ra.c:775: (apr_err=20014) svn: Unable to find repository location for 'concept/IDEA' in revision 1
并且在执行时,具有预期的结果。当应用于工作副本路径时,钉住修订版通常默认为BASE(当前存在于工作副本中的修订版),并且当应用于 URL 时,默认为HEAD。
那么,我们来询问另一个问题——在修订版 1 中,占用地址concepts/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.
这似乎是正确的输出。文本甚至提到了“frabbing naggily worts”,所以这几乎可以肯定就是描述现在称为 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 以及在修订版 20 中包含 Frabnaggilywort 的IDEA文件的 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 消除歧义所需要的额外提示。