本文档旨在描述 Subversion 1.4。如果您运行的是更新版本的 Subversion,我们强烈建议您访问 https://svnbook.subversion.org.cn/ 并查阅适合您 Subversion 版本的版本。

分层库设计

Subversion 的每个核心库都可以说是存在于三个主要层中的一个——存储库层、存储库访问 (RA) 层或客户端层(参见 图 1,“Subversion 的体系结构”)。我们将很快研究这些层,但首先,让我们简要总结一下 Subversion 的各个库。为了保持一致性,我们将使用其无扩展名的 Unix 库名称来引用这些库(libsvn_fs、libsvn_wc、mod_dav_svn 等)。

libsvn_client

客户端程序的主要接口

libsvn_delta

树和字节流差异化例程

libsvn_diff

上下文差异化和合并例程

libsvn_fs

文件系统通用组件和模块加载器

libsvn_fs_base

Berkeley DB 文件系统后端

libsvn_fs_fs

原生文件系统 (FSFS) 后端

libsvn_ra

存储库访问通用组件和模块加载器

libsvn_ra_dav

WebDAV 存储库访问模块

libsvn_ra_local

本地存储库访问模块

libsvn_ra_serf

另一个(实验性的)WebDAV 存储库访问模块

libsvn_ra_svn

自定义协议存储库访问模块

libsvn_repos

存储库接口

libsvn_subr

各种有用的子例程

libsvn_wc

工作副本管理库

mod_authz_svn

Apache 授权模块,用于通过 WebDAV 访问 Subversion 存储库

mod_dav_svn

Apache 模块,用于将 WebDAV 操作映射到 Subversion 操作

前面的列表中“各种”这个词只出现过一次,这是一个好兆头。Subversion 开发团队认真确保功能在正确的层和库中。也许模块化设计的最大优势是它从开发人员的角度来看没有复杂性。作为一名开发人员,您可以快速形成一种“全局”的概念,使您能够轻松地确定某些功能的位置。

模块化的另一个好处是能够用一个完全新的库替换给定的模块,该库实现相同的 API,而不会影响代码库的其余部分。在某种程度上,这已经在 Subversion 中发生了。libsvn_ra_dav、libsvn_ra_local、libsvn_ra_serf 和 libsvn_ra_svn 库都实现了相同的接口,它们都作为插件工作到 libsvn_ra。所有四个库都与存储库层通信——libsvn_ra_local 直接连接到存储库;其他三个通过网络连接。libsvn_fs_base 和 libsvn_fs_fs 库是另一对以不同方式实现相同功能的库——两者都是 libsvn_fs 通用库的插件。

客户端本身也突出了 Subversion 设计中模块化的优势。Subversion 的 libsvn_client 库是设计功能完备的 Subversion 客户端所需的大部分功能的一站式商店(参见 名为“客户端层”的部分)。因此,虽然 Subversion 发行版只提供 svn 命令行客户端程序,但有几个第三方程序提供了各种形式的图形客户端 UI。这些 GUI 使用与库存命令行客户端相同的 API。这种模块化在可用的 Subversion 客户端和 IDE 集成的激增中发挥了重要作用,并因此促成了 Subversion 本身的巨大采用率。

存储库层

在提到 Subversion 的存储库层时,我们通常指的是两个基本概念——版本化文件系统实现(通过 libsvn_fs 访问,并由其 libsvn_fs_base 和 libsvn_fs_fs 插件支持),以及包装它的存储库逻辑(如在 libsvn_repos 中实现)。这些库为您的版本控制数据的各个修订版提供存储和报告机制。此层通过存储库访问层连接到客户端层,并且从 Subversion 用户的角度来看,它是“线路的另一端”。

Subversion 文件系统不是您会在操作系统中安装的内核级文件系统(如 Linux ext2 或 NTFS),而是一个虚拟文件系统。它不是将“文件”和“目录”存储为真实的文件和目录(例如,您可以使用您喜欢的 shell 程序导航到其中的文件和目录),而是使用两个可用的抽象存储后端之一——Berkeley DB 数据库环境或平面文件表示。(要了解有关两个存储库后端的更多信息,请参见 名为“选择数据存储”的部分)。开发人员社区对让未来的 Subversion 版本能够使用其他后端数据库系统表现出极大的兴趣,也许可以通过像 Open Database Connectivity (ODBC) 这样的机制来实现。事实上,Google 在推出 Google Code 项目托管服务之前就做过类似的事情:他们在 2006 年年中宣布,其开源团队的成员编写了一个新的专有 Subversion 文件系统插件,该插件使用其超可扩展的 Bigtable 数据库进行存储。

