阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Subversion API 使用简介

106次阅读
没有评论

共计 10866 个字符,预计需要花费 28 分钟才能阅读完成。

使用 API

使用 Subversion 库 API 开发应用看起来相当的直接,所有的公共头文件放在源文件的 subversion/include 目录,从源代码编译和安装 Subversion 本身,需要这些头文件拷贝到系统位置。这些头文件包括了所有用户和 Subversion 库可以访问的功能和类型。Subversion 开发者社区仔细的确保所有的公共 API 有完好的文档—直接引用头文件的文档。

你首先应该注意 Subversion 的数据类型和方法是命名空间保护的,每一个公共 Subversion 对象名以 svn_ 开头,然后紧跟一个这个对象定义 (如wcclientfs等等)所在的库的简短编码,然后是一个下划线 (_) 和后面的对象名称。半公开的方法 (库使用,但是但库之外代码不可以使用并且只可以在库自己的目录看到) 与这个命名模式不同,并不是库代码之后紧跟一个下划线,他们是用两个下划线 (__)。给定源文件的私有方法没有特殊前缀,使用static 声明。当然,一个编译器不会关心命名习惯,只是用来区分给定方法或数据类型的应用范围。

关于 Subversion 的 API 编程的另一个好的资源是 hacking 指南,可以在 http://subversion.tigris.org/hacking.html 找到,这个文档包含了有用的信息,同时满足 Subversion 本身的开发者和将 Subversion 作为第三方库的开发者。[50]

Apache 可移植运行库

伴随 Subversion 自己的数据类型,你会看到许多 apr 开头的数据类型引用—来自 Apache 可移植运行库 (APR) 的对象。APR 是 Apache 可移植运行库,源自为了服务器代码的多平台性,尝试将不同的操作系统特定字节与操作系统无关代码隔离。结果就提供了一个基础 API 的库,只有一些适度区别—或者是广泛的—来自各个操作系统。Apache HTTP 服务器很明显是 APR 库的第一个用户,Subversion 开发者立刻发现了使用 APR 库的价值。意味着 Subversion 没有操作系统特定的代码,也意味着 Subversion 客户端可以在 Server 存在的平台编译和运行。当前这个列表包括,各种类型的 Unix、Win32、OS/ 2 和 Mac OS X。

除了提供了跨平台一致的系统调用,[51]APR 给 Subversion 对多种数据类型有快速的访问,如动态数组和哈希表。Subversion 在代码中广泛使用这些类型,但是 Subversion 的 API 原型中最常见的 APR 类型是 apr_pool_t—APR 内存池,Subversion 使用内部缓冲池用来进行内存分配(除非外部库在 API 传递参数时需要一个不同的内存管理模式),[52] 而且一个人如果针对 Subversion 的 API 编码不需要做同样的事情,他们可以在需要时给 API 提供缓冲池,这意味着 Subversion 的 API 使用者也必须链接到 APR,必须调用 apr_initialize() 来初始化 APR 子系统,而且在使用 Subversion API 时必须创建和管理池,通常是使用 svn_pool_create()svn_pool_clear()svn_pool_destroy()

URL 和路径需求

因为分布式版本控制操作是 Subversion 存在的重点,有意义来关注一下国际化 (i18n) 支持。毕竟,当“分布式 ”或许意味着“ 横跨办公室 ”,它也意味着“ 横跨全球”。为了更容易一点,Subversion 的所有公共接口只接受路径参数, 这些参数是传统的,使用 UTF- 8 编码。这意味着,举个例子,任何新的使用 libsvn_client 接口客户端库,在把这些参数传递给 Subversion 库前,需要首先将路径从本地代码转化为 UTF- 8 代码,然后将 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 的编码和解码。

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

除 C 语言以外,如果你对使用其他语言结合 Subversion 库感兴趣—如 Python 脚本或是 Java 应用—Subversion 通过简单包裹生成器 (SWIG) 提供了最初的支持。Subversion 的 SWIG 绑定位于subversion/bindings/swig,并且慢慢的走向成熟进入可用状态。这个绑定允许你直接调用 Subversion 的 API 方法,使用包裹器会把脚本数据类型转化为 Subversion 需要的 C 语言库类型。

