您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 Code iProcess 课程 认证 咨询 工具 火云堂 讲座吧   成长之路  
会员   
 
   
 
  
每天15篇文章
不仅获得谋生技能
更可以追随信仰
 
     
   
 订阅
  捐助
Subversion API 函数和开发思路介绍
 
329 次浏览     评价:  
 2018-1-29  
 
编辑推荐:
本文来自csdns,本章是针对那些打算直接利用这个软件的源代码帮助这个软件发展的人。

SubVersion开发信息

SubVersion是一个开源项目,它的财政支持来自CollabNet Inc,这是位于加州的一个软件公司。本项目欢迎程序员加入开发,鼓励您为它做点事情,无论是出点主意,还是修正Bug,或者是提炼代码。

本章是针对那些打算直接利用这个软件的源代码帮助这个软件发展的人。我们会在这里公布一些该软件的内部细节,这些技术细节对你继续开发subversion有帮助,也可以让你利用SubVersion的库写出全新的工具。

分层设计的库

Subversion采用模块化设计,是一组C库的集合。每个库都有定义良好的接口和Purpose,而且大多数模块都处于三个主要的层之一中,这三个层分别是:存储层(Repositiry layer)、存储访问层(Repository Access layer)和客户层(Client Layer)。在学习这些层之前,先学习下表中列出的库的详细目录。为了保持一致性,这里使用它们的Unix 库名称(如:libsvc_fs , libsvn_wc, mod_dav_svn)。

表:subversion库的目录

在上面的表中,单词"miscellaneous"只出现了一次是个好事情。Subversion的开发团队很严谨的让各种功能放合适的层和库中。模块化设计最大的好处是,从开发者的角度看,它不复杂。作为一个开发者,你可以很快地找到你所需要的功能的位置。下面的图表表示,Subversion库是怎样组装在一起的。

图表:Subversion的层的结构

使用模块化设计的另外一个好处是:我们可以用一个全新的库去替换一个指定的模块,在不影响其它的代码的情况下,实现相同的API。其实这种情况已经发生过了。Libsvn_ra_dav, libsvn_ra_local 和实现的是相同的接口。三个库都与存储层通信,其中Libsvn_ra_dav和libsvn_ra_svn通过网络通信,而libsvn_ra_local则是直接连接。

Subversion的客户端的设计也是高度模块化的。目前已经可以使用的客户端只是个基于命令行的程序,但是有很多第三方已经在开发GUI程序了。这些客户端都是使用着相同的API函数,对于大多数的客户端程序设计来说,libsvn_client一个库就足够了。

存储层

与存储层有关的是两个库,一个是repository库,一个是filesystem库。这些库提供了存储和那些版本控制数据的修订报告。这个层是通过存储访问层连接client层的,而且从用户的角度来看,素材来自网络的另外一端。

Subversion文件系统可以通过libsvn_fs API函数访问,这个文件系统其实是一个虚拟的文件系统,不是那种在操作系统中使用的真正的文件系统。在这里所谓的存储,不是真的创建一个文件或者目录,相反,它是使用一个数据库系统作为后台。当前使用的数据库系统是BDB。但是随着以后的开发,它将与其他的数据库系统兼容,也许还可以通过ODBC访问。

这个文件系统的API函数提供了在大多数的文件系统中能够使用的功能:你可以创建、移动文件和目录,复制和移动它们,编辑文件的内容等等。某些功能还需要继续完善,比如:添加、删除和移动文件或文件夹的原数据(属性)。此外,这个文件系统还有版本控制功能,就是说,当你改变了目录树以后,它能够记忆在更动以前,或者以前以前的目录树的样子—一直到最初的样子。

你对你的目录树的任何更动都是在一个Subversion事务中完成的。下面是一个简化的例子,用于描述如何编辑你的文件系统:

1、 Begin:启动一个Subversion事务;

2、 进行修改(添加、删除、属性编辑等);

3、 Commit:提交事务。

一旦你提交了事务,对文件系统的修改将永远地存储在historical artifacts中。每次这样的操作都会形成一个单一的新的目录树的修正版本,而且每个版本都有一个永远存在的snapshot,这样你就总是可以访问到它们。

有关事务的解释

在靠近数据库代码的libsvn_fs库中使用事务,特别容易将它与数据库系统本身使用的事务混淆。这两种事务都支持原子操作和事务隔离。换句话说,使用事务,可以让你的一组操作要么全部成功,要么全部失败,如果失败,就像什么事情也没有发生过一样—这样做,也不会影响到其它过程对数据的操作。