libsvn_fs 导出的文件系统 API 包含您对任何其他文件系统 API 所期望的功能——您可以创建和删除文件和目录,在它们之间复制和移动它们,修改文件内容,等等。它还具有一些不太常见的功能,例如能够在每个文件或目录上添加、修改和删除元数据(“属性”)。此外,Subversion 文件系统是一个版本化文件系统,这意味着当您对目录树进行更改时,Subversion 会记住您的树在更改之前的样子。以及之前的更改。以及之前的更改。以及之前的更改。等等,一直追溯到版本化时间(并且略微超出)您第一次开始将内容添加到文件系统的那一刻。

您对树所做的所有修改都是在 Subversion 提交事务的上下文中完成的。以下是修改文件系统的简化通用例程

  1. 开始一个 Subversion 提交事务。

  2. 进行更改(添加、删除、属性修改等)。

  3. 提交您的事务。

提交您的事务后,您的文件系统修改将永久存储为历史工件。这些循环中的每一个都会生成您的树的单个新修订版,并且每个修订版都永远可作为“当时的情况”的不可变快照访问。

文件系统接口提供的多数功能都处理对单个文件系统路径进行的操作。也就是说,从文件系统外部来看,描述和访问文件的单个修订版以及目录的主要机制是通过使用像 /foo/bar 这样的路径字符串,就好像您正在通过您喜欢的 shell 程序来访问文件和目录一样。您可以通过将它们将要位于的路径传递给正确的 API 函数来添加新文件和目录。您可以通过相同的机制查询有关它们的信息。

但是,与大多数文件系统不同的是,仅仅路径不足以在 Subversion 中识别文件或目录。将目录树视为一个二维系统,其中节点的兄弟节点表示一种左右移动,而进入子目录表示一种向下移动。 图 8.1,“文件和目录在两个维度中” 显示了树的典型表示,正是这样。

图 8.1. 文件和目录在两个维度中

Files and directories in two dimensions

这里的区别是 Subversion 文件系统有一个大多数文件系统都没有的第三维度——时间![53] 在文件系统接口中,几乎每个带有path 参数的函数也都需要一个root 参数。此 svn_fs_root_t 参数描述了修订版或 Subversion 事务(它只是正在进行的修订版),并提供了理解修订版 32 中 /foo/bar 与修订版 98 中的相同路径之间的区别所需的三维上下文。 图 8.2,“版本化时间——第三维度!” 显示了修订版历史记录作为 Subversion 文件系统宇宙的附加维度。

图 8.2. 版本化时间——第三维度!

Versioning time—the third dimension!

正如我们之前提到的,libsvn_fs API 看起来和感觉起来就像任何其他文件系统一样,除了它具有这种神奇的版本化功能。它旨在可被任何对版本化文件系统感兴趣的程序使用。并非巧合的是,Subversion 本身也对该功能感兴趣。但是,虽然文件系统 API 应该足以满足基本的文件和目录版本化支持,但 Subversion 想要更多——这就是 libsvn_repos 的用武之地。

Subversion 存储库库 (libsvn_repos) 位于(从逻辑上来说)libsvn_fs API 之上,提供了超出底层版本化文件系统逻辑的功能。它不会完全包装每个文件系统函数——只有文件系统活动的一般循环中的某些主要步骤会被存储库接口包装。其中一些包括创建和提交 Subversion 事务,以及修改修订版属性。这些特定事件被存储库层包装,因为它们与它们相关联的钩子。存储库钩子系统与实现版本化文件系统没有直接关系,因此它位于存储库包装库中。

钩子机制只是将单独的存储库库从文件系统代码的其余部分抽象出来的原因之一。libsvn_repos API 为 Subversion 提供了一些其他重要工具。这些功能包括能够

  • 创建、打开、销毁和对 Subversion 存储库以及该存储库中包含的文件系统执行恢复步骤。

  • 描述两棵文件系统树之间的差异。

  • 查询与文件系统中一组文件修改相关的提交日志信息,可以是所有修改,也可以是部分修改。

  • 生成一个人类可读的“转储”文件系统,即对文件系统中修订版本的完整表示。

  • 解析该转储格式,并将转储的修订版本加载到另一个 Subversion 存储库中。

随着 Subversion 的不断发展,存储库库将与文件系统库一起发展,以提供更强大的功能和可配置的选项支持。

存储库访问层

