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

分层库设计

Subversion 的每个核心库都可以说是存在于三个主要层中的一个——存储库层、存储库访问 (RA) 层或客户端层(参见前言中的 图 1,"Subversion 的架构")。我们将在稍后研究这些层,但首先,让我们简要总结一下 Subversion 的各种库。为了保持一致性,我们将使用它们的无扩展名 Unix 库名称来引用这些库(libsvn_fslibsvn_wcmod_dav_svn 等)。

libsvn_client

客户端程序的主要接口

libsvn_delta

树和字节流差异化例程

libsvn_diff

上下文差异化和合并例程

libsvn_fs

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

libsvn_fs_base

Berkeley DB 文件系统后端

libsvn_fs_fs

本地文件系统 (FSFS) 后端

libsvn_ra

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

libsvn_ra_local

本地存储库访问模块

libsvn_ra_neon

WebDAV 存储库访问模块

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_locallibsvn_ra_neonlibsvn_ra_serflibsvn_ra_svn 库都实现了相同的接口,它们都作为 libsvn_ra 的插件工作。所有四个库都与存储库层通信——libsvn_ra_local 直接连接到存储库;另外三个库通过网络连接。libsvn_fs_baselibsvn_fs_fs 库是另一对以不同方式实现相同功能的库——它们都是通用 libsvn_fs 库的插件。

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

仓库层

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

Subversion 文件系统不是在操作系统中安装的内核级文件系统(例如 Linux ext2 或 NTFS),而是一个虚拟文件系统。它不是将 文件目录 存储为真实文件和目录(可以使用您最喜欢的 shell 程序浏览的类型),而是使用两个可用的抽象存储后端之一——Berkeley DB 数据库环境或平面文件表示。(要了解有关两个仓库后端的更多信息,请参见 名为“选择数据存储”的部分)。开发社区甚至对让未来版本的 Subversion 能够使用其他后端数据库系统表现出极大的兴趣,也许可以通过 Open Database Connectivity (ODBC) 等机制实现。事实上,Google 在推出 Google Code 项目托管服务之前就做了类似的事情:他们在 2006 年中期宣布,其开源团队的成员编写了一个新的专有 Subversion 文件系统插件,该插件使用 Google 的超可扩展 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 文件系统有一个大多数文件系统都没有的第三维——时间![67] 在文件系统接口中,几乎每个具有 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_neonlibsvn_ra_locallibsvn_ra_serflibsvn_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.7.0
   compiled Nov 15 2011, 10:10:24

Copyright (C) 2011 The Apache Software Foundation.
This software consists of contributions made by many people; see the NOTICE
file for more information.
Subversion is open source software, see https://subversion.org.cn/

The following repository access (RA) modules are available:

* ra_neon : Module for accessing a repository via WebDAV protocol using Neon.
  - handles 'http' scheme
  - handles 'https' scheme
* ra_svn : Module for accessing a repository using the svn network protocol.
  - with Cyrus SASL authentication
  - handles 'svn' scheme
* ra_local : Module for accessing a repository on local disk.
  - handles 'file' scheme
* ra_serf : Module for accessing a repository via WebDAV protocol using serf.
  - handles 'http' scheme
  - handles 'https' scheme

$

RA 层导出的公共 API 包含将版本化数据发送到仓库和从仓库接收版本化数据所需的功能。每个可用的 RA 插件都能够使用特定协议执行该任务——libsvn_ra_neonlibsvn_ra_serf 使用 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 管理目录。

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 创建一个简单的客户端程序。



[67] 我们理解这可能让科幻迷感到震惊,他们长期以来一直认为时间实际上是第四维度,我们对我们断言不同理论所造成的任何情感创伤表示歉意。