数据库事务通常只处理针对数据库自身一些小的操作。而Subversion的事务的范围要大一些,它处理一些高层次的操作,像修改一个目录和文件的集合,并生成文件系统库新的版本。如果你还没有明白,就这样考虑,在使用数据库事务前,需要先创建Subversion事务(这样,如果Sunversion事务创建失败,数据库根本就不会创建任何事务)。

对用户来说幸运的是:在文件系统的API函数中,数据库系统自己的事务已经几乎完全被隐藏了。只有在你深入挖掘文件系统具体的实现的时候,它才有意义。

与一般的文件系统相似,你要使用Subversion的文件系统,访问或者描述一个修正版本的文件或目录的时候,同样是使用路径字符串,像/foo/bar这样,就像你在某个经常使用中的shell程序中进行的操作一样。通过给恰当的API函数传递文件或目录的名称,你就可以创建一个新的文件或目录。查询信息的时候也是一样。

与大多数的文件系统不同的是,仅仅一个路径并不能提供足够的描述一个文件或者文件夹的信息。普通的文件夹结构是下面这样的(以一个两层的结构为例):

图表:2层结构的目录结构

Subversion的文件系统有一个属于自己的属性,那是其他的文件系统所没有的,这就是“时间”。在这个文件系统的接口,几乎每个有path参数的函数,都同时需要一个root参数。这个svn_fs_root_t参数即描述了一个修订版本也描述了一个事务,并且提供了关于版本32的/foo/bar与版本98的/foo/bar的不同。下面的图表显示了这个特性:

图表:修订时间,Subversion的特性

正如前面所说的,lib_svn的API函数与其他的文件系统很相似,只是它有很有趣的版本控制能力。使用这些API函数已经能够满足基本的对文件和目录版本控制的要求,尽管如此,SubVersion还是需要更多的功能,这样就引入了libsvn_repos库。

Libsvn_repos基本上就是文件系统函数库的封装。这个库负责创建仓储层,确保文件系统是否初始化等等。这个库也提供一些钩子脚本,这些脚本在某些行为发生的时候,被存储代码执行。这些脚本可以用于通知、授权,以及仓库管理员想要做的其他事情。这些功能与版本控制系统并没有直接的关系,所以它们被放置在单独的库中。

使用libsvn_repos库的开发人员会发现,它并没有完全封装文件系统的全部接口。只有一些主要的文件系统的活动才会被存储接口封装。其中包括创建和提交Subversion事务,以及对修订版本的修改。在以后,剩余的事件也将被封装起来。没有封装的还是要直接调用libsvn_fs API函数。

下面是一个代码片断,它同时使用了存储和文件系统接口,通过增加一个目录,创建了一个新的文件系统修订版本。注意,在这个例子里面(包括以后所有的例子),SVN_ERR宏简单地检测来自某个函数的未成功的错误,如果存在错误就返回它。

例:使用存储层

/* 在REPOS_PATH目录中(Subversion仓库目录),创建一个新的目录NEW_DIRECTORY。在POOL中进行所有的内存分配。这个函数会创建一个增加了NEW_DIRECTORY的修正版本。*/

/* 在REPOS_PATH目录中(Subversion仓库目录),创建一个新的目录NEW_DIRECTORY。在POOL中进行所有的内存分配。这个函数会创建一个增加了NEW_DIRECTORY的修正版本。*/
static svn_error_t * 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;

/* 打开仓库中 REPOS_PATH目录 */
SVN_ERR (svn_repos_open (&repos, repos_path, pool));

/* 获取存储在REPOS中的文件系统对象的指针*/
fs = svn_repos_fs (repos);

/*查询文件系统,获得当前存在的最新的修订版本*/
SVN_ERR (svn_fs_youngest_rev (&youngest_rev, fs, pool));

/* 基于YOUNGEST_REV启动一个事务。因为我们是基于文件系统最新的Snapshot,因此在后面提交事务的时候,发生冲突的可能性就少很多*/
SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, pool));

/* 现在,我们已经启动了一个新的Subversion事务,先获得一个表示这个事务的root对象 */
SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool));

/*在事务的Root下创建一个新的目录 NEW_DIRECTORY */
SVN_ERR (svn_fs_make_dir (txn_root, new_directory, pool));

/* 提交这个事务,创建一个包含了我们新添加的目录的新的修正版本*/
err = svn_repos_fs_commit_txn (&conflict_str, repos,
&youngest_rev, txn);
if (! err)
{
/* 没有错误?很好,输出一个简要的报告*/
printf ("Directory '%s' was successfully added as new revision "
"'%" SVN_REVNUM_T_FMT "'./n", new_directory, youngest_rev);
}
else if (err->apr_err == SVN_ERR_FS_CONFLICT))
{
/* Uh-oh. 因为发生了某个冲突,我们的提交失败了(我们正在更改的区域,可能有别的人先更改了)。输出错误信息。 */
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);
}

