本文档旨在描述 Apache™ Subversion® 的 1.7.x 系列。如果您运行的是其他版本的 Subversion,强烈建议您访问 https://svnbook.subversion.org.cn/ 并查阅适合您 Subversion 版本的文档。

版本控制基础

版本控制系统(或修订控制系统)是一个跟踪文件和目录随时间推移的增量版本(或修订)的系统。当然,仅仅跟踪用户(或用户组)文件和目录的各种版本本身并不有趣。使版本控制系统变得有用的原因是它允许您探索导致每个版本的更改,并方便地随意恢复这些版本。

在本节中,我们将介绍一些相当高级的版本控制系统组件和概念。我们将讨论范围仅限于现代版本控制系统——在当今互联的世界中,承认无法跨广域网运行的版本控制系统几乎没有意义。

仓库

版本控制系统的核心是仓库,它是该系统数据的中央存储库。仓库通常以 文件系统树 的形式存储信息——文件和目录的层次结构。任何数量的 客户端 都可以连接到仓库,然后读写这些文件。通过写入数据,客户端使信息对其他人可用;通过读取数据,客户端从其他人那里接收信息。 图 1.1,“典型的客户端/服务器系统” 说明了这一点。

图 1.1 典型的客户端/服务器系统

A typical client/server system

为什么这很有趣?到目前为止,这听起来像是对典型文件服务器的定义。实际上,仓库 一种文件服务器,但它不是你通常见到的那种。使仓库特殊的是,当仓库中的文件发生更改时,仓库会记住这些文件的每个版本。

当客户端从仓库读取数据时,它通常只看到文件系统树的最新版本。但使版本控制客户端有趣的是,它还可以从仓库请求文件系统的先前状态。版本控制客户端可以提出诸如 上周三这个目录包含什么内容?谁是最后修改此文件的人,他做了哪些更改? 这样的历史问题。这些问题是任何版本控制系统的核心。

工作副本

版本控制系统的价值在于它跟踪文件和目录的版本,但软件世界的其他部分并不基于文件和目录的版本。大多数软件程序只知道如何操作特定类型文件的单个版本。那么,版本控制用户如何以具体的方式与包含多个版本文件的抽象(通常是远程的)存储库进行交互?他的或她的文字处理软件、演示文稿软件、源代码编辑器、网页设计软件或其他程序(所有这些程序都以简单数据文件的形式进行交易)如何访问这些文件?答案可以在版本控制结构中找到,称为工作副本

工作副本实际上是用户 VCS 管理数据的特定版本的本地副本,用户可以自由地对其进行操作。工作副本[5] 对其他软件来说就像任何其他包含文件的本地目录一样,因此这些程序不需要版本控制感知 才能从该数据中读取和写入数据。管理工作副本并将对工作副本内容所做的更改与存储库进行通信的任务完全落在了版本控制系统的客户端软件上。

版本控制模型

如果版本控制系统的首要任务是跟踪数字信息随时间的各种版本,那么任何现代版本控制系统的次要任务是启用对该数据的协作编辑和共享。但是不同的系统使用不同的策略来实现这一点。了解这些不同的策略很重要,原因有以下几点。首先,它将帮助您比较和对比现有的版本控制系统,以防您遇到类似于 Subversion 的其他系统。除此之外,它还将帮助您更有效地使用 Subversion,因为 Subversion 本身支持几种不同的工作方式。

文件共享问题

所有版本控制系统都必须解决同一个基本问题:系统将如何允许用户共享信息,但防止他们意外踩到彼此的脚?用户很容易意外地覆盖彼此对存储库所做的更改。

考虑图 1.2,“要避免的问题”中所示的场景。假设我们有两个同事,哈里和莎莉。他们都决定同时编辑同一个仓库文件。如果哈里先将他的更改保存到仓库,那么(片刻之后)莎莉可能会不小心用她自己的新版本文件覆盖这些更改。虽然哈里的文件版本不会永远丢失(因为系统会记住每次更改),但哈里所做的任何更改都不会出现在莎莉的较新版本文件中,因为她一开始就没有看到哈里的更改。哈里的工作实际上还是丢失了——或者至少在文件的最新版本中缺失——而且很可能是意外造成的。这绝对是我们想要避免的情况!

图 1.2. 要避免的问题

The problem to avoid

锁定-修改-解锁解决方案