非常不幸,Subversion 的语言绑定缺乏对核心 Subversion 模块的关注,但是,花了很多力气处理创建针对 Python、Perl 和 Ruby 的功能绑定,在一定程度上,在这些接口上的工作量可以在其他语言的 SWIG(包括 C#、Guile、Java、MzScheme、OCaml、PHP、Tcl 等等)接口上得到重用。然而,为了完成复杂的 API,一些 SWIG 接口仍然需要额外的编程工作,关于 SWIG 本身的更多信息可以看项目的网站http://www.swig.org/

Subversion 也有 Java 的语言绑定,JavaJL 绑定 (位于 Subversion 源目录树的subversion/bindings/java) 不是基于 SWIG 的,而是 javah 和手写 JNI 的混合,JavaHL 几乎覆盖 Subversion 客户端的 API,目标是作为 Java 基础的 Subversion 客户端和集成 IDE 的实现。

Subversion 的语言绑定缺乏 Subversion 核心模块的关注,但是通常可以作为一个产品信赖。大量脚本、应用、Subversion 的 GUI 客户端和其他第三方工具现在已经成功地运用了 Subversion 语言绑定来完成 Subversion 的集成。

这里使用其它语言的方法来与 Subversion 交互没有任何意义:Subversion 开发社区没有提供其他的绑定,你可以在 Subversion 项目链接页里 (http://subversion.tigris.org/links.html) 找到其他绑定的链接,但是有一些流行的绑定我觉得应该特别留意。首先是 Python 的流行绑定,Barry Scott 的 PySVN(http://pysvn.tigris.org/)。PySVN 鼓吹它们提供了更多 Python 样式的接口,而不像 Subversion 自己的 Python 绑定的 C 样式接口。对于希望寻求 Subversion 纯 Java 实现的人,可以看看 SVNKit(http://svnkit.com/),也就是从头使用 Java 编写的 Subversion。你必须要小心,SVNKit 没有采用 Subversion 的核心库,其行为方式没有确保与 Subversion 匹配。

代码样例

例 8.1“使用版本库层”包含了一段 C 代码 (C 编写) 描述了我们讨论的概念,它使用了版本库和文件系统接口 (可以通过方法名svn_repos_svn_fs_分辨)创建了一个添加目录的修订版本。你可以看到 APR 库的使用,为了内存分配而传递,这些代码也揭开了一些关于 Subversion 错误处理的晦涩事实—所有的 Subversion 错误必须需要明确的处理以防止内存泄露(在某些情况下,应用失败)。

例 8.1. 使用版本库层

/* Convert a Subversion error into a simple boolean error code.
 *
 * NOTE:  Subversion errors must be cleared (using svn_error_clear())
 *        because they are allocated from the global pool, else memory
 *        leaking occurs.
 */
#define INT_ERR(expr)                           \
  do {                                          \
    svn_error_t *__temperr = (expr);            \
    if (__temperr)                              \
      {                                         \
        svn_error_clear(__temperr);             \
        return 1;                               \
      }                                         \
    return 0;                                   \
  } while (0)

/* Create a new directory at the path NEW_DIRECTORY in the Subversion
 * repository located at REPOS_PATH.  Perform all memory allocation in
 * POOL.  This function will create a new revision for the addition of
 * NEW_DIRECTORY.  Return zero if the operation completes
 * successfully, non-zero otherwise.
 */
static int
make_new_directory(const char *repos_path,
                   const char *new_directory,
                   apr_pool_t *pool)
{
  svn_error_t *err;
  svn_repos_t *repos;
  svn_fs_t *fs;
  svn_revnum_t youngest_rev;
  svn_fs_txn_t *txn;
  svn_fs_root_t *txn_root;
  const char *conflict_str;

  /* Open the repository located at REPOS_PATH. 
   */
  INT_ERR(svn_repos_open(&repos, repos_path, pool));

  /* Get a pointer to the filesystem object that is stored in REPOS. 
   */
  fs = svn_repos_fs(repos);

  /* Ask the filesystem to tell us the youngest revision that
   * currently exists. 
   */
  INT_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));

  /* Begin a new transaction that is based on YOUNGEST_REV.  We are
   * less likely to have our later commit rejected as conflicting if we
   * always try to make our changes against a copy of the latest snapshot
   * of the filesystem tree. 
   */
  INT_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool));

  /* Now that we have started a new Subversion transaction, get a root
   * object that represents that transaction. 
   */
  INT_ERR(svn_fs_txn_root(&txn_root, txn, pool));
  
  /* Create our new directory under the transaction root, at the path
   * NEW_DIRECTORY. 
   */
  INT_ERR(svn_fs_make_dir(txn_root, new_directory, pool));

  /* Commit the transaction, creating a new revision of the filesystem
   * which includes our added directory path.
   */
  err = svn_repos_fs_commit_txn(&conflict_str, repos, 
                                &youngest_rev, txn, pool);
  if (! err)
    {
      /* No error?  Excellent!  Print a brief report of our success.
       */
      printf("Directory'%s'was successfully added as new revision"
             "'%ld'.\n", new_directory, youngest_rev);
    }
  else if (err->apr_err == SVN_ERR_FS_CONFLICT)
    {
      /* Uh-oh.  Our commit failed as the result of a conflict
       * (someone else seems to have made changes to the same area 
       * of the filesystem that we tried to modify).  Print an error
       * message.
       */
      printf("A conflict occurred at path'%s'while attempting"
             "to add directory'%s'to the repository at'%s'.\n", 
             conflict_str, new_directory, repos_path);
    }
  else
    {
      /* Some other error has occurred.  Print an error message.
       */
      printf("An error occurred while attempting to add directory'%s'""to the repository at '%s'.\n", 
             new_directory, repos_path);
    }

  INT_ERR(err);
}


