此文本尚未完善,内容可能随时更改,可能不准确地描述 Apache™ Subversion® 软件的任何已发布版本。将此页面添加为书签或推荐给他人可能不是一个好主意。请访问 https://svnbooks.subversion.org.cn/ 获取此书的稳定版本。
版本控制系统(或修订控制系统)是跟踪文件(在某些情况下还有目录)随时间推移的增量版本(或修订)的系统。当然,仅仅跟踪用户(或用户组)文件和目录的各个版本本身并不是很有趣。使版本控制系统有用的是,它允许你探索导致每个版本的更改,并方便地任意调回这些更改。
在本节中,我们将介绍一些相当高级的版本控制系统组件和概念。我们将限制我们的讨论范围,只涉及现代版本控制系统——在当今互联的世界中,承认无法跨广域网运行的版本控制系统几乎没有意义。
版本控制系统的核心是代码库,它是该系统数据的中心存储库。代码库通常以 文件系统树(文件和目录的层次结构)的形式存储信息。任意数量的 客户端连接到代码库,然后读写这些文件。通过写入数据,客户端使信息可供其他人使用;通过读取数据,客户端从其他人那里接收信息。图 1.1,“典型的客户端/服务器系统” 说明了这一点。
为什么这很有趣?到目前为止,这听起来像是典型文件服务器的定义。实际上,代码库 是 一种文件服务器,但它并非你通常遇到的类型。代码库的特殊之处在于,随着代码库中文件的更改,代码库会记住这些文件的每个版本。
当客户端从代码库读取数据时,它通常只看到文件系统树的最新版本。但使版本控制客户端有趣的是,它还有能力从代码库请求文件系统的先前状态。版本控制客户端可以提出诸如 “上周三这个目录包含什么内容?” 和 “谁是最后修改此文件的人,他做了哪些修改?” 之类的历史问题。这些问题是任何版本控制系统的核心。
版本控制系统的价值在于它跟踪文件和目录的版本,但软件世界的其余部分并不以 “文件和目录的版本” 为操作对象。大多数软件程序只知道如何操作特定类型文件的 单个 版本。那么,版本控制用户如何以具体的方式与包含多个版本文件的抽象(并且通常是远程的)代码库进行交互?他的或她的文字处理软件、演示软件、源代码编辑器、网页设计软件或其他程序(它们都以简单数据文件的形式进行交易)如何访问这些文件?答案在于版本控制结构,称为 工作副本。
工作副本实际上是用户 VCS 管理数据特定版本的本地副本,用户可以自由地在这个副本上工作。工作副本[5] 在其他软件看来与任何其他包含文件的本地目录相同,因此这些程序不需要 “版本控制感知” 才能从这些数据中读取和写入这些数据。管理工作副本并将对内容所做的更改传达至代码库以及从代码库接收更改的任务完全由版本控制系统的客户端软件负责。
如果版本控制系统的首要任务是跟踪数字信息随时间的推移而产生的各种版本,那么任何现代版本控制系统的次要任务是使协作编辑和共享这些数据成为可能。但不同的系统使用不同的策略来实现这一点。了解这些不同的策略很重要,原因有以下两个。首先,它将帮助你比较和对比现有的版本控制系统,以防你遇到其他类似于 Subversion 的系统。除此之外,它还有助于你更有效地使用 Subversion,因为 Subversion 本身支持几种不同的工作方式。
所有版本控制系统都必须解决同一个基本问题:系统如何允许用户共享信息,同时又防止他们意外地踩到对方的脚?用户很容易意外地覆盖代码库中的对方更改。
考虑 图 1.2,“要避免的问题” 中所示的场景。假设我们有两个同事,哈里和莎莉。他们都决定同时编辑同一个代码库文件。如果哈里首先将他的更改保存到代码库,那么(几分钟后)莎莉可能会意外地用她自己的新版本文件覆盖这些更改。虽然哈里的文件版本不会永远丢失(因为系统会记住每一次更改),但哈里所做的任何更改 不会 出现在莎莉更新的文件版本中,因为她一开始就没有看到哈里的更改。哈里的工作实际上还是丢失了——或者至少没有出现在文件的最新版本中——而且很可能是意外造成的。这绝对是我们想要避免的情况!
许多版本控制系统使用 锁定-修改-解锁 模型来解决多个作者相互覆盖对方工作的这个问题。在此模型中,代码库一次只允许一个人更改文件。这种排他性策略使用锁来管理。哈里必须 “锁定” 文件,然后才能开始对其进行更改。如果哈里锁定了某个文件,莎莉就不能锁定它,因此也不能对该文件进行任何更改。她所能做的就是等待哈里完成他的更改,保存文件并释放他的锁。哈里解锁文件后,莎莉就可以通过锁定文件来轮流操作。然后她就可以读取文件的最新版本并进行编辑。图 1.3,“锁定-修改-解锁解决方案” 演示了这种简单的解决方案。
锁定-修改-解锁模型的问题在于它有点限制性,并且经常成为用户的障碍。
锁定可能会导致管理问题。 有时哈里会锁定文件然后忘记它。同时,因为莎莉仍在等待编辑文件,所以她的手被绑住了。然后哈里去度假了。现在莎莉必须让管理员释放哈里的锁。这种情况最终会导致不必要的延迟和时间浪费。
锁定可能会导致不必要的序列化。 如果哈里正在编辑文本文件开头,而莎莉只想编辑同一个文件的结尾会怎样?这些更改根本没有重叠。他们可以轻松地同时编辑文件,并且不会造成任何重大损害,前提是更改已正确合并在一起。在这种情况下,他们不需要轮流操作。
锁定可能会造成虚假的安全感。 假设哈里锁定并编辑文件 A,而莎莉同时锁定并编辑文件 B。但是,如果 A 和 B 相互依赖,并且对每个文件所做的更改在语义上不兼容会怎样?突然,A 和 B 不再一起工作了。锁定系统无力阻止这个问题,但它以某种方式提供了虚假的安全感。哈里和莎莉很容易想象,通过锁定文件,每个人都在开始一项安全、隔离的任务,因此他们无需在早期讨论他们不兼容的更改。锁定通常成为真实沟通的替代品。
Subversion、CVS 和许多其他版本控制系统使用 复制-修改-合并 模型作为锁定的替代方案。在此模型中,每个用户的客户端都与项目代码库联系并创建个人工作副本。然后用户同时独立地工作,修改他们的私有副本。最后,私有副本合并成一个新的最终版本。版本控制系统通常会协助合并,但最终,人类负责确保合并正确完成。
以下是一个示例。假设哈里和莎莉都从代码库复制创建了同一个项目的副本。他们同时工作,对他们副本中的同一个文件 A 进行更改。莎莉首先将她的更改保存到代码库。当哈里稍后尝试保存他的更改时,代码库会通知他他的文件 A 已过期。换句话说,代码库中的文件 A 在他上次复制它之后已发生更改。因此,哈里要求他的客户端将任何新的更改从代码库合并到他工作副本中的文件 A 中。很可能莎莉的更改没有与他自己的更改重叠;一旦他将两组更改都整合在一起,他便将他的工作副本保存回代码库。图 1.4,“复制-修改-合并解决方案” 和 图 1.5,“复制-修改-合并解决方案(续)” 展示了此过程。
但是,如果莎莉的更改 确实 与哈里的更改重叠会怎样?然后呢?这种情况称为 冲突,通常不是什么大问题。当哈里要求他的客户端将最新的代码库更改合并到他的工作副本中时,他文件 A 的副本会以某种方式被标记为处于冲突状态:他将能够看到两组冲突的更改并手动选择其中之一。请注意,软件无法自动解决冲突;只有人类能够理解并做出必要的明智选择。一旦哈里手动解决了重叠的更改——也许是在与莎莉讨论之后——他就可以安全地将合并后的文件保存回代码库。
复制-修改-合并模型听起来可能有点混乱,但在实践中,它运行起来非常流畅。用户可以并行工作,无需相互等待。当他们在同一个文件上工作时,事实证明他们大多数并发更改根本没有重叠;冲突很少见。并且解决冲突所需的时间通常远少于锁定系统所浪费的时间。
归根结底,这一切都取决于一个关键因素:用户沟通。当用户沟通不良时,语法和语义冲突都会增加。没有哪个系统能迫使用户完美沟通,也没有哪个系统能检测到语义冲突。因此,没有必要陷入虚假的安全感中,认为锁定系统会以某种方式防止冲突;实际上,锁定似乎更多地阻碍了生产力而不是其他任何事情。