/* 返回结果 */
return err;
}

在前面的代码片断中,我们调用了repository和filesystem的接口。我们可以简单地使用svn_fs_commit_txn提交一个事务,但是,文件系统的API并不知道repository库的hook机制。如果你想同时自动执行一些与Subversion无关的任务(例如发送一个邮件给别人,告诉他们你做的改动),那么,就必须使用libsvn_repos封装的提交函数—svn_repos_fs_commit_txn。这个函数首先会执行一个“pre-commit”hook脚本(如果有的话),然后提交事务,最后会执行一个“post-commit”hook脚本。这个hook提供了一种并不真正属于核心的文件系统库的报告机制。(参阅HOOK Script章节)

Hook机制是将repository库从filesystem代码中分离出来的原因之一。Libsvn_repos API函数还提供了其它几个重要的功能,其中包括:

1. 在Subversion repository和包含在repository中的filesysytem中,创建、打开、销毁和执行recovery steps。

2. 描述在两个文件系统树中的不同。

3. 查询所有存在文件被编辑的修订版本的提交日志的信息。

4. 产生一个可读的文件系统的“dump”,这是在文件系统中修订版本的完整描述。

5. 解析dump的格式,并将一个“dump”出来的修正版本导入到一个不同的Subversion repositiry中。

Repository 访问层

如果Subversion Repository层是在“line的底端”,那么Repository Access层就是line本身。它负责负载着在client库和Repository之间的数据。这个层包含了libsvn_ra模块导入库,RA模块本身(当前包括libsvn_ra_dav, libsvn_ra_local,和libsvn_ra_svn库),以及被这些RA模块所需要的其它的库,像mod_dav_svn ,或者libsvn_ra_svn的服务svnserve。

Subversion使用URL标示存储资源,因此,协议的URL架构部分(通常是file:、http:、https:、或svn:)用于确定用哪个RA模块处理通信。每个模块都注册了一个它所知道如何“talk”的协议列表,因此,RA loader可以在运行时确定究竟使用哪个模块处理任务。你可以执行SVN—version,确定哪个RA模块在Subversion命令行客户端可用,以及它们都支持什么协议(例子如下):

$ svn --version
svn, version 0.25.0 (dev build)
compiled Jul 18 2003, 16:25:59
Copyright (C) 2000-2003 CollabNet.
Subversion is open source software, see http://subversion.tigris.org/
The following repository access (RA) modules are available:
* ra_dav : Module for accessing a repository via WebDAV (DeltaV) protocol.
- handles 'http' schema
* ra_local : Module for accessing a repository on local disk.
- handles 'file' schema
* ra_svn : Module for accessing a repository using the svn network protocol.
- handles 'svn' schema

RA-DAV(使用HTTP/DAV的Rrpository Access)

当客户端机器运行在与服务器不同的机器上,使用包含着http:或https:协议的URL机制进行通信的时候,可以使用这个库。要明白这个模块如何工作,我们必须先说明其它的一些在Repository Access层中配置的关键部分—强大的Apache HTTP 服务器和Neon HTTP/WebDAV client库。

Subversion主要的网络服务是Apache HTTP服务。Apache是一个久经考验的,可扩展的源代码开放服务器。它可以承受很高的网络负荷,并且可以运行在多个平台上。Apache服务器支持很多的标准的验证协议,并且可以通过加载其它的模块进行扩展。它也支持网络pipeling和缓存优化。通过使用Apache服务器,Subversion获得了所有的这些性能。另外,由于大多数的防火墙是允许Http协议的,因此,系统管理员不必为了让Subversion工作就更改防火墙的配置。

Subversion使用HTTP和WebDAV(与DeltaV一起)与Apache服务器通信。在本章的WebDAV节中有关于它的详细解释,简单地说,WebDAV和DeltaV是标准HTTP1.1版本的扩展,可以支持Web上文件的共享和传输。Apache2.0本身有mod_dav模块,这个模块可以理解HTTP的DAV扩展。Subversion本身支持mod_dav_svn模块,它是另外一个Apache模块,作为后台与mod_dav协同工作,以便支持WebDAV和DeltaV的特殊操作。

