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

Subversion API 使用简介

436次阅读
没有评论

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

星哥玩云

星哥玩云
星哥玩云
分享互联网知识
用户数
4
文章数
19351
评论数
4
阅读量
7988958
文章搜索
热门文章
星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛 NAS-6:抖音视频同步工具,视频下载自动下载保存 前言 各位玩 NAS 的朋友好,我是星哥!...
星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛 NAS-3:安装飞牛 NAS 后的很有必要的操作 前言 如果你已经有了飞牛 NAS 系统,之前...
我把用了20年的360安全卫士卸载了

我把用了20年的360安全卫士卸载了

我把用了 20 年的 360 安全卫士卸载了 是的,正如标题你看到的。 原因 偷摸安装自家的软件 莫名其妙安装...
再见zabbix!轻量级自建服务器监控神器在Linux 的完整部署指南

再见zabbix!轻量级自建服务器监控神器在Linux 的完整部署指南

再见 zabbix!轻量级自建服务器监控神器在 Linux 的完整部署指南 在日常运维中,服务器监控是绕不开的...
飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛 NAS 中安装 Navidrome 音乐文件中文标签乱码问题解决、安装 FntermX 终端 问题背景 ...
阿里云CDN
阿里云CDN-提高用户访问的响应速度和成功率
随机文章
每年0.99刀,拿下你的第一个顶级域名,详细注册使用

每年0.99刀,拿下你的第一个顶级域名,详细注册使用

每年 0.99 刀,拿下你的第一个顶级域名,详细注册使用 前言 作为长期折腾云服务、域名建站的老玩家,星哥一直...
恶意团伙利用 PHP-FPM 未授权访问漏洞发起大规模攻击

恶意团伙利用 PHP-FPM 未授权访问漏洞发起大规模攻击

恶意团伙利用 PHP-FPM 未授权访问漏洞发起大规模攻击 PHP-FPM(FastCGl Process M...
星哥带你玩飞牛NAS-13:自动追番、订阅下载 + 刮削,动漫党彻底解放双手!

星哥带你玩飞牛NAS-13:自动追番、订阅下载 + 刮削,动漫党彻底解放双手!

星哥带你玩飞牛 NAS-13:自动追番、订阅下载 + 刮削,动漫党彻底解放双手! 作为动漫爱好者,你是否还在为...
星哥带你玩飞牛NAS硬件03:五盘位+N5105+双网口的成品NAS值得入手吗

星哥带你玩飞牛NAS硬件03:五盘位+N5105+双网口的成品NAS值得入手吗

星哥带你玩飞牛 NAS 硬件 03:五盘位 +N5105+ 双网口的成品 NAS 值得入手吗 前言 大家好,我...
150元打造低成本NAS小钢炮,捡一块3865U工控板

150元打造低成本NAS小钢炮,捡一块3865U工控板

150 元打造低成本 NAS 小钢炮,捡一块 3865U 工控板 一块二手的熊猫 B3 工控板 3865U,搭...

免费图片视频管理工具让灵感库告别混乱

一言一句话
-「
手气不错
开发者福利:免费 .frii.site 子域名,一分钟申请即用

开发者福利:免费 .frii.site 子域名,一分钟申请即用

  开发者福利:免费 .frii.site 子域名,一分钟申请即用 前言 在学习 Web 开发、部署...
240 元左右!五盘位 NAS主机,7 代U硬解4K稳如狗,拓展性碾压同价位

240 元左右!五盘位 NAS主机,7 代U硬解4K稳如狗,拓展性碾压同价位

  240 元左右!五盘位 NAS 主机,7 代 U 硬解 4K 稳如狗,拓展性碾压同价位 在 NA...
小白也能看懂:什么是云服务器?腾讯云 vs 阿里云对比

小白也能看懂:什么是云服务器?腾讯云 vs 阿里云对比

小白也能看懂:什么是云服务器?腾讯云 vs 阿里云对比 星哥玩云,带你从小白到上云高手。今天咱们就来聊聊——什...
仅2MB大小!开源硬件监控工具:Win11 无缝适配,CPU、GPU、网速全维度掌控

仅2MB大小!开源硬件监控工具:Win11 无缝适配,CPU、GPU、网速全维度掌控

还在忍受动辄数百兆的“全家桶”监控软件?后台偷占资源、界面杂乱冗余,想查个 CPU 温度都要层层点选? 今天给...
浏览器自动化工具!开源 AI 浏览器助手让你效率翻倍

浏览器自动化工具!开源 AI 浏览器助手让你效率翻倍

浏览器自动化工具!开源 AI 浏览器助手让你效率翻倍 前言 在 AI 自动化快速发展的当下,浏览器早已不再只是...