这份文档描述了 Subversion 1.2。如果您运行的是更新版本的 Subversion,我们强烈建议您访问 https://svnbooks.subversion.org.cn/ 并查阅适合您 Subversion 版本的书籍。

使用 API

针对 Subversion 库 API 开发应用程序相当简单。所有公共头文件都位于源代码树的 subversion/include 目录中。当您从源代码构建和安装 Subversion 本身时,这些头文件将被复制到您的系统位置。这些头文件代表了 Subversion 库用户可以访问的所有函数和类型。

您可能会注意到的第一件事是,Subversion 的数据类型和函数受到命名空间保护。每个公共 Subversion 符号名称都以 svn_ 开头,后面跟着一个简短的代码,表示定义符号的库(例如 wcclientfs 等),然后是一个下划线 (_),最后是符号名称的其余部分。半公共函数(在给定库的源文件之间使用,但不在该库之外的代码中使用,并在库目录本身内找到)与这种命名方案不同,它们在库代码后面不使用单个下划线,而是使用双下划线 (__)。私有函数对于给定的源文件来说没有特殊的前缀,并且声明为 static。当然,编译器对这些命名约定不感兴趣,但它们有助于阐明给定函数或数据类型的范围。

Apache Portable Runtime 库

除了 Subversion 自己的数据类型之外,您还会看到许多对以 apr_ 开头的类型数据的引用——来自 Apache Portable Runtime (APR) 库的符号。APR 是 Apache 的可移植性库,最初是从其服务器代码中提取出来的,试图将操作系统特定的部分与代码的与操作系统无关的部分分离。结果是一个库,它提供了一个通用的 API,用于执行跨操作系统略有不同或完全不同的操作。虽然 Apache HTTP Server 显然是 APR 库的第一个用户,但 Subversion 开发人员立即认识到使用 APR 的价值。这意味着 Subversion 本身几乎没有操作系统特定的代码部分。此外,这意味着 Subversion 客户端可以在服务器运行的任何地方编译和运行。目前,此列表包括所有版本的 Unix、Win32、BeOS、OS/2 和 Mac OS X。

除了提供跨操作系统一致的系统调用实现外,[43] APR 使 Subversion 可以立即访问许多自定义数据类型,例如动态数组和哈希表。Subversion 在整个代码库中广泛使用这些类型。但也许最普遍的 APR 数据类型,几乎在每个 Subversion API 原型中都存在,是 apr_pool_t——APR 内存池。Subversion 在内部使用池来满足其所有内存分配需求(除非外部库要求对其通过 API 传递的数据使用不同的内存管理方案),[44] 虽然针对 Subversion API 进行编码的人员不需要这样做,但他们需要为需要它们的 API 函数提供池。这意味着 Subversion API 用户还必须链接到 APR,必须调用 apr_initialize() 来初始化 APR 子系统,然后必须获取一个池用于 Subversion API 调用。有关更多信息,请参阅 名为“使用内存池编程”的部分

URL 和路径要求

由于 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%20File.txt,而不是传递 file:///home/username/My File.txt 作为名为 My File.txt 的文件的 URL。同样,Subversion 提供了您的应用程序可以使用的一些辅助函数——svn_path_uri_encode()svn_path_uri_decode(),分别用于 URI 编码和解码。

使用 C 和 C++ 以外的语言

如果您有兴趣将 Subversion 库与 C 程序以外的其他程序一起使用——例如 Python 或 Perl 脚本——Subversion 通过简化包装器和接口生成器 (SWIG) 为此提供了一些支持。Subversion 的 SWIG 绑定位于 subversion/bindings/swig 中,虽然仍在不断完善,但已经可以正常使用。这些绑定允许您间接调用 Subversion API 函数,使用包装器将您脚本语言的本机数据类型转换为 Subversion 的 C 库所需的数据类型。