如果 Subversion 存储库层位于“线的另一端”,那么存储库访问 (RA) 层就是这条线本身。该层负责在客户端库和存储库之间传递数据,包括 libsvn_ra 模块加载器库、RA 模块本身(目前包括 libsvn_ra_dav、libsvn_ra_local、libsvn_ra_serf 和 libsvn_ra_svn),以及一个或多个 RA 模块需要的任何其他库(例如 mod_dav_svn Apache 模块或 libsvn_ra_svn 的服务器,svnserve)。

由于 Subversion 使用 URL 来标识其存储库资源,因此 URL 方案的协议部分(通常是 file://http://https://svn://svn+ssh://)用于确定哪个 RA 模块将处理通信。每个模块都注册一个它知道如何“”的协议列表,以便 RA 加载器可以在运行时确定使用哪个模块来完成当前的任务。您可以通过运行 svn --version 来确定哪些 RA 模块可供 Subversion 命令行客户端使用,以及它们声称支持哪些协议。

$ svn --version
svn, version 1.4.3 (r23084)
   compiled Jan 18 2007, 07:47:40

Copyright (C) 2000-2006 CollabNet.
Subversion is open source software, see http://subversion.tigris.org/
This product includes software developed by CollabNet (http://www.Collab.Net/).

The following repository access (RA) modules are available:

* ra_dav : Module for accessing a repository via WebDAV (DeltaV) protocol.
  - handles 'http' scheme
  - handles 'https' scheme
* ra_svn : Module for accessing a repository using the svn network protocol.
  - handles 'svn' scheme
* ra_local : Module for accessing a repository on local disk.
  - handles 'file' scheme

$

RA 层导出的公共 API 包含将版本化数据发送到存储库和从存储库接收版本化数据所需的功能。每个可用的 RA 插件都能够使用特定的协议来执行该任务——libsvn_ra_dav 使用 HTTP/WebDAV(可以选择使用 SSL 加密)与运行 mod_dav_svn Subversion 服务器模块的 Apache HTTP 服务器进行通信;libsvn_ra_svn 使用自定义网络协议与 svnserve 程序进行通信;等等。

对于那些希望使用其他协议访问 Subversion 存储库的人来说,这就是存储库访问层模块化的原因!开发人员只需编写一个新的库,该库在一端实现 RA 接口,并在另一端与存储库进行通信。您的新库可以使用现有的网络协议,也可以发明自己的协议。您可以使用进程间通信 (IPC) 调用,或者——让我们疯狂一下,怎么样?——您甚至可以实现一个基于电子邮件的协议。Subversion 提供 API;您提供创造力。

客户端层

在客户端,Subversion 工作副本是所有操作发生的地方。客户端库实现的大部分功能都是为了管理工作副本而存在的——这些目录包含文件和其他子目录,充当一个或多个存储库位置的本地、可编辑的“反映”——并将更改传播到存储库访问层和从存储库访问层传播。

Subversion 的工作副本库 libsvn_wc 负责管理工作副本中的数据。为了实现这一点,该库将每个工作副本目录的管理信息存储在一个特殊的子目录中。这个子目录名为 .svn,存在于每个工作副本目录中,并包含各种其他文件和目录,这些文件和目录记录状态并为管理操作提供私有工作区。对于熟悉 CVS 的人来说,这个 .svn 子目录在目的上类似于 CVS 工作副本中找到的 CVS 管理目录。有关 .svn 管理区域的更多信息,请参见本章的“Inside the Working Copy Administration Area”部分

Subversion 客户端库 libsvn_client 承担着最广泛的责任;它的工作是将工作副本库的功能与存储库访问层的功能融合在一起,然后为任何希望执行一般版本控制操作的应用程序提供最高级的 API。例如,函数 svn_client_checkout() 以 URL 作为参数。它将此 URL 传递给 RA 层,并与特定存储库建立经过身份验证的会话。然后,它要求存储库提供特定的树,并将此树发送到工作副本库,然后工作副本库将完整的副本写入磁盘(.svn 目录和所有)。

客户端库旨在被任何应用程序使用。虽然 Subversion 源代码包含一个标准的命令行客户端,但使用客户端库编写任意数量的 GUI 客户端应该非常容易。Subversion 的新 GUI(或任何新的客户端,实际上)不必是包含命令行客户端的笨拙包装程序——它们可以通过 libsvn_client API 完全访问与命令行客户端使用的相同功能、数据和回调机制。事实上,Subversion 源代码树包含一个小的 C 程序(可以在 tools/examples/minimal_client.c 中找到),它举例说明了如何使用 Subversion API 创建一个简单的客户端程序。



[53] 我们理解这可能会让科幻迷感到震惊,他们长期以来一直认为时间实际上是第四维,我们对我们关于不同理论的主张带来的任何情绪创伤表示歉意。