本文详叙了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没有直接模拟树增量概念。客户端可以通过在各种WebD{敏感词}上发送一系列PROPFIND请求发现变更,但是这个调用 多个请求将是一个耗时的操作。作为替代,Subversion将这个概念整理为一个自定义的WebDAV报告。使用这个报告,客户端可以了解工作副本中的 哪项过时并可以发送GET和PROPFIND方法获取新数据。 在Subversion中,分支和标签是简单的拷贝,是用WebDAV COPY处理的。
在某处需要讨论拷贝.需要讨论拷贝的历史如何被保存 (svn是自动处理的, 但是与其他服务端交互的时候可能需要我们在其他服务端设置一个自定义属性. Subversion中使用的Deltav概念
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | < 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中的一组元素。
<directory ancestor="/dir1/dir3"> 指定我们用/dir1/dir3替换/dir1/dir2
CHECKOUT /dir1/dir2/
(返回该目录的一个当前工作资源URL)
COPY /dir1/dir3/
Destination: http://www.example.com/$svn/wrk/.../
Overwrite: T
/dir1/dir2/file3是新的(因为我们刚刚重写了原始的dir2目录),并来源于/dir1/dir2/file3。因此,我们只需要COPY文件到目标目录的当前工作资源:
COPY /dir1/dir2/file3
Destination: http://www.example.com/$svn/wrk/.../file3
CHECKOUT /dir1/dir3/
(返回该目录的一个当前工作资源URL)
DELETE /$svn/wrk/.../
我们将会在/dir1下创建新的子目录(dir4)。由于还没有检出/dir1,我们这样做:
CHECKOUT /dir1/
(返回该目录的一个当前工作资源URL)
现在我们把正确的目录复制到新的当前工作资源:
COPY /dir1/dir2/
Destination: http://www.example.com/$svn/wrk/.../dir4/
COPY在服务器上创建了完整的一组当前工作资源,我们只需删除不再需要的那部分:
DELETE: /$svn/wrk/.../dir4/file3
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修订版)和每一个修订版创建的版本资源。由于这些资源都是版本资源,因此下面的属性都是只读的。
DAV:comment
用于指定检入评论的标准的(过期的)属性
DAV:creator-displayname
Subversion的“用户”一次特定修改后生成的(已过期的)属性
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编码的因特网草案。
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草案已经历了多个修订版,我们使用的版本变更了一部分。
为了能正常操作,服务器需要实现以下WebDAV方法:
OPTIONS
GET
DELETE
COPY
PROPPATCH
PROPFIND
MKACTIVITY
CHECKOUT
MERGE
REPORT
当前Subversion需要下面的方法:
CHECKIN
UNCHECKOUT
UPDATE
LABEL
VERSION-CONTROL
BASELINE-CONTROL
MKWORKSPACE
以下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请求表示支持下文的DAV特性:
1
2
版本控制/version-control
检出/checkout
工作资源working-resource
合并/merge
基线/baseline
活动/activity
版本控制集/version-controlled-collection
DAV:supported-report-set属性表示支持以下报告:
svn:update-report
svn:log-report
这些报告仅可用在公共资源(版本控制资源, VCRs)上。$svn/内的资源不可用。
讨论超时和自动清洗的活动(以及相关工作资源)。
讨论通过的mod_dav_svn维护热数据库。
讨论ra_dav和mod_dav_svn的其他实施细则。
有好几次,人问,“为什么选择HTTP/ WebDAV/ DeltaV系统?这似乎是非常臃肿,不适合。你为什么不设计一个定制的,精心调校的协议?或者,使用cvs协议?”下面列出的是一些我们选择WebDAV作为我们的网络协议的原因。
虽然这个名单当然可以有更多的理由去扩展(为了公平,列出为何WebDAV的是一个糟糕的选择的原因),但同时那肯定证明了我们的选择它的基本原因。
注:此表来自电子邮件,所以语气和观点可能有点过。而且文字推敲也这样。
例如,一起来看看:http://svn.apache.org/repos/asf/subversion/trunk/README(这是概述,我们还对以前版本每个文件的网址)
使用Web文件夹或WebDrive或类似在Windows中(适用于Windows XP的原生DAV坐骑)浏览SVN仓库与Windows资源管理器。 Mac OS X中有内置的DAV服务器的安装。Nautilus 拥有DAV功能。然后,你有你的开源工具,如cadaver,Goliath等
我甚至不能开始计算的HTTP工具和可用的库的数量。如果我们设计我们自己的协议,那么我们将得不到任何好处。两个HTTP库的实现(Joe Orton of Neon, and Daniel Stenberg of CURL)都是这样。我用Python的httplib的(和我自己的davlib)做了很多我们的服务器的测试。没有必要去和推出新的协议库。
一个字:虚: - )当我们捕获网络跟踪,粗略的了解HTTP。这是相当不错的,但我知道有更棒的。不过我们也有其他的工具,比如squid 和其他(高速缓存)代理(见下一条)。
Subversion与缓存代理一起会工作得更有效。没有必要再使用特殊的工具如cvsup。只要访问缓存代理,你就拥有了一个分布式的只读仓库。那个欧洲开发小组只要访问他们与美国服务器间的缓存,他们的检出和更新就会被缓存下来,以方便小组内的其他成员。而提交将会流经缓存,发回美国服务器。
我们没必要为新协议重新实现一个认证模式,因为已经可以使用为HTTP定义的一簇模式。看过CVS协议吗?见过“我爱你”或者“我恨你”吧(表示重新实现协议过程中,程序员的爱恨情绪)?:-) 这就是创建新的认证模式的全部。但是只要我们愿意,我们就可以使用SSL和基于证书的认证。还有Kerberos, NTLM, 甚至是简单的Basic或者摘要(Digest)。我们的用户可以来自文本文件,数据库,LDAP, 或PAM。我们不必重新发明轮子,因为这些Apache都可以用。
我们不必担心如何设置可移植TCP_CORK以提供最佳的网络数据包。我们不必担心何时使用sendfile()是有道理的,或者何时它是可用的。我们不必担心断开的客户端连接,如何充分利用线程和进程如何缩放,请求管理,监视,日志记录等。Apache给了我们这些所有或者更多。我"真的"不希望通过xinetd这样做。我的意思是...在标准输出上设置TCP_CORK?怪异:-)
我们已经使用了在线压缩,类似于CVS的“-z#”开关。而其他我们什么也没做。客户端库和服务器,对于我们来说只是根据 RFC2616 使用了自动支持。
在未来,我们将能够实现与众多IDE和其他WebDAV/ DeltaV客户端互操作。由于DeltaV的日益流行,集成开发环境可以很好地使用它的源代码管理,而不需要写一些MS/ SCC库接口的工具。