许多版本控制系统使用锁定-修改-解锁模型来解决多个作者互相覆盖彼此工作的这个问题。在这个模型中,仓库一次只允许一个人更改文件。这种排他性策略是使用锁来管理的。哈里必须锁定一个文件才能开始对其进行更改。如果哈里锁定了文件,莎莉就不能也锁定它,因此也不能对该文件进行任何更改。她只能读取文件,并等待哈里完成他的更改并释放他的锁。哈里解锁文件后,莎莉就可以通过锁定和编辑文件来轮流操作。图 1.3,“锁定-修改-解锁解决方案”演示了这个简单的解决方案。

图 1.3. 锁定-修改-解锁解决方案

The lock-modify-unlock solution

锁定-修改-解锁模型的问题在于它有点限制性,而且经常成为用户的障碍

  • 锁定可能会导致管理问题。有时哈里会锁定一个文件,然后忘记它。与此同时,因为莎莉还在等待编辑文件,她的手被绑住了。然后哈里去度假了。现在莎莉必须找管理员来释放哈里的锁。这种情况最终会导致很多不必要的延迟和浪费时间。

  • 锁定可能会导致不必要的序列化。 如果哈利正在编辑文本文件的开头,而莎莉只是想编辑同一个文件的结尾,会怎么样? 这些更改根本不会重叠。 他们可以轻松地同时编辑文件,并且不会造成重大损害,假设更改已正确合并在一起。 在这种情况下,他们无需轮流进行。

  • 锁定可能会造成虚假的安全感。 假设哈利锁定并编辑文件 A,而莎莉同时锁定并编辑文件 B。 但是如果 A 和 B 相互依赖,并且对每个文件所做的更改在语义上不兼容,会怎么样? 突然之间,A 和 B 不再协同工作。 锁定系统无能为力阻止此问题,但它却以某种方式提供了虚假的安全感。 哈利和莎莉很容易想象,通过锁定文件,每个人都开始了一个安全、隔离的任务,因此他们无需过早地讨论他们不兼容的更改。 锁定通常成为真实沟通的替代品。

复制-修改-合并解决方案

Subversion、CVS 和许多其他版本控制系统使用 复制-修改-合并 模型作为锁定的替代方案。 在此模型中,每个用户的客户端都会联系项目存储库并创建一个个人工作副本。 然后,用户同时独立地工作,修改他们的私有副本。 最后,私有副本被合并到一个新的最终版本中。 版本控制系统通常会协助合并,但最终,人类负责确保合并正确完成。

以下是一个示例。 假设哈利和莎莉分别从存储库中创建了同一个项目的副本。 他们同时工作,并对副本中的同一个文件 A 进行更改。 莎莉首先将她的更改保存到存储库中。 当哈利稍后尝试保存他的更改时,存储库会通知他他的文件 A 已过期。 换句话说,存储库中的文件 A 自他上次复制它以来发生了变化。 因此,哈利要求他的客户端将存储库中的任何新更改 合并 到他工作副本中的文件 A 中。 很有可能莎莉的更改不会与他自己的更改重叠; 一旦他将两组更改都集成在一起,他就会将他的工作副本保存回存储库。 图 1.4,“复制-修改-合并解决方案”图 1.5,“复制-修改-合并解决方案(续)” 显示了此过程。

图 1.4. 复制-修改-合并解决方案

The copy-modify-merge solution

图 1.5. 复制-修改-合并解决方案(续)

The copy-modify-merge solution (continued)

但是,如果莎莉的更改确实与哈里的更改重叠怎么办?在这种情况下,这种情况被称为冲突,通常不是什么大问题。当哈里要求他的客户将最新的存储库更改合并到他的工作副本中时,他文件 A 的副本以某种方式被标记为处于冲突状态:他将能够看到两组冲突的更改并手动选择它们。请注意,软件无法自动解决冲突;只有人类能够理解并做出必要的明智选择。一旦哈里手动解决了重叠的更改——也许是在与莎莉讨论之后——他就可以安全地将合并后的文件保存回存储库。

复制-修改-合并模型听起来可能有点混乱,但在实践中,它运行得非常顺畅。用户可以并行工作,永远不会互相等待。当他们在同一个文件上工作时,事实证明他们的大多数并发更改根本没有重叠;冲突很少见。而且解决冲突所需的时间通常远远少于锁定系统所浪费的时间。

归根结底,这一切都归结于一个关键因素:用户沟通。当用户沟通不畅时,语法和语义冲突都会增加。没有系统可以强迫用户完美沟通,也没有系统可以检测语义冲突。因此,没有必要被锁定系统会以某种方式防止冲突的错误安全感所迷惑;实际上,锁定似乎比其他任何东西都更能抑制生产力。



[5] 术语工作副本通常可以应用于任何一个文件版本的本地实例。然而,大多数人使用这个术语时,指的是包含由版本控制系统管理的文件和子目录的整个目录树。