当通过HTTP访问Repository的时候,RA Loader库选择libsvn_ra_dav作为访问模块。Subversion客户端调用通用的RA接口,libsvn_ra_dav将这些调用影射为一组HTTP/WebDAV request。通过使用Neon库,libsvn_ra_dav将这些request传输到Apache服务器。Apache接收到以后,发现这些request定位的URL配置为DAV location(在http.conf中使用Location指示),就会将这些request剥离自己的mod_dav模块。如果配置是正确的,mod_dav会使用Subversion自己的mod_dav_svn处理相关的文件系统需求,而不是使用Apache中自带的通用mod_dav_fs。最后的结果是:客户端直接与mod_dav_svn通信,而后者是直接绑定在Subversion Repository层上的。

这是一个实际发生的事情的简要介绍。例如:Subversion Repository可能不被Apache的权限系统所保护。这样,在客户端连接的时候,可能会被Apache的权限系统所拒绝。这个时候,libsvn_ra_dav会从Apache获取权限不足的信息,返回客户端获取更新的授权数据。如果能够正确提供数据,用户就具有了查询Apache的权限,libsvn_ra_dav下一步自动尝试执行的命令将被接收,一切OK。如果权限信息不充分,request最终将会失败,客户端将会给用户报告错误。

通过使用Non和Apache,Subversion在一些复杂的领域也有充分的处理能力。比如,如果Neon发现OpenSSL库,它将允许客户端使用SSL加密协议与Apache服务器通信(Apache服务器自己有mod_ssl“认识这个语言”)。另外,Neon本身和Apache的mod_deflate都能够理解“deflate”算法,因此可以压缩request,使传输的数据更小。以后还将支持重定向服务。

RA-SVN(专用得Respository Access协议)

作为标准的HTTP/WebDAV协议的补充,Subvesion还提供了一个专用的协议。Libsvn_ra_svn模块使用自己的Socket,并且与一个独立的服务器通信(svnServer程序),这个服务器与Repository在一个主机上。客户端使用svn://结构访问Repository。

这个机制缺乏上面一节中使用Apache服务器的大部分的优点,但是,它对某些系统管理员来说可能有一定的吸引力。它很容易配置并运行,安装一个svnServer进程速度很快。相比于Apache来说,它也小很多,很容易检查。另外,一些系统管理员已经有了自己的SSH底层结构,而且希望Subversion使用它。使用ra_svn的客户端很容易调整为基于SSH协议。

RA-Local(直接访问Respository)

不是所有对Repository的通信都需要一个服务器进程和一个网络层。对于只想在本地磁盘上访问Repository的用户,只需要使用file:URL格式,以及libsvn_ra_local库提供的功能。这个模块直接绑定了Repository和filesystem库,因此完全不需要网络通信。

Subversion要求在file:中包含的服务器名称或者是空,或者是localhost,而且不需要通信端口。你的URL应该看上去是这样的:file://localhost/path/to/repos或者file:///path/to/repos。

需要注意的是,file:这种URL不能在正规的web浏览器中使用,否则浏览器将直接在本地的文件系统中查询这个文件,但是,Subversion的文件系统实际上是一个虚拟的文件系统,你的浏览器并不能理解这个系统。

你自己的RA库

如果你还想用另外一个协议,那正好。这就是为什么要设计一个Repository Access层的原因!开发人员可以重新设计一个网络层,一端实现RA接口,另外一端实现与Repository的通信。新的库可以使用现成的协议,你也可以自己发明一个。你可以用IPC或者let’s get crazy, shall we? 设置你可以设计一个基于email的协议。Subversion提供API函数,而你负责提供创造力。

Client层

在客户端,客户端库的大多数功能是用来管理工作版本,包括容纳文件的目录和作为本地服务存在的子目录,可以编辑Repository的本地“影像”,并且反映来自/发送到Repository Access层的改变。

Subversion的工作版本库—libsvn_wc,它的责任就是管理工作版本的数据。为了完成这个功能,这个库在一个特定的子目录中存储了所有的工作版本目录的管理信息。这个子目录的名称是.svn,存放在每个工作版本的目录内,而且包含了一些文件和子目录,用于记录状态和提供一个私有的用于管理的工作空间。与CVS很相似的是,在CVS工作目录中也有一个CVS目录。

Subversion的客户端库,libsvn_client的功能非常广泛;它混合了工作版本库与Repository Access层的功能,然后提供了最高层的API函数,可以用于任何想执行修订版本控制功能的应用程序。例如:函数svn_clinet_checkout有一个URL参数。它将一个URL传递到RA层,并且打开针对特定的Repository的authenticated session。然后它向Repository查询某个特定的树,并将这个树发送到工作版本库,工作版本库将把整个的工作版本写入到磁盘上。

客户端库的设计目的就是用于应用程序。虽然Subversion源代码中包含一个基于命令行的客户端,但是在客户端库的基础上设计一个GUI客户端是很容易的。新的GUIs没有必要封装或包含命令行的客户端,它可以使用libsvn_client