本文档旨在描述 Subversion 1.1。如果您正在运行更新版本的 Subversion,我们强烈建议您访问 https://svnbook.subversion.org.cn/ 并查阅适合您 Subversion 版本的版本。
针对 Subversion 库 API 开发应用程序相当简单。所有公共头文件都位于subversion/include源代码树的目录中。当您从源代码构建和安装 Subversion 本身时,这些头文件将被复制到您的系统位置。这些头文件代表了 Subversion 库用户可以访问的所有函数和类型的完整集合。
您可能会注意到的第一件事是 Subversion 的数据类型和函数是命名空间保护的。每个公共 Subversion 符号名称都以svn_开头,后跟定义符号的库的简短代码(例如wc, client, fs等),后跟一个下划线(_),然后是符号名称的其余部分。半公共函数(在给定库的源文件之间使用,但不在该库外部的代码中使用,并在库目录本身内找到)与这种命名方案的不同之处在于,它们在库代码之后使用双下划线(__),而不是单个下划线。对于给定源文件私有的函数没有特殊的前缀,并且声明为static。当然,编译器对这些命名约定不感兴趣,但它们有助于阐明给定函数或数据类型的范围。
除了 Subversion 自己的数据类型之外,您还会看到许多对以apr_开头的 数据类型的引用——来自 Apache 可移植运行时 (APR) 库的符号。APR 是 Apache 的可移植性库,最初是从其服务器代码中分离出来的,试图将操作系统特定的部分与代码的操作系统无关部分分开。结果是一个库,它提供了一个通用 API 来执行在不同操作系统之间略有不同或完全不同的操作。虽然 Apache HTTP Server 显然是 APR 库的第一个用户,但 Subversion 开发人员立即认识到使用 APR 的价值。这意味着 Subversion 本身实际上没有操作系统特定的代码部分。另外,这意味着 Subversion 客户端可以在服务器运行的任何地方编译和运行。目前此列表包括所有类型的 Unix、Win32、BeOS、OS/2 和 Mac OS X。
除了提供跨操作系统一致的系统调用实现之外,[36] APR 使 Subversion 立即能够访问许多自定义数据类型,例如动态数组和哈希表。Subversion 在整个代码库中广泛使用这些类型。但也许最普遍的 APR 数据类型(在几乎每个 Subversion API 原型中都找到)是apr_pool_t——APR 内存池。Subversion 在内部使用池来满足其所有内存分配需求(除非外部库要求其 API 传递的数据使用不同的内存管理模式),[37] 虽然对 Subversion API 进行编码的人员不需要这样做,但他们需要为需要它们的 API 函数提供池。这意味着 Subversion API 的用户也必须链接到 APR,必须调用apr_initialize()来初始化 APR 子系统,然后必须获取一个池用于 Subversion API 调用。有关更多信息,请参阅 名为“使用内存池进行编程”的部分。
由于远程版本控制操作是 Subversion 存在的全部意义,因此对国际化 (i18n) 支持给予一定的关注是合乎情理的。毕竟,“远程”可能意味着“跨办公室”,但也可能意味着“跨越全球”。为了方便这一点,所有接受路径参数的 Subversion 公共接口都期望这些路径被规范化,并以 UTF-8 编码。例如,这意味着任何驱动 libsvn_client 接口的新客户端二进制文件都需要首先将路径从特定于区域设置的编码转换为 UTF-8,然后再将这些路径传递给 Subversion 库,然后在将这些路径用于非 Subversion 目的之前,将任何结果输出路径从 Subversion 重新转换为区域设置的编码。幸运的是,Subversion 提供了一套函数(请参阅subversion/include/svn_utf.h),任何程序都可以使用这些函数进行这些转换。
此外,Subversion API 要求所有 URL 参数都经过正确 URI 编码。因此,您需要传递file:///home/username/My File.txt作为名为My File.txt的文件的 URL,而不是传递file:///home/username/My%20File.txt。同样,Subversion 提供了您的应用程序可以使用的辅助函数——svn_path_uri_encode和svn_path_uri_decode,分别用于 URI 编码和解码。
如果您有兴趣将 Subversion 库与除 C 程序之外的任何东西一起使用——比如 Python 脚本或 Java 应用程序——Subversion 通过简化包装器和接口生成器 (SWIG) 对此提供了一些初始支持。Subversion 的 SWIG 绑定位于subversion/bindings/swig中,并且正在慢慢发展为可用的状态。这些绑定允许您使用包装器间接调用 Subversion API 函数,这些包装器将您的脚本语言的本机数据类型转换为 Subversion 的 C 库所需的数据类型。
通过语言绑定访问 Subversion API 显然有一个好处——简单。一般来说,像 Python 和 Perl 这样的语言比 C 或 C++ 更灵活、更易于使用。这些语言提供的这种高级数据类型和上下文驱动的类型检查通常更擅长处理来自用户的 信息。众所周知,人类擅长把信息输入到程序中,而脚本语言往往更优雅地处理这些错误信息。当然,这种灵活性通常是以性能为代价的。这就是为什么使用高度优化的基于 C 的接口和库套件,再加上功能强大、灵活的绑定语言,如此具有吸引力的原因。
让我们看一个使用 Subversion 的 Python SWIG 绑定的示例。我们的示例将执行与我们上一个示例相同的事情。请注意这次函数的大小和复杂度的差异!
示例 8.2 使用 Python 的存储库层
from svn import fs import os.path def crawl_filesystem_dir (root, directory, pool): """Recursively crawl DIRECTORY under ROOT in the filesystem, and return a list of all the paths at or below DIRECTORY. Use POOL for all allocations.""" # Get the directory entries for DIRECTORY. entries = fs.dir_entries(root, directory, pool) # Initialize our returned list with the directory path itself. paths = [directory] # Loop over the entries names = entries.keys() for name in names: # Calculate the entry's full path. full_path = os.path.join(basepath, name) # If the entry is a directory, recurse. The recursion will return # a list with the entry and all its children, which we will add to # our running list of paths. if fs.is_dir(fsroot, full_path, pool): subpaths = crawl_filesystem_dir(root, full_path, pool) paths.extend(subpaths) # Else, it is a file, so add the entry's full path to the FILES list. else: paths.append(full_path) return paths
之前示例的 C 实现将变得更长。C 中的相同例程需要密切关注内存使用情况,并且需要使用自定义数据类型来表示条目哈希表和路径列表。Python 有哈希表(称为“字典”)和列表作为内置数据类型,并提供大量操作这些类型的方法。由于 Python 使用引用计数和垃圾收集,因此语言用户无需担心分配和释放内存。
在本章的上一节中,我们提到了libsvn_client接口,以及它是如何为了简化编写 Subversion 客户端的过程而存在的。以下是通过 SWIG 绑定访问该库的简要示例。只需几行 Python 代码,您就可以检出功能齐全的 Subversion 工作副本!
示例 8.3 用于检出工作副本的简单脚本。
#!/usr/bin/env python import sys from svn import util, _util, _client def usage(): print "Usage: " + sys.argv[0] + " URL PATH\n" sys.exit(0) def run(url, path): # Initialize APR and get a POOL. _util.apr_initialize() pool = util.svn_pool_create(None) # Checkout the HEAD of URL into PATH (silently) _client.svn_client_checkout(None, None, url, path, -1, 1, None, pool) # Cleanup our POOL, and shut down APR. util.svn_pool_destroy(pool) _util.apr_terminate() if __name__ == '__main__': if len(sys.argv) != 3: usage() run(sys.argv[1], sys.argv[2])
不幸的是,Subversion 的语言绑定往往缺乏对核心 Subversion 模块的关注程度。但是,已经付出了巨大的努力来创建 Python、Perl 和 Java 的功能绑定。一旦您正确配置了 SWIG 接口文件,理论上为所有支持的 SWIG 语言(当前包括 C#、Guile、Java、MzScheme、OCaml、Perl、PHP、Python、Ruby 和 Tcl 的版本)生成特定的包装器应该很简单。尽管如此,还需要一些额外的编程来弥补 SWIG 需要一些帮助才能推广的复杂 API。有关 SWIG 本身的更多信息,请参阅该项目的网站,网址为http://www.swig.org/.