通过语言绑定访问 Subversion API 有一个明显的好处——简单性。一般来说,像 Python 和 Perl 这样的语言比 C 或 C++ 灵活且易于使用。这些语言提供的这种高级数据类型和上下文驱动的类型检查通常更适合处理来自用户的 信息。正如您所知,人类擅长将输入错误地输入程序,而脚本语言往往更优雅地处理这些错误的信息。当然,这种灵活性通常是以性能为代价的。这就是为什么使用经过严格优化的基于 C 的接口和库套件,结合功能强大、灵活的绑定语言如此吸引人的原因。

让我们看一个使用 Subversion 的 Python SWIG 绑定递归地遍历最年轻的存储库修订版并打印遍历过程中到达的各种路径的示例程序。

示例 8.2 使用 Python 的存储库层

#!/usr/bin/python

"""Crawl a repository, printing versioned object path names."""

import sys
import os.path
import svn.fs, svn.core, svn.repos

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."""

    # Print the name of this path.
    print directory + "/"
    
    # Get the directory entries for DIRECTORY.
    entries = svn.fs.svn_fs_dir_entries(root, directory, pool)

    # Use an iteration subpool.
    subpool = svn.core.svn_pool_create(pool)

    # Loop over the entries.
    names = entries.keys()
    for name in names:
        # Clear the iteration subpool.
        svn.core.svn_pool_clear(subpool)

        # Calculate the entry's full path.
        full_path = directory + '/' + 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 svn.fs.svn_fs_is_dir(root, full_path, subpool):
            crawl_filesystem_dir(root, full_path, subpool)
        else:
            # Else it's a file, so print its path here.
            print full_path

    # Destroy the iteration subpool.
    svn.core.svn_pool_destroy(subpool)

def crawl_youngest(pool, repos_path):
    """Open the repository at REPOS_PATH, and recursively crawl its
    youngest revision."""
    
    # Open the repository at REPOS_PATH, and get a reference to its
    # versioning filesystem.
    repos_obj = svn.repos.svn_repos_open(repos_path, pool)
    fs_obj = svn.repos.svn_repos_fs(repos_obj)

    # Query the current youngest revision.
    youngest_rev = svn.fs.svn_fs_youngest_rev(fs_obj, pool)
    
    # Open a root object representing the youngest (HEAD) revision.
    root_obj = svn.fs.svn_fs_revision_root(fs_obj, youngest_rev, pool)

    # Do the recursive crawl.
    crawl_filesystem_dir(root_obj, "", pool)
    
if __name__ == "__main__":
    # Check for sane usage.
    if len(sys.argv) != 2:
        sys.stderr.write("Usage: %s REPOS_PATH\n"
                         % (os.path.basename(sys.argv[0])))
        sys.exit(1)

    # Canonicalize (enough for Subversion, at least) the repository path.
    repos_path = os.path.normpath(sys.argv[1])
    if repos_path == '.': 
        repos_path = ''

    # Call the app-wrapper, which takes care of APR initialization/shutdown
    # and the creation and cleanup of our top-level memory pool.
    svn.core.run_app(crawl_youngest, repos_path)

在 C 中,这个相同的程序需要处理自定义数据类型(例如 APR 库提供的那些)来表示条目哈希和路径列表,但 Python 有哈希(称为“字典”)和列表作为内置数据类型,并提供了一系列丰富的函数来操作这些类型。因此,SWIG(在 Subversion 的语言绑定层的一些自定义的帮助下)负责将这些自定义数据类型映射到目标语言的本机数据类型。这为使用该语言的用户提供了一个更直观的接口。

Subversion 的 Python 绑定也可以用于工作副本操作。在本章的上一节中,我们提到了 libsvn_client 接口,以及它是为了简化编写 Subversion 客户端的过程而存在的。以下是如何通过 SWIG 绑定访问该库来重新创建 svn status 命令的简化版本的简要示例。

示例 8.3 Python 状态爬虫

#!/usr/bin/env python

"""Crawl a working copy directory, printing status information."""

import sys
import os.path
import getopt
import svn.core, svn.client, svn.wc

def generate_status_code(status):
    """Translate a status value into a single-character status code,
    using the same logic as the Subversion command-line client."""

    if status == svn.wc.svn_wc_status_none:
        return ' '
    if status == svn.wc.svn_wc_status_normal:
        return ' '
    if status == svn.wc.svn_wc_status_added:
        return 'A'
    if status == svn.wc.svn_wc_status_missing:
        return '!'
    if status == svn.wc.svn_wc_status_incomplete:
        return '!'
    if status == svn.wc.svn_wc_status_deleted:
        return 'D'
    if status == svn.wc.svn_wc_status_replaced:
        return 'R'
    if status == svn.wc.svn_wc_status_modified:
        return 'M'
    if status == svn.wc.svn_wc_status_merged:
        return 'G'
    if status == svn.wc.svn_wc_status_conflicted:
        return 'C'
    if status == svn.wc.svn_wc_status_obstructed:
        return '~'
    if status == svn.wc.svn_wc_status_ignored:
        return 'I'
    if status == svn.wc.svn_wc_status_external:
        return 'X'
    if status == svn.wc.svn_wc_status_unversioned:
        return '?'
    return '?'

def do_status(pool, wc_path, verbose):
    # Calculate the length of the input working copy path.
    wc_path_len = len(wc_path)

    # Build a client context baton.
    ctx = svn.client.svn_client_ctx_t()

    def _status_callback(path, status, root_path_len=wc_path_len):
        """A callback function for svn_client_status."""

        # Print the path, minus the bit that overlaps with the root of
        # the status crawl
        text_status = generate_status_code(status.text_status)
        prop_status = generate_status_code(status.prop_status)
        print '%s%s  %s' % (text_status, prop_status, path[wc_path_len + 1:])
        
    # Do the status crawl, using _status_callback() as our callback function.
    svn.client.svn_client_status(wc_path, None, _status_callback,
                                 1, verbose, 0, 0, ctx, pool)

def usage_and_exit(errorcode):
    """Print usage message, and exit with ERRORCODE."""
    stream = errorcode and sys.stderr or sys.stdout
    stream.write("""Usage: %s OPTIONS WC-PATH