请注意在例 8.1“使用版本库层”中,代码可以非常容易使用 svn_fs_commit_txn() 提交事务。但是文件系统的 API 对版本库库的钩子一无所知,如果你希望你的 Subversion 版本库在每次提交一个事务时自动执行一些非 Subversion 的任务 (例如,给开发者邮件组发送一个描述事务修改的邮件),你需要使用 libsvn_repos 包裹的功能版本—这个功能会实际上首先运行一个如果存在的pre-commit 钩子脚本,然后提交事务,最后会运行一个 post-commit 钩子脚本。钩子提供了一种特别的报告机制,不是真的属于核心文件系统库本身。(关于 Subversion 版本库钩子的更多信息,见“实现版本库钩子”一节。)

现在我们转换一下语言,例 8.2“使用 Python 处理版本库层”使用 Subversion SWIG 的 Python 绑定实现了从版本库取得最新的版本,并且打印了取出时访问的目录。

例 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):
    """Recursively crawl DIRECTORY under ROOT in the filesystem, and return
    a list of all the paths at or below DIRECTORY."""

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

    # Loop over the entries.
    names = entries.keys()
    for name in names:
        # 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):
            crawl_filesystem_dir(root, full_path)
        else:
            # Else it's a file, so print its path here.
            print full_path

def crawl_youngest(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)
    fs_obj = svn.repos.svn_repos_fs(repos_obj)

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

    # Do the recursive crawl.
    crawl_filesystem_dir(root_obj, "")
    
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 the repository path.
    repos_path = svn.core.svn_path_canonicalize(sys.argv[1])

    # Do the real work.
    crawl_youngest(repos_path)


同样的 C 程序需要处理 APR 内存池系统,但是 Python 自己处理内存,Subversion 的 Python 绑定也遵循这种习惯。在 C 语言中,为表示路径和条目的 hash 需要处理自定义的数据类型 (例如 APR 提供的库),但是 Python 有 hash(叫做“dictionaries”),并且是内置数据类型,而且还提供了一系列操作这些类型的函数,所以 SWIG(通过 Subversion 的语言绑定层的自定义帮助) 要小心的将这些自定义数据类型映射到目标语言的数据类型,这为目标语言的用户提供了一个更加直观的接口。

Subversion 的 Python 绑定也可以用来进行工作拷贝的操作,在本章前面的小节中,我们提到过 libsvn_client 接口,它存在的目的就是简化编写 Subversion 客户端的难度,例 8.3“一个 Python 状态爬虫”是一个例子,讲的是如何使用 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."""code_map = { svn.wc.svn_wc_status_none        :' ',
                 svn.wc.svn_wc_status_normal      : ' ',
                 svn.wc.svn_wc_status_added       : 'A',
                 svn.wc.svn_wc_status_missing     : '!',
                 svn.wc.svn_wc_status_incomplete  : '!',
                 svn.wc.svn_wc_status_deleted     : 'D',
                 svn.wc.svn_wc_status_replaced    : 'R',
                 svn.wc.svn_wc_status_modified    : 'M',
                 svn.wc.svn_wc_status_merged      : 'G',
                 svn.wc.svn_wc_status_conflicted  : 'C',
                 svn.wc.svn_wc_status_obstructed  : '~',
                 svn.wc.svn_wc_status_ignored     : 'I',
                 svn.wc.svn_wc_status_external    : 'X',
                 svn.wc.svn_wc_status_unversioned : '?',
               }
    return code_map.get(status, '?')

def do_status(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)

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 the repository path.
    wc_path = svn.core.svn_path_canonicalize(args[0])

    # Do the real work.
    try:
        do_status(wc_path, verbose)
    except svn.core.SubversionException, e:
        sys.stderr.write("Error (%d): %s\n" % (e[1], e[0]))
        sys.exit(1)


就像例 8.2“使用 Python 处理版本库层”中的例子,这个程序是池自由的,而且最重要的是使用 Python 的数据类型。svn_client_ctx_t()是欺骗,因为 Subversion 的 API 没有这个方法—这仅仅是 SWIG 自动语言生成中的一点问题 (这是对应复杂 C 结构的一种工厂方法)。也需要注意传递给程序的路径(象最后一个) 是通过 svn_path_canonicalize()执行的,因为要防止触发 Subversion 底层 C 库的断言,也就是防止导致程序立刻随意退出。

正文完
星哥说事-微信公众号
post-qrcode
 
星锅
版权声明:本站原创文章,由 星锅 2022-01-20发表,共计10866字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中