| 
                           
                            | 
								
										
                                    | 编辑推荐: |  
										
                                    | 本文来自写乐的博客,本文主要从使用处理版本库介绍了使用Subversion库API开发应用。 |  |   使用 API 
                            使用Subversion库API开发应用看起来相当的直接,所有的公共头文件放在源文件的subversion/include目录,从源代码编译和安装Subversion本身,需要这些头文件拷贝到系统位置。这些头文件包括了所有用户和Subversion库可以访问的功能和类型。Subversion开发者社区仔细的确保所有的公共API有完好的文档—直接引用头文件的文档。 
                            你首先应该注意Subversion的数据类型和方法是命名空间保护的,每一个公共Subversion对象名以svn_开头,然后紧跟一个这个对象定义(如wc、client和fs等等)所在的库的简短编码,然后是一个下划线(_)和后面的对象名称。半公开的方法(库使用,但是但库之外代码不可以使用并且只可以在库自己的目录看到)与这个命名模式不同,并不是库代码之后紧跟一个下划线,他们是用两个下划线(__)。给定源文件的私有方法没有特殊前缀,使用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()。 
                            使用内存池编程 
                            几乎每一个使用过C语言的开发者曾经感叹令人畏缩的内存管理,分配足够的内存,并且追踪内存的分配,在不需要时释放内存—这个任务会非常复杂。当然,如果没有正确地做到这一点会导致程序毁掉自己,或者更加严重一点,把电脑搞瘫。 
                            另一方面高级语言使开发者完全摆脱了内存管理,[53]Java和Python之类的语言使用垃圾收集原理,在需要的时候分配对象内存,在不使用时进行清理。 
                            APR提供了一种叫做池基础的中等的内存管理方法,允许开发者以一种低分辨率的方式控制内存—每块(或池“pool”)的内存,而不是每个对象。不是使用malloc()和其他按照对象分配内存的方式,你要求APR从内存创建一段内存池,当你结束使用在池中创建的对象,你销毁池,可以有效地取消其中的对象消耗的内存。通过池,你不需要跟踪每个对象的内存释放,你的程序只需要跟踪这些对象,将对象分配到池中,而池的生命周期(池的创建和删除之间的时间)满足所有对象的需要。 
                            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. 使用版本库层 
                           
                              | #define 
                                  INT_ERR(expr) \ do { \svn_error_t *__temperr = (expr); \
 if (__temperr) \
 { \
 svn_error_clear(__temperr); \
 return 1; \
 } \
 return 0; \
 } while (0)
 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;
 INT_ERR(svn_repos_open(&repos, repos_path, 
                                  pool));
 fs = svn_repos_fs(repos);
 INT_ERR(svn_fs_youngest_rev(&youngest_rev, 
                                  fs, pool));
 INT_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, 
                                  pool));
 INT_ERR(svn_fs_txn_root(&txn_root, txn, 
                                  pool));
 INT_ERR(svn_fs_make_dir(txn_root, new_directory, 
                                  pool));
 err = svn_repos_fs_commit_txn(&conflict_str, 
                                  repos,
 &youngest_rev, txn, pool);
 if (! err)
 {
 printf("Directory '%s' was successfully 
                                  added as new revision "
 "'%ld'.\n", new_directory, youngest_rev);
 }
 else if (err->apr_err == SVN_ERR_FS_CONFLICT)
 {
 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
 {
 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库的断言,也就是防止导致程序立刻随意退出。   
                           |