Options:
  --help, -h    : Show this usage message
  --verbose, -v : Show all statuses, even uninteresting ones
""" % (os.path.basename(sys.argv[0])))
    sys.exit(errorcode)
    
if __name__ == '__main__':
    # Parse command-line options.
    try:
        opts, args = getopt.getopt(sys.argv[1:], "hv", ["help", "verbose"])
    except getopt.GetoptError:
        usage_and_exit(1)
    verbose = 0
    for opt, arg in opts:
        if opt in ("-h", "--help"):
            usage_and_exit(0)
        if opt in ("-v", "--verbose"):
            verbose = 1
    if len(args) != 1:
        usage_and_exit(2)
            
    # Canonicalize (enough for Subversion, at least) the working copy path.
    wc_path = os.path.normpath(args[0])
    if wc_path == '.': 
        wc_path = ''

    # Call the app-wrapper, which takes care of APR initialization/shutdown
    # and the creation and cleanup of our top-level memory pool.
    svn.core.run_app(do_status, wc_path, verbose)

不幸的是,Subversion 的语言绑定往往缺乏对核心 Subversion 模块的关注程度。但是,人们已经付出了巨大的努力来为 Python、Perl 和 Ruby 创建功能绑定。在某种程度上,为这些语言准备 SWIG 接口文件所做的工作可以在为 SWIG 支持的其他语言生成绑定时重复使用(包括 C#、Guile、Java、MzScheme、OCaml、PHP、Tcl 等的版本)。但是,需要一些额外的编程来弥补 SWIG 需要帮助进行交互的复杂 API。有关 SWIG 本身的更多信息,请参阅项目的网站 http://www.swig.org/



[43] Subversion 尽可能地使用 ANSI 系统调用和数据类型。

[44] Neon 和 Berkeley DB 是这类库的示例。

TortoiseSVN 官方中文版 1.14.7 发布