本文详叙了WebDAV怎样用于Subversion。特别是,客户端怎么用Neon生成WebDAV请求,以及服务器端怎么将WebDAV请求转变成Subversion代码仓库的操作。这里的服务器端是一个作为mod_dav功能后端的Apache
2.0模块。
本文大量引用了 Subversion设计文档和最新的 Delta-V协议草案。这两份文档的详细内容就不在本文中重复了。
注意:Subversion使用DeltaV来进行通信,但是Subversion客户端并不是一个通用的DeltaV客户端。实际上,Subversion客户端依赖于服务器的一些专有特性。更进一步来说,Subversion服务器端也不是一个通用的DeltaV服务器。它只是实现了DeltaV协议的一个子集。当且仅当只使用Subversion服务器端实现的这个子集的功能时,WebDAV或者DeltaV客户端才能正常的与Subversion服务器端协作
Subversion的2.0版将会处理WebDAV协作性(包括与Class
1、Class 2和DeltaV)的问题。Subversion客户端需要的专有特性都会在DeltaV中提供一个替代方案,虽然替代方案不太高效。
Subversion的1.0版会支持只读的Class 1 WebDAV客户端。任何能够增强DeltaV协同性,且容易实现的功能都将被考虑。
基本概念
Subversion使用一个基于树的格式来描述版本库的变更集。这个树是在客户端(通过在工作副本中“行走”)被构造用来描述变更。这个树作为被应用于版本库一个线性序列的变更被编组到服务端。版本库可以以随机存取的方式接受变更,所以从树到一系列变化的映射对版本库来说非常容易。
Subversion为文件,目录甚至修订版本的抽象概念提供了属性。牵涉属性的每一个操作可以通过操作PROPFIND和PROPPATCH
HTTP方法直接映射到WebDAV的属性。修订版本被建模为DeltaV基线,所以修订版本的属性可以通过基线的PROPFIND来获得。
Subversion服务端能够有效的计算两个版本之间的增量(这些增量是完整的树增量,不是简单的文本增量)。Deltav没有直接模拟树增量概念。客户端可以通过在各种WebDAV资源上发送一系列PROPFIND请求发现变更,但是这个调用多个请求将是一个耗时的操作。作为替代,Subversion将这个概念整理为一个自定义的WebDAV报告。使用这个报告,客户端可以了解工作副本中的哪项过时并可以发送GET和PROPFIND方法获取新数据。
在Subversion中,分支和标签是简单的拷贝,是用WebDAV COPY处理的。
在某处需要讨论拷贝.需要讨论拷贝的历史如何被保存 (svn是自动处理的,
但是与其他服务端交互的时候可能需要我们在其他服务端设置一个自定义属性.
Subversion中使用的Deltav概念
Subversion使用了许多Deltav概念, 列表如下. 注意这些概念中有很多并没有被Subversion完全实现;
我们已经实现了足够的概念以满足我们的需要,但是比较少.
基线
活动
版本资源
版本控制配置
基线集合
版本控制的资源
工作资源(功能)
合并功能
标签功能
版本控制集合功能
Subversion项目表现为URL形式
第一个要定义的概念是项目是如何显示给客户端的。 在服务端Subversion将所有项目表现为URL的形式。
这个项目下的文件或子目录将通过URL命名空间被表现。
例如,假设我们有一个叫做“example”的项目。并且我们说,这个项目将被表现为这个URL:
http://subversion.tigris.org/repos/example/。
这个映射通过Apache HTTP Server(在问题中托管Subversion代码和特定项目)的一系列配置参数被设置。配置可能像下面这样:
<Location /repos/example> DAV svn SVNPath /home/svn-projects/example </Location> |
这个项目下的文件或目录将直接被映射到URL命名空间。例如,如果这个项目在一个子目录“sub”中包含一个文件“file.c”,那么那个文件的URL将是:
http://subversion.tigris.org/repos/example/sub/file.c。
初始检出
当用户执行一个Subversion项目的初始检出操作,客户端将发出一连串的PROPFIND和GET请求。这些请求将遍历版本库,获得一些所需的元数据,
然后获得最新的修订版本号.描述获取活动集合的OPTIONS请求。描述到达基线集合的一系列PROPFIND请求。
(从下面移到这里,需要被重写)
初始化检出操作被执行后, Subversion获取到DAV:活动集合给定值并在工作副本的每个目录中将其存储为属性。每个集合都有属性。属性列出了服务端活动被创建的位置。第一个位置被存储在客户端用来在提交过程中使用。
或许应该描述我们获取元数据后,检出“非最新的”(如日期或修订版本号)如何工作。
提交变更
Subversion提交使用Deltav的“活动”概念来建模。一个活动能够被看做是一次传输一系列资源。
创建活动
在提交的时候, Subversion客户端将读取存储的DAV:DAV:activity-collection-set用来了解应该在哪里创建活动。
接下来,客户端将为活动的位置生成一个UUID(惟一值) 。最后客户端发送一个MKACTIVITY方法请求,
请求的URL从活动位置和UUID被表现。zh
简短总结:
在检出时:
请求: OPTIONS for DAV:activity-collection-set响应:http://www.example.com/repos/foo/$svn/act/
在提交时:
请求: MKACTIVITY http://www.example.com/repos/foo/$svn/act/01234567-89ab-cdef-0123-456789abcdef
响应: 201 (Created)
CHECKOUT方法能够指定一个活动用来检出。 这个功能用于关联所有的条目和新创建的活动。
存储提交信息
检出基线,并对工作基线应用PROPPATCK方法。
将变更映射到WebDAV
Subversion的变更集用tree delta表示(查看SVN的设计细节以确定那些变更可以放进tree
delta)。tree delta会被拆成请求集,其中请求是以下几种形式之一:
1.删除文件或目录
这些变更被映射成DELETE操作。目标父集的版本资源使用CHECKOUT方法检出(到当前活动)。目标(名称)随后被DELETE方法从结果工作集删除。
2.添加文件
这种变更会映射为对目标父集的版本资源进行CHECKOUT操作。新文件被使用PUT请求创建于结果工作集。属性使用PROPPATCH设置。
3.添加目录
这种变更被映射为对目标父集的版本资源进行CHECKOUT操作。新目录被使用MKCOL请求创建于结果工作集。属性通过PROPPATCH设置。
4.添加指向前代祖先(副本)的文件或目录(这种文件或目录类似linux的文件链接?)
需要更新本节
tree delta可以指定文件或目录为其他文件或目录的一份副本。这个副本可被其他元素的tree
delta修改。
这种变更被映射为,对包含新资源的父集的版本资源进行CHECKOUT操作。VERSION-CONTROL方法会在当前工作集内创建新的版本控制资源(VCRs),VCR的DAV:checked-in属性指向其祖先的版本资源。
提示:看起来我们应该使用COPY把正确资源拷入当前工作集。这将会创建一个新的版本历史(随后被放入当前工作集),该版本历史使用DAV:precursor-set属性指定其祖先的版本资源。由于版本资源不指定修订版,不可能COPY一个版本资源到当前工作集,此举不会通知我们究竟是哪个修订版被拷贝。相反,我们最好从正确的基线中拷出版本资源。这表明,客户端可以把一个URL/修订版对映射为基线的版本资源URL。
第二个问题时,我们如何设置版本历史的 DAV:precursor-set属性。或者,更详细点,我们如何根据存储在仓库的信息提取该值。这仍需要研究。
5.用文件/目录替换另一个文件/目录
这种变更不能对应WebDAV的任何操作,因为tree delta会映射为两个连续的操作:先delete然后add
6.移动文件或目录
这种变更不能对应WebDAV的任何操作,因为tree delta会映射为两个不同的操作:先delete然后add前代祖先。
7.替换文件
映射为对目标的版本资源进行CHECKOUT操作,然后PUT到结果工作集。
8.替换目录
在Subversion的术语中,“替换目录”意味着目录内的添加、删除及其他操作。每个变更会单独映射,而且对目录的变更是隐式操作的。
9.属性delta
属性delta(与文件或目录相对)直接被映射为WebDAV术语中的PROPPATCH。目标的版本资源使用CHECKOUT检出,PROPPATCH会应用于结果工作集。
最终提交
最后的提交动作将会向Subversion服务器生成一个MERGE请求,指定检入的活动(之前创建),以及被更新的指向新版本资源的相应版本控制资源。
关于当前工作基线,以及如果用其创建新的基线(包含提交信息),下文的评论(?)并不正确。
版本控制资源也是基线控制的,即对它的更新会自动创建一个新基线。本质上,提交会相应于新的Subversion修订版创建一个新基线。
例子
警告: 本节没有更新到最新对SVN-to-DAV的映射的变更,在此警告移除前,暂认为本节已过期。
考虑下以下一组操作,以及相应的tree delta(摘自SVN的设计文档):
1.重命名/dir1/dir2为/dir1/dir4,
2.重命名/dir1/dir3为/dir1/dir2, 并且
3.把file3从/dir1/dir4移动到/dir1/dir2
<tree-delta> <replace name='dir1'> <directory> <tree-delta> <replace name='dir2'> <directory ancestor='/dir1/dir3'> (1) <tree-delta> <new name='file3'> (2) <file ancestor='/dir1/dir2/file3'/> </new> </tree-delta> </directory> </replace> <delete name='dir3'/> (3) <new name='dir4'> (4) <directory ancestor='/dir1/dir2'> <tree-delta> <delete name='file3'/> (5) </tree-delta> </directory> </new> </tree-delta> </directory> </replace> </tree-delta> |
遍历此delta,我们列出了对应的WebDAV请求。上文delta的数字编号大致相当于下面的列表项编号。这种对应关系并不十分准确,因为一个特定的、既成的行为通常基于delta中的一组元素。
1.<directory ancestor="/dir1/dir3">
指定我们用/dir1/dir3替换/dir1/dir2CHECKOUT /dir1/dir2/
(返回该目录的一个当前工作资源URL) COPY /dir1/dir3/
Destination: http://www.example.com/$svn/wrk/.../
Overwrite: T
2./dir1/dir2/file3是新的(因为我们刚刚重写了原始的dir2目录),并来源于/dir1/dir2/file3。因此,我们只需要COPY文件到目标目录的当前工作资源:COPY
/dir1/dir2/file3
Destination: http://www.example.com/$svn/wrk/.../file3
3.CHECKOUT /dir1/dir3/
(返回该目录的一个当前工作资源URL) DELETE /$svn/wrk/.../
4.我们将会在/dir1下创建新的子目录(dir4)。由于还没有检出/dir1,我们这样做:CHECKOUT
/dir1/
(返回该目录的一个当前工作资源URL) 现在我们把正确的目录复制到新的当前工作资源:COPY
/dir1/dir2/
Destination: http://www.example.com/$svn/wrk/.../dir4/
5.COPY在服务器上创建了完整的一组当前工作资源,我们只需删除不再需要的那部分:DELETE:
/$svn/wrk/.../dir4/file3
URL布局
Subversion服务器通过自定义的URL公开仓库。例如,foo仓库可能位于http://www.example.com/repos/foo/。但是,服务器还需要一组其他公开资源来保证正常操作。这些附加资源会与主仓库URL下的某位置下的所有仓库关联。默认,此位置为$svn,也可以通过SVNSpecialURI指令来设置:
<Location /repos/foo> DAV svn SVNPath /home/svn-projects/foo SVNSpecialURI .special </Location> |
在SVNSpecialURI指定的位置下,我们需要公开一些集合(collections)。假设我们使用默认的$svn,这些集合为:$svn/act
此区域是活动创建的位置。客户端需要选择在此集合内唯一的名字,然后向该URL发送一个MKACTIVITY。然后,客户端就可以使用该活动做进一步交互。$svn/act/内的资源不能使用方法.
提示: 实际上我们可以使用带Depth:1头的PROPFIND来允许客户端遍历当前所有活动。
只有一个方法子集可以操作该集合下的活动。它们是:PROPFIND, MERGE
(提交活动), and DELETE (终止活动). 根据Delta-V规范,所有的活动资源在DAV:activity下都有DAV:resourcetype属性。
$svn/his/本节需要改进;事实上,我们没有使用版本历史资源,将来,它们可能被设计成类似下面的模型:
此集合包含一个项目下的文件和目录的版本历史资源。其内部布局完全由服务器定义。客户端将通过多个响应获取此集合及其子集合的URL。$svn/his/内的资源不能使用方法。URL的命名空间被设计成如下的URL形式:
$svn/his/node-id
node-id是Subversion用来引用单独文件和目录的内部值,是由Subversion仓库定义的单个整数值。注意此node-id是非点状节点ID(undotted
node id ?),仓库中给定节点的全部历史的基础。node-id的DAV:resourcetype的集合是DAV:version-history.
提示: 上文信息可能不太准确。链接一个版本历史到其他版本历史的问题仍然存。进一步,我认为node
73 和 node 73.4.1是各自的版本历史(后者链接到了前者)。73.x 和73.4.1.x 是版本历史内的版本。
$svn/ver/
这个集合包含项目的版本资源。$svn/ver/内的资源不能使用方法。这个集合的布局由服务器定义。为了引用(以及描述实现)的需要,它被设计为:
$svn/ver/node-id/path
只有只读的方法才能用在这些资源上,如 GET, PROPFIND, REPORT;其他方法都是不合法的。版本资源的DAV:resourcetype是检入时资源的值。(如
<D:resourcetype/> 或 <D:resourcetype><D:collection/></D:resourcetype>).
$svn/wrk/
此集合包含所有通过CHECKOUT方法被检出的资源的当前工作资源。集合的形式和结构也由服务器定义,也是明确定义的,这样客户端才可以与被检出的集合资源进行适当交互。$svn/wrk/内的资源不能使用方法。为了引用目的,当前工作资源的URL被设计成:
$svn/wrk/activity/path
所有方法都能用在当前工作资源上,但是不能用在其父资源上。当前工作资源的DAV:resourcetype遵循一般资源命名方式:<D:resourcetype/>代表经常性的当前工作资源,<D:resourcetype><D:collection/></D:resourcetype>代表当前工作集。
$svn/vcc/
本节还不完整。 版本控制的配置(version-controlled configuration)...$svn/vcc/root是个单例(singleton)
$svn/bln/
本节还不完整。基线(baselines)... $svn/bln/rev/
$svn/wbl/
本节还不完整。 当前工作基线(working baseline)...
$svn/bc/
本节还不完整。. 基线集合(baseline collection)...
属性管理(以及历史/日志报告)
本节需要修订。属性发生在FS修订版中(通过基线公开)。
之前也提到过,Subversion的属性会映射到WebDAV的属性。对历史/日志报告,下面的WebDAV属性适用于每个基线(Subversion修订版)和每一个修订版创建的版本资源。由于这些资源都是版本资源,因此下面的属性都是只读的。
1.DAV:comment
用于指定检入评论的标准的(过期的)属性
2.DAV:creator-displayname
Subversion的“用户”一次特定修改后生成的(已过期的)属性
3.DAV:creationdate
提交时服务器生成的只读(未过期的)属性
一个特定文件的历史将会通过REPORT方法和DAV:property-report报告生成。一个典型的历史将会获取每个文件或目录版本的以上三个属性。
基于客户端的设计,指定关于版本信息的其他只读属性可能很重要。例如,一个文件检入时,有多少行增加/删除?生成这些属性将会相当直接,并会随着时间由客户端的设计推动。
提示: 如果我们这样做,我们将会把客户端捆绑于服务器。如果客户端与其他不报告这些属性的Delta服务器交互,我们简单地在UI中不显示它们就可以了(如优雅的功能退化,graceful
degradation of functionality)。
获取状态和更新
初始检出后,客户端就可以请求状态报告(客户端的变更,会挂起提交,服务器的变更,会挂起更新)。更新过程是类似的,但还会获取服务器上的变更。
本地的变更将全部由客户端处理。当前工作拷贝(the Working Copy)库会很容易地检测和报告这些变更。我们只需要关心,如何高效地检测服务器上的变更。
虽然可以遍历仓库,获取当前状态,然后与客户端状态进行比较,但是这样做不是高效的。Subvesion的设计允许服务器在获知客户端状态的情况下,轻松地计算出变更(相对于客户端)。
status和update命令的核心是基于自定义的、Subversion特定的WebDAV报告。这份自定义的报告将会通知服务器当前工作拷贝的状态,服务器的响应将会指定哪些资源需要被更新。
这种请求是个包含自定义XML结构的标准REPORT请求。这个结构体,将利用Subversion报告顶层修订版号的方法,只报告修订版不同的子节点。报告的响应也会使用相同的方式报告有变更的资源。如果有变更,服务器将会提供一个供获取已变更资源的URL。服务器也会报告当前修订版号。
请求和响应的XML DTD都是TBD。
自定义报告将只适用于支持此报告的服务器,但是将来的软件版本可能包含一个回退的代码路径(优雅的退化)来支持其他DeltaV服务器。
更新时,客户端将会获取(使用GET请求)每个服务器响应提供的URL。
可能的话,GET(和PUT)操作将会使用diff格式传递内容。该机制遵循名为HTTP的Delta编码的因特网草案。
实体标签 (etags)
etag需要在一个资源的所有版本中保持唯一。幸好,对版本管理系统来说,这很容易。每个etag可以简单地为资源所在的仓库节点ID(node-id)
etag通常用于生成diff(差异),可以参考上文提及的草案 HTTP中的Delta编码。现在问题是如果获取存储在客户端的每个文件(不需要文件夹的etag,因为我们从不读取)的etag。在检出或更新过程中,很简单:etag包含在所获取的每个文件的HTTP响应头里。
问题的另一个方面是如何获取一次提交后的etag。版本资源是在活动检入时创建的,而MERGE响应头提供了一种获取版本资源属性的方法,etag以及其他属性可以通过此机制获取。
标签和分支
在Subversion中标签和分支被复制从一个区域到另一个区域被执行。例如:
[.../src/my-project]$ svn cp trunk tags/1.0.3-rc4 [.../src/my-project]$ svn commit |
在上面的例子中,tags/1.0.3-RC4 现在应该考虑只读并时刻反映trunk的状态。
这些副本就像一个普通的提交处理。 activity 用MKACTIVITY创建,工作资源通过CHECKOUT
创建(例如上面在我们的例子中 目标目录;tags/),然后执行复制操作。该activity然后合并到有MERGE要求的信息库。
服务器需求
警告: 本节已过期。DeltaV草案已经历了多个修订版,我们使用的版本变更了一部分。
DAV方法
为了能正常操作,服务器需要实现以下WebDAV方法:
OPTIONS GET DELETE COPY PROPPATCH PROPFIND MKACTIVITY CHECKOUT MERGE REPORT |
当前Subversion需要下面的方法:
CHECKIN UNCHECKOUT UPDATE LABEL VERSION-CONTROL BASELINE-CONTROL MKWORKSPACE |
DAV属性
以下DeltaV属性需要被实现:
DAV:comment DAV:creator-displayname DAV:supported-method-set DAV:supported-live-property-set DAV:supported-report-set DAV:version-controlled-configuration DAV:checked-in DAV:auto-version,是个只读的空元素(不支持自动版本化/auto-versioning) DAV:checked-out DAV:predecessor-set 提示:在多个前驱版本如何合并生成单个新修订版机制上,
Subversion的设计文档表述不是很清楚。搞明白之后发现,
DAV:predecessors可能最终包含多余零个或一个前驱URL DAV:version-name, (全局)修订版本号 DAV:checkout-fork DAV:checkin-fork DAV:auto-update DAV:subbaseline-set,只读空属性(不支持子基线/sub-baselines) DAV:unreserved,被设成F DAV:baseline-controlled-collection DAV:baseline-collection DAV:subactivity-set,只读空属性(不支持子活动/sub-activities) DAV:eclipsed-set,总是空(内部成员不能被隐藏) |
与DeltaV规范相反,下面的必须属性没有被实现:
DAV:successor-set,获取该值计算量很大 DAV:checkout-set,事实上我们没有被检出的记录,而是使用工作资源URL来提供必要的信息;
因此我们没有记录数据来填充该属性
DAV:merge-set, MERGE只支持一次提交。并不支持规范里定义的任意合并 DAV:auto-merge-set ,等同DAV:merge-set. DAV:activity-version-set,仅用于工作资源的活动,这样版本不能是活动的一部分也许应该被定义为空集? DAV:activity-checkout-set,仅用于工作资源的活动,我们也不记录哪些工作资源存在 DAV:activity-set,仅用于工作资源的活动,这样版本不能是活动的一部分也许应该被定义为空集? DAV:version-controlled-binding-set,我们没有版本历史资源赋给此属性 |
OPTIONS
OPTIONS请求表示支持下文的DAV特性:
版本控制/version-control 检出/checkout 工作资源working-resource 合并/merge 基线/baseline 活动/activity 版本控制集/version-controlled-collection |
报告(reports)
DAV:supported-report-set属性表示支持以下报告:
svn:update-report
svn:log-report
这些报告仅可用在公共资源(版本控制资源, VCRs)上。$svn/内的资源不可用。
注意事项,提醒
讨论超时和自动清洗的活动(以及相关工作资源)。
讨论通过的mod_dav_svn维护热数据库。
讨论ra_dav和mod_dav_svn的其他实施细则。
|