本手册旨在描述 Subversion 1.6.x 系列。如果您运行的是其他版本的 Subversion,我们强烈建议您访问 https://svnbook.subversion.org.cn/ 并查阅与您 Subversion 版本相符的手册版本。
Apache HTTP 服务器是一个 “重型” 网络服务器,Subversion 可以利用它。通过自定义模块,httpd 使 Subversion 仓库可以通过 WebDAV/DeltaV[47] 协议提供给客户端,该协议是 HTTP 1.1 的扩展。该协议采用作为万维网核心的普遍 HTTP 协议,并添加了写入功能,特别是版本化的写入功能。结果是一个标准化、健壮的系统,它方便地打包为 Apache 2.0 软件的一部分,由众多操作系统和第三方产品支持,并且不需要网络管理员打开另一个自定义端口。[48] 虽然 Apache-Subversion 服务器比 svnserve 具有更多功能,但设置起来也稍微复杂一些。灵活性通常伴随着更高的复杂性。
以下大部分讨论都包含对 Apache 配置指令的引用。虽然提供了一些使用这些指令的示例,但全面描述它们超出了本章的范围。Apache 团队维护着优秀的文档,在他们的网站上公开提供,地址为 https://httpd.apache.ac.cn。例如,配置指令的一般参考位于 https://httpd.apache.ac.cn/docs-2.0/mod/directives.html。
此外,当您对 Apache 设置进行更改时,很可能在某个过程中会发生错误。如果您还不熟悉 Apache 的日志记录子系统,您应该了解它。在您的 httpd.conf
文件中,有一些指令指定了 Apache 生成的访问日志和错误日志的磁盘位置(分别是 CustomLog
和 ErrorLog
指令)。Subversion 的 mod_dav_svn 也使用 Apache 的错误日志记录接口。您始终可以浏览这些文件的内容,以获取可能揭示问题来源的信息,而这些信息在其他情况下并不明显。
要通过 HTTP 将您的仓库联网,您基本上需要四个组件,分别包含在两个包中。您需要 Apache httpd 2.0 或更高版本、附带的 mod_dav DAV 模块、Subversion 以及与 Subversion 一起分发的 mod_dav_svn 文件系统提供程序模块。一旦您拥有所有这些组件,联网仓库的过程就非常简单,只需:
使用 mod_dav 模块启动并运行 httpd
将 mod_dav_svn 后端安装到 mod_dav,它使用 Subversion 的库来访问仓库
配置您的 httpd.conf
文件以导出(或公开)仓库
您可以通过从源代码编译 httpd 和 Subversion 或在系统上安装它们的预构建二进制包来完成前两项。有关如何编译 Subversion 以与 Apache HTTP Server 一起使用的最新信息,以及如何为此目的编译和配置 Apache 本身的最新信息,请参阅 Subversion 源代码树顶层的 INSTALL
文件。
在您将所有必要的组件安装到系统中之后,剩下的就是通过 httpd.conf
文件配置 Apache。指示 Apache 使用 LoadModule
指令加载 mod_dav_svn 模块。此指令必须位于任何其他与 Subversion 相关的配置项之前。如果您的 Apache 是使用默认布局安装的,那么您的 mod_dav_svn 模块应该已安装在 Apache 安装位置(通常为 /usr/local/apache2
)的 modules
子目录中。LoadModule
指令具有简单的语法,将命名模块映射到磁盘上共享库的位置
LoadModule dav_svn_module modules/mod_dav_svn.so
Apache 将 LoadModule
配置项的库路径解释为相对于其自己的服务器根目录。如果按照前面所示进行配置,Apache 将在其自己的 modules/
子目录中查找 Subversion DAV 模块共享库。根据 Subversion 在您的系统上的安装方式,您可能需要为该库指定完全不同的路径,甚至可能是绝对路径,例如以下示例
LoadModule dav_svn_module C:/Subversion/lib/mod_dav_svn.so
请注意,如果 mod_dav 作为共享对象编译(而不是静态链接到 httpd 二进制文件),您也需要类似的 LoadModule
语句。确保它位于 mod_dav_svn 行之前
LoadModule dav_module modules/mod_dav.so LoadModule dav_svn_module modules/mod_dav_svn.so
在您的配置文件中的某个后面位置,您现在需要告诉 Apache 您将 Subversion 仓库(或仓库)放在哪里。Location
指令具有类似 XML 的表示法,以一个开始标签开头,以一个结束标签结尾,中间包含各种其他配置指令。Location
指令的目的是指示 Apache 在处理指向给定 URL 或其子项的请求时执行一些特殊的操作。对于 Subversion,您希望 Apache 将指向版本化资源的 URL 的支持简单地移交给 DAV 层。您可以指示 Apache 将所有路径部分(URL 中服务器名称和可选端口号后面的部分)以 /repos/
开头的 URL 的处理委托给 DAV 提供程序,该提供程序的仓库位于 /var/svn/repository
,使用以下 httpd.conf
语法
<Location /repos> DAV svn SVNPath /var/svn/repository </Location>
如果您计划支持多个 Subversion 仓库,这些仓库将位于本地磁盘上的同一个父目录中,您可以使用一个替代指令(SVNParentPath
)来指示该通用父目录。例如,如果您知道您将在一个目录 /var/svn
中创建多个 Subversion 仓库,这些仓库将通过诸如 http://my.server.com/svn/repos1
、http://my.server.com/svn/repos2
等 URL 访问,您可以使用以下示例中的 httpd.conf
配置语法
<Location /svn> DAV svn # Automatically map any "/svn/foo" URL to repository /var/svn/foo SVNParentPath /var/svn </Location>
使用此语法,Apache 将所有路径部分以 /svn/
开头的 URL 的处理委托给 Subversion DAV 提供程序,然后该提供程序将假设 SVNParentPath
指令指定的目录中的任何项目实际上都是 Subversion 仓库。这是一种特别方便的语法,因为与使用 SVNPath
指令不同,您无需重新启动 Apache 即可添加或删除托管的仓库。
确保在定义新的 Location
时,它不会与其他导出的位置重叠。例如,如果您的主 DocumentRoot
导出到 /www
,请不要在 <Location /www/repos>
中导出 Subversion 仓库。如果收到针对 URI /www/repos/foo.c
的请求,Apache 将不知道是查找 DocumentRoot
中的文件 repos/foo.c
,还是委托 mod_dav_svn 从 Subversion 仓库返回 foo.c
。结果通常是服务器出现类似 301 Moved Permanently
的错误。
在此阶段,您应该认真考虑权限问题。如果您已经运行 Apache 一段时间了,作为您的普通 Web 服务器,您可能已经有一组内容——网页、脚本等。这些项目已经配置了一组权限,允许它们与 Apache 协同工作,或者更确切地说,允许 Apache 与这些文件协同工作。当用作 Subversion 服务器时,Apache 也需要正确的权限才能读取和写入您的 Subversion 仓库。
您需要确定一个满足 Subversion 要求的权限系统设置,而不会破坏任何现有的网页或脚本安装。这可能意味着更改 Subversion 仓库的权限,使其与 Apache 为您提供服务的其他内容使用的权限匹配,或者它可能意味着在 httpd.conf
中使用 User
和 Group
指令来指定 Apache 应该以拥有 Subversion 仓库的用户和组身份运行。没有单一正确的设置权限的方法,每个管理员都有不同的原因以某种方式执行操作。请注意,权限相关问题可能是配置 Subversion 仓库以与 Apache 一起使用时最常见的疏忽。
此时,如果您将 httpd.conf
配置为包含以下内容
<Location /svn> DAV svn SVNParentPath /var/svn </Location>
那么您的仓库将对全世界 “匿名” 可访问。在您配置一些身份验证和授权策略之前,您通过 Location
指令提供的 Subversion 仓库将对所有人普遍可访问。换句话说
任何人都可以使用 Subversion 客户端签出一个仓库 URL(或其任何子目录)的工作副本。
任何人都可以通过将 Web 浏览器指向仓库 URL 来交互式地浏览仓库的最新修订版。
任何人都可以提交到仓库。
当然,您可能已经设置了 pre-commit
挂钩脚本以阻止提交(请参阅 名为“实现仓库挂钩”的部分)。但是,正如您接下来将看到的那样,也可以使用 Apache 的内置方法以特定方式限制访问。
![]() |
提示 |
---|---|
需要身份验证可以防止无效用户直接访问存储库,但不能保护有效用户的网络活动隐私。有关如何配置服务器以支持 SSL 加密(可以提供额外的保护层)的信息,请参见名为“使用 SSL 保护网络流量”的部分。 |
验证客户端最简单的方法是通过 HTTP 基本身份验证机制,该机制只需使用用户名和密码来验证用户的身份。Apache 提供了htpasswd 实用程序[49] 用于管理包含用户名和密码的文件。
![]() |
警告 |
---|---|
基本身份验证非常不安全,因为它以几乎纯文本的形式在网络上传输密码。有关使用更安全的 Digest 机制的信息,请参见名为“摘要身份验证”的部分。 |
首先,创建一个密码文件并授予 Harry 和 Sally 访问权限
$ ### First time: use -c to create the file $ ### Use -m to use MD5 encryption of the password, which is more secure $ htpasswd -c -m /etc/svn-auth.htpasswd harry New password: ***** Re-type new password: ***** Adding password for user harry $ htpasswd -m /etc/svn-auth.htpasswd sally New password: ******* Re-type new password: ******* Adding password for user sally $
接下来,确保 Apache 可以访问提供基本身份验证和相关功能的模块:mod_auth_basic
、mod_authn_file
和 mod_authz_user
。在许多情况下,这些模块被编译到 httpd 本身,但如果不是,您可能需要使用 LoadModule
指令显式加载其中一个或多个模块。
LoadModule auth_basic_module modules/mod_auth_basic.so LoadModule authn_file_module modules/mod_authn_file.so LoadModule authz_user_module moduels/mod_authz_user.so
确保 Apache 可以访问所需的功能后,您需要在 <Location>
块中添加更多指令,以告诉 Apache 您希望使用哪种类型的身份验证,以及如何执行该操作。
<Location /svn> DAV svn SVNParentPath /var/svn # Authentication: Basic AuthName "Subversion repository" AuthType Basic AuthBasicProvider file AuthUserFile /etc/svn-auth.htpasswd </Location>
这些指令的工作原理如下
AuthName
是您为身份验证域选择的任意名称。大多数浏览器在提示用户名和密码时会在对话框中显示此名称。
AuthType
指定要使用的身份验证类型。
AuthBasicProvider
指定要用于该位置的基本身份验证提供程序。在我们的示例中,我们希望查询本地密码文件。
AuthUserFile
指定要使用的密码文件的位置。
但是,此 <Location>
块目前还没有做任何有用的事情。它只是告诉 Apache 如果需要授权,它应该向 Subversion 客户端请求用户名和密码。(当需要授权时,Apache 也需要身份验证。)然而,这里缺少的是指示 Apache 哪些类型的 客户端请求需要授权的指令;目前,没有任何指令。最简单的方法是指定所有请求都通过添加 Require valid-user
到块中来要求授权。
<Location /svn> DAV svn SVNParentPath /var/svn # Authentication: Basic AuthName "Subversion repository" AuthType Basic AuthBasicProvider file AuthUserFile /etc/svn-auth.htpasswd # Authorization: Authenticated users only Require valid-user </Location>
有关 Require
指令以及其他设置授权策略的方法的更多详细信息,请参见名为“授权选项”的部分。
![]() |
注意 |
---|---|
|
摘要身份验证是对基本身份验证的改进,它允许服务器验证客户端的身份,而无需在网络上传输未经保护的密码。客户端和服务器都会对用户名、密码、请求的 URI 和服务器提供的 nonce(一次性号码)进行不可逆的 MD5 散列,并且每次需要身份验证时都会更改。客户端将散列发送到服务器,然后服务器验证散列是否匹配。
配置 Apache 以使用摘要身份验证非常简单。您需要确保 mod_auth_digest
模块可用(而不是 mod_auth_basic
),然后对之前的示例进行一些小的改动。
<Location /svn> DAV svn SVNParentPath /var/svn # Authentication: Digest AuthName "Subversion repository" AuthType Digest AuthDigestProvider file AuthUserFile /etc/svn-auth.htdigest # Authorization: Authenticated users only Require valid-user </Location>
请注意,AuthType
现在被设置为 Digest
,并且我们为 AuthUserFile
指定了不同的路径。摘要身份验证使用与基本身份验证不同的文件格式,该格式由 Apache 的 htdigest 实用程序[50] 而不是 htpasswd 创建和管理。摘要身份验证还具有额外的 “领域” 概念,它必须与 AuthName
指令的值匹配。
![]() |
注意 |
---|---|
对于摘要身份验证,可以使用 |
可以按如下方式创建密码文件:
$ ### First time: use -c to create the file $ htdigest -c /etc/svn-auth.htdigest "Subversion repository" harry Adding password for harry in realm Subversion repository. New password: ***** Re-type new password: ***** $ htdigest /etc/svn-auth.htdigest "Subversion repository" sally Adding user sally in realm Subversion repository New password: ******* Re-type new password: ******* $
此时,您已经配置了身份验证,但没有配置授权。Apache 能够向客户端发出挑战并确认身份,但它尚未被告知如何允许或限制对拥有这些身份的客户端的访问。本节介绍两种控制对存储库访问的策略。
最简单的访问控制形式是授权某些用户对存储库进行只读访问或对存储库进行读写访问。
您可以通过在 <Location>
块内直接添加 Require valid-user
来限制所有存储库操作的访问权限。来自名为“摘要身份验证”的部分 的示例只允许成功通过身份验证的客户端对 Subversion 存储库执行任何操作。
<Location /svn> DAV svn SVNParentPath /var/svn # Authentication: Digest AuthName "Subversion repository" AuthType Digest AuthUserFile /etc/svn-auth.htdigest # Authorization: Authenticated users only Require valid-user </Location>
有时您不需要执行如此严格的管理。例如,Subversion 自己的源代码存储库位于http://svn.collab.net/repos/svn,它允许世界上任何人都执行只读存储库任务(例如检出工作副本和浏览存储库),但将写入操作限制为经过身份验证的用户。 Limit
和 LimitExcept
指令允许进行这种类型的选择性限制。与 Location
指令一样,这些块也有开始和结束标签,您会将它们嵌套在 <Location>
块中。
Limit
和 LimitExcept
指令上存在的参数是受该块影响的 HTTP 请求类型。例如,要允许匿名只读操作,您将使用 LimitExcept
指令(传递 GET
、PROPFIND
、OPTIONS
和 REPORT
请求类型参数)并将之前提到的 Require valid-user
指令放在 <LimitExcept>
块内,而不是仅放在 <Location>
块内。
<Location /svn> DAV svn SVNParentPath /var/svn # Authentication: Digest AuthName "Subversion repository" AuthType Digest AuthUserFile /etc/svn-auth.htdigest # Authorization: Authenticated users only for non-read-only # (write) operations; allow anonymous reads <LimitExcept GET PROPFIND OPTIONS REPORT> Require valid-user </LimitExcept> </Location>
这些只是一些简单的示例。有关 Apache 访问控制和 Require
指令的更深入的信息,请查看 Apache 文档教程集合中的 Security
部分,地址为https://httpd.apache.ac.cn/docs-2.0/misc/tutorials.html。
可以使用 mod_authz_svn 设置更细粒度的权限。此 Apache 模块获取从客户端到服务器传递的各种不透明 URL,要求 mod_dav_svn 解码它们,然后根据配置文件中定义的访问策略可能否决请求。
如果您从源代码构建了 Subversion,则 mod_authz_svn 会自动构建并与 mod_dav_svn 一起安装。许多二进制发行版也会自动安装它。要验证它是否已正确安装,请确保它位于 httpd.conf
中 mod_dav_svn 的 LoadModule
指令之后。
LoadModule dav_module modules/mod_dav.so LoadModule dav_svn_module modules/mod_dav_svn.so LoadModule authz_svn_module modules/mod_authz_svn.so
要激活此模块,您需要将 <Location>
块配置为使用 AuthzSVNAccessFile
指令,该指令指定一个包含存储库中路径的权限策略的文件。(稍后,我们将讨论该文件的格式。)
Apache 很灵活,因此您可以选择以三种基本模式之一配置您的块。首先,选择以下基本配置模式之一。(以下示例非常简单;有关 Apache 身份验证和授权选项的更多详细信息,请查看 Apache 自身的文档。)
最开放的方法是允许所有人访问。这意味着 Apache 从不发送身份验证挑战,所有用户都被视为 “匿名”。(请参见示例 6.2:“匿名访问的示例配置”。)
示例 6.2:匿名访问的示例配置
<Location /repos> DAV svn SVNParentPath /var/svn # Authentication: None # Authorization: Path-based access control AuthzSVNAccessFile /path/to/access/file </Location>
在偏执程度的另一端,您可以将 Apache 配置为验证所有客户端。此块无条件地通过 Require valid-user
指令要求身份验证,并定义一种方法来验证有效用户。(请参见示例 6.3:“经过身份验证的访问的示例配置”。)
示例 6.3:经过身份验证的访问的示例配置
<Location /repos> DAV svn SVNParentPath /var/svn # Authentication: Digest AuthName "Subversion repository" AuthType Digest AuthUserFile /etc/svn-auth.htdigest # Authorization: Path-based access control; authenticated users only AuthzSVNAccessFile /path/to/access/file Require valid-user </Location>
第三种非常流行的模式是允许组合经过身份验证和匿名访问。例如,许多管理员希望允许匿名用户读取某些存储库目录,但将对更敏感区域的访问限制为经过身份验证的用户。在此设置中,所有用户最初都以匿名方式访问存储库。如果您的访问控制策略在任何时候都需要真实的用户名,Apache 将要求客户端进行身份验证。为此,请同时使用 Satisfy Any
和 Require valid-user
指令。(请参见示例 6.4:“混合经过身份验证/匿名访问的示例配置”。)
示例 6.4:混合经过身份验证/匿名访问的示例配置
<Location /repos> DAV svn SVNParentPath /var/svn # Authentication: Digest AuthName "Subversion repository" AuthType Digest AuthUserFile /etc/svn-auth.htdigest # Authorization: Path-based access control; try anonymous access # first, but authenticate if necessary AuthzSVNAccessFile /path/to/access/file Satisfy Any Require valid-user </Location>
下一步是创建授权文件,其中包含对存储库中特定路径的访问规则。我们将在本章后面讨论如何操作,在名为“基于路径的授权”的部分 中。
The mod_dav_svn module goes through a lot of work to make sure that data you've marked “unreadable” doesn't get accidentally leaked. This means it needs to closely monitor all of the paths and file-contents returned by commands such as svn checkout and svn update. If these commands encounter a path that isn't readable according to some authorization policy, the path is typically omitted altogether. In the case of history or rename tracing—for example, running a command such as svn cat -r OLD foo.c
on a file that was renamed long ago—the rename tracking will simply halt if one of the object's former names is determined to be read-restricted.
All of this path checking can sometimes be quite expensive, especially in the case of svn log. When retrieving a list of revisions, the server looks at every changed path in each revision and checks it for readability. If an unreadable path is discovered, it's omitted from the list of the revision's changed paths (normally seen with the --verbose
(-v
) option), and the whole log message is suppressed. Needless to say, this can be time-consuming on revisions that affect a large number of files. This is the cost of security: even if you haven't configured a module such as mod_authz_svn at all, the mod_dav_svn module is still asking Apache httpd to run authorization checks on every path. The mod_dav_svn module has no idea what authorization modules have been installed, so all it can do is ask Apache to invoke whatever might be present.
On the other hand, there's also an escape hatch of sorts, which allows you to trade security features for speed. If you're not enforcing any sort of per-directory authorization (i.e., not using mod_authz_svn or similar module), you can disable all of this path checking. In your httpd.conf
file, use the SVNPathAuthz
directive as shown in Example 6.5, “Disabling path checks altogether”.
Example 6.5. Disabling path checks altogether
<Location /repos> DAV svn SVNParentPath /var/svn SVNPathAuthz off </Location>
The SVNPathAuthz
directive is “on” by default. When set to “off,” all path-based authorization checking is disabled; mod_dav_svn stops invoking authorization checks on every path it discovers.
通过 http://
连接到存储库意味着所有 Subversion 活动都是明文发送的。这意味着,诸如签出、提交和更新之类的操作可能会被未经授权的方 “嗅探” 网络流量截获。使用 SSL 加密流量是保护网络中可能敏感信息的有效方法。
如果 Subversion 客户端被编译为使用 OpenSSL,它将获得通过 https://
URL 与 Apache 服务器通信的能力,因此所有流量都将使用每个连接会话密钥加密。Subversion 客户端使用的 WebDAV 库不仅能够验证服务器证书,而且还可以在服务器挑战时提供客户端证书。
本书不涉及如何生成客户端和服务器 SSL 证书以及如何配置 Apache 以使用它们的描述。许多其他参考资料,包括 Apache 自己的文档,都描述了此过程。
![]() |
提示 |
---|---|
来自知名实体的 SSL 证书通常需要付费,但至少,你可以配置 Apache 以使用使用 OpenSSL(https://openssl.ac.cn)等工具生成的自签名证书。[51] |
当通过 https://
连接到 Apache 时,Subversion 客户端可能会收到两种不同的响应类型
服务器证书
客户端证书挑战
当客户端收到服务器证书时,需要验证服务器是否为它声称的身份。OpenSSL 通过检查服务器证书的签名者或 证书颁发机构 (CA) 来完成此操作。如果 OpenSSL 无法自动信任 CA,或出现其他问题(例如证书过期或主机名不匹配),Subversion 命令行客户端会询问你是否要仍然信任服务器证书
$ svn list https://host.example.com/repos/project Error validating server certificate for 'https://host.example.com:443': - The certificate is not issued by a trusted authority. Use the fingerprint to validate the certificate manually! Certificate information: - Hostname: host.example.com - Valid: from Jan 30 19:23:56 2004 GMT until Jan 30 19:23:56 2006 GMT - Issuer: CA, example.com, Sometown, California, US - Fingerprint: 7d:e1:a9:34:33:39:ba:6a:e9:a5:c4:22:98:7b:76:5c:92:a0:9c:7b (R)eject, accept (t)emporarily or accept (p)ermanently?
此对话实质上与你可能从 Web 浏览器中看到的相同问题(它只是与 Subversion 类似的另一个 HTTP 客户端)。如果你选择 (p)
ermanent 选项,Subversion 将在你的私有运行时 auth/
区域中缓存服务器证书,就像你的用户名和密码被缓存一样(参见 名为“Caching credentials”的部分),并将自动信任将来该证书。
你的运行时 servers
文件还允许你使你的 Subversion 客户端自动信任特定的 CA,无论是全局还是每个主机。只需将 ssl-authority-files
变量设置为以分号分隔的 PEM 编码 CA 证书列表即可
[global] ssl-authority-files = /path/to/CAcert1.pem;/path/to/CAcert2.pem
许多 OpenSSL 安装也有一组预定义的 “默认” CA,这些 CA 几乎得到普遍信任。要使 Subversion 客户端自动信任这些标准颁发机构,请将 ssl-trust-default-ca
变量设置为 true
。
如果客户端收到证书挑战,则服务器要求客户端证明其身份。客户端必须发送回由服务器信任的 CA 签名的证书,以及 挑战响应,证明客户端拥有与证书关联的私钥。私钥和证书通常以加密格式存储在磁盘上,并受到密码保护。当 Subversion 收到此挑战时,它会要求你提供加密文件的路径和保护它的密码
$ svn list https://host.example.com/repos/project Authentication realm: https://host.example.com:443 Client certificate filename: /path/to/my/cert.p12 Passphrase for '/path/to/my/cert.p12': ********
请注意,客户端凭据存储在 .p12
文件中。要使用 Subversion 客户端证书,它必须采用 PKCS#12 格式,这是一个可移植标准。大多数 Web 浏览器都能够以这种格式导入和导出证书。另一种选择是使用 OpenSSL 命令行工具将现有证书转换为 PKCS#12。
运行时 servers
文件还允许你自动执行每个主机的此挑战。如果你设置了 ssl-client-cert-file
和 ssl-client-cert-password
变量,Subversion 可以在不提示你的情况下自动响应客户端证书挑战
[groups] examplehost = host.example.com [examplehost] ssl-client-cert-file = /path/to/my/cert.p12 ssl-client-cert-password = somepassword
更注重安全的用户可能希望排除 ssl-client-cert-password
以避免将密码明文存储在磁盘上。
我们已经涵盖了 Apache 和 mod_dav_svn 的大多数身份验证和授权选项。但 Apache 还提供了一些其他不错的功能。
为你的 Subversion 存储库配置 Apache/WebDAV 的最有用益处之一是,你的版本化文件和目录可以立即通过常规 Web 浏览器查看。由于 Subversion 使用 URL 来识别版本化资源,因此用于 HTTP 存储库访问的那些 URL 可以直接键入 Web 浏览器中。你的浏览器将对该 URL 发出 HTTP GET
请求;根据该 URL 是否表示版本化目录或文件,mod_dav_svn 将返回目录列表或文件内容。
如果 URL 不包含任何有关要查看的资源版本的信息,mod_dav_svn 将返回最新版本。此功能有一个很好的副作用,即你可以将 Subversion URL 传递给你的同伴作为文档的引用,这些 URL 将始终指向该文档的最新版本。当然,你甚至可以将这些 URL 作为来自其他网站的超链接使用。
从 Subversion 1.6 开始,mod_dav_svn 支持用于检查文件和目录的旧版本的公共 URI 语法。该语法使用 URL 的查询字符串部分来指定挂钩修订版和操作修订版中的一个或两个,Subversion 将使用它们来确定要显示给你的 Web 浏览器的文件或目录的版本。添加查询字符串名称/值对 p=
,其中 PEGREV
PEGREV
是一个修订版号,以指定要应用于请求的挂钩修订版。使用 r=
,其中 REV
REV
是一个修订版号,以指定操作修订版。
例如,如果你要查看项目 /trunk
中的 README.txt
文件的最新版本,请将你的 Web 浏览器指向该文件的存储库 URL,它可能类似于以下内容
http://host.example.com/repos/project/trunk/README.txt
如果你现在要查看该文件的某个旧版本,请在 URL 的查询字符串中添加操作修订版
http://host.example.com/repos/project/trunk/README.txt?r=1234
如果要查看的内容在存储库的最新修订版中不再存在怎么办?这就是挂钩修订版派上用场的地方
http://host.example.com/repos/project/trunk/deleted-thing.txt?p=321
当然,你可以组合挂钩修订版和操作修订版说明符来微调要查看的确切项目
http://host.example.com/repos/project/trunk/renamed-thing.txt?p=123&r=21
前面的 URL 将显示修订版 21 的对象,该对象在修订版 123 中位于存储库中的 /trunk/renamed-thing.txt
。参见 名为“Peg and Operative Revisions”的部分,详细了解这些 “挂钩修订版” 和 “操作修订版” 概念。它们可能有点难以理解。
提醒一下,mod_dav_svn 的此功能仅提供有限的存储库浏览体验。你可以看到目录列表和文件内容,但没有修订版属性(如提交日志消息)或文件/目录属性。对于需要更广泛地浏览存储库及其历史记录的用户,有几个第三方软件包可以提供此功能。一些示例包括 ViewVC (http://viewvc.tigris.org)、Trac (http://trac.edgewall.org) 和 WebSVN (http://websvn.info)。这些第三方工具不会影响 mod_dav_svn 的内置 “可浏览性”,并且通常提供更广泛的功能集,包括显示上述属性集、显示文件修订版之间的内容差异等。
在浏览 Subversion 存储库时,Web 浏览器通过查看 Apache 对 HTTP GET
请求的响应中返回的 Content-Type:
标头来了解如何呈现文件的内容。此标头的值是某种 MIME 类型。默认情况下,Apache 会告诉 Web 浏览器所有存储库文件都是 “默认” MIME 类型,通常是 text/plain
。但是,如果用户希望存储库文件以更有意义的方式呈现,这可能会让人感到沮丧——例如,让存储库中的 foo.html
文件在浏览时实际呈现为 HTML 可能会很好。
要实现这一点,您只需要确保您的文件具有正确的 svn:mime-type
设置。我们将在 名为“文件内容类型”的部分 中更详细地讨论这一点,您甚至可以配置您的客户端,以便在首次将文件添加到存储库时自动附加正确的 svn:mime-type
属性;请参见 名为“自动属性设置”的部分。
继续我们的示例,如果将 svn:mime-type
属性设置为 text/html
并在文件 foo.html
上设置,Apache 将正确地告诉您的 Web 浏览器将文件呈现为 HTML。您也可以将适当的 image/*
MIME 类型属性附加到图像文件,并最终使整个网站可以直接从存储库中查看!通常这没有问题,只要网站不包含任何动态生成的内容。
您通常会更多地使用指向版本化文件的 URL——毕竟,有趣的内容往往就在那里。但是,您可能需要浏览 Subversion 目录列表,您会很快注意到,用于显示该列表的生成的 HTML 非常基本,肯定不旨在美观(甚至有趣)。为了能够自定义这些目录显示,Subversion 提供了 XML 索引功能。在您存储库的 httpd.conf
文件的 Location
块中,一个单独的 SVNIndexXSLT
指令将指示 mod_dav_svn 在显示目录列表时生成 XML 输出,并引用您选择的 XSLT 样式表
<Location /svn> DAV svn SVNParentPath /var/svn SVNIndexXSLT "/svnindex.xsl" … </Location>
使用 SVNIndexXSLT
指令以及创意性的 XSLT 样式表,您可以使目录列表与您网站其他部分中使用的颜色方案和图像相匹配。或者,如果您愿意,您可以使用 Subversion 源代码分发目录的 tools/xslt/
目录中提供的示例样式表。请记住,提供给 SVNIndexXSLT
目录的路径实际上是一个 URL 路径——浏览器需要能够读取您的样式表才能使用它们!
如果您使用 SVNParentPath
指令从单个 URL 提供一组存储库,那么 Apache 也可以将所有可用存储库显示给 Web 浏览器。只需激活 SVNListParentPath
指令
<Location /svn> DAV svn SVNParentPath /var/svn SVNListParentPath on … </Location>
如果用户现在将她的 Web 浏览器指向 URL http://host.example.com/svn/
,她将看到位于 /var/svn
中的所有 Subversion 存储库的列表。显然,这可能是一个安全问题,因此默认情况下此功能处于关闭状态。
由于 Apache 本质上是一个 HTTP 服务器,因此它包含非常灵活的日志记录功能。讨论所有可以配置日志记录的方式超出了本书的范围,但我们应该指出,即使是最通用的 httpd.conf
文件也会导致 Apache 生成两个日志:error_log
和 access_log
。这些日志可能出现在不同的位置,但通常是在您的 Apache 安装的日志记录区域中创建的。(在 Unix 上,它们通常位于 /usr/local/apache2/logs/
中。)
error_log
描述了 Apache 在工作时遇到的任何内部错误。 access_log
文件记录 Apache 收到的每个传入 HTTP 请求。这使得您可以轻松地查看,例如,Subversion 客户端来自哪些 IP 地址、客户端使用服务器的频率、哪些用户通过身份验证以及哪些请求成功或失败。
不幸的是,由于 HTTP 是一种无状态协议,即使是最简单的 Subversion 客户端操作也会生成多个网络请求。查看 access_log
并推断出客户端在做什么非常困难——大多数操作看起来像一系列神秘的 PROPPATCH
、GET
、PUT
和 REPORT
请求。更糟糕的是,许多客户端操作发送几乎相同的请求序列,因此更难将它们区分开来。
mod_dav_svn 却可以帮助您。通过激活 “操作日志记录” 功能,您可以要求 mod_dav_svn 创建一个单独的日志文件,描述您的客户端正在执行的哪种高级操作。
为此,您需要使用 Apache 的 CustomLog
指令(在 Apache 的自述文件中进行了更详细的解释)。请务必在 Subversion Location
块 外部 调用此指令
<Location /svn> DAV svn … </Location> CustomLog logs/svn_logfile "%t %u %{SVN-ACTION}e" env=SVN-ACTION
在这个例子中,我们要求 Apache 在标准的 Apache logs
目录中创建一个特殊的日志文件 svn_logfile
。 %t
和 %u
变量分别由请求的时间和用户名替换。真正重要的部分是 SVN-ACTION
的两个实例。当 Apache 看到该变量时,它会替换 SVN-ACTION
环境变量的值,该变量由 mod_dav_svn 在检测到高级客户端操作时自动设置。
因此,您不必像这样解释传统的 access_log
[26/Jan/2007:22:25:29 -0600] "PROPFIND /svn/calc/!svn/vcc/default HTTP/1.1" 207 398 [26/Jan/2007:22:25:29 -0600] "PROPFIND /svn/calc/!svn/bln/59 HTTP/1.1" 207 449 [26/Jan/2007:22:25:29 -0600] "PROPFIND /svn/calc HTTP/1.1" 207 647 [26/Jan/2007:22:25:29 -0600] "REPORT /svn/calc/!svn/vcc/default HTTP/1.1" 200 607 [26/Jan/2007:22:25:31 -0600] "OPTIONS /svn/calc HTTP/1.1" 200 188 [26/Jan/2007:22:25:31 -0600] "MKACTIVITY /svn/calc/!svn/act/e6035ef7-5df0-4ac0-b811-4be7c823f998 HTTP/1.1" 201 227 …
您可以浏览更清晰的 svn_logfile
,就像这样
[26/Jan/2007:22:24:20 -0600] - get-dir /tags r1729 props [26/Jan/2007:22:24:27 -0600] - update /trunk r1729 depth=infinity [26/Jan/2007:22:25:29 -0600] - status /trunk/foo r1729 depth=infinity [26/Jan/2007:22:25:31 -0600] sally commit r1730
除了 SVN-ACTION
环境变量外,mod_dav_svn 还填充了 SVN-REPOS
和 SVN-REPOS-NAME
变量,它们分别包含到存储库的文件系统路径和基本名称。您可能也希望在您的 CustomLog
格式字符串中包含对其中一个或两个变量的引用,尤其是在将来自多个存储库的使用信息组合到单个日志文件时。
有关所有记录的操作的详尽列表,请参见 名为“高级日志记录”的部分。
使用 Apache 作为 Subversion 服务器的一个好处是,可以将其设置为简单复制。例如,假设您的团队分布在全球四个办事处。Subversion 存储库只能存在于其中一个办事处,这意味着其他三个办事处将无法访问它——在更新和提交代码时,他们可能会遇到明显更慢的流量和响应时间。一个强大的解决方案是建立一个由一个 主 Apache 服务器和多个 从 Apache 服务器组成的系统。如果您在每个办事处放置一个从服务器,用户可以从最靠近他们的任何从服务器检出一个工作副本。所有读取请求都转到其本地从服务器。写入请求会自动路由到单个主服务器。提交完成后,主服务器随后会自动使用 svnsync 复制工具将新修订版 “推送到” 每个从服务器。
这种配置为您的用户创造了巨大的感知速度提升,因为 Subversion 客户端流量通常是 80-90% 的读取请求。如果这些请求来自 本地 服务器,那么这是一个巨大的优势。
在本节中,我们将指导您完成此单个主/多个从系统标准设置。但是,请记住,您的服务器必须至少运行 Apache 2.2.0(加载了 mod_proxy)和 Subversion 1.5(mod_dav_svn)。
首先,按照常规方式配置主服务器的 httpd.conf
文件。使存储库在某个 URI 位置可用,并根据需要配置身份验证和授权。完成此操作后,以完全相同的方式配置每个 “从” 服务器,但将特殊的 SVNMasterURI
指令添加到块中
<Location /svn> DAV svn SVNPath /var/svn/repos SVNMasterURI http://master.example.com/svn … </Location>
此新指令告诉从服务器将所有写入请求重定向到主服务器。(这是通过 Apache 的 mod_proxy 模块自动完成的。)但是,普通的读取请求仍然由从服务器处理。确保您的主服务器和从服务器都具有匹配的身份验证和授权配置;如果它们不同步,可能会导致很大的麻烦。
接下来,我们需要处理无限递归问题。在当前配置下,想象一下当 Subversion 客户端对主服务器执行提交时会发生什么。提交完成后,服务器使用 svnsync 将新修订版复制到每个从服务器。但由于 svnsync 看起来只是另一个执行提交的 Subversion 客户端,因此从服务器将立即尝试将传入的写入请求代理回主服务器!就会出现混乱。
解决此问题的办法是让主服务器将修订版推送到从服务器上的另一个 <Location>
。此位置被配置为 不 代理任何写入请求,而是接受来自(且仅来自)主服务器 IP 地址的正常提交
<Location /svn-proxy-sync> DAV svn SVNPath /var/svn/repos Order deny,allow Deny from all # Only let the server's IP address access this Location: Allow from 10.20.30.40 … </Location>
现在您已经配置了主服务器和从服务器上的 Location
块,您需要配置主服务器以复制到从服务器。这以通常的方式完成——使用 svnsync。如果您不熟悉此工具,请参见 名为“存储库复制”的部分 以了解详细信息。
首先,确保每个从服务器存储库都具有一个 pre-revprop-change
钩子脚本,该脚本允许远程修订版属性更改。(这是在 svnsync 的接收端进行的标准操作。)然后登录到主服务器,并配置每个从服务器存储库 URI 以从本地磁盘上的主存储库接收数据
$ svnsync init http://slave1.example.com/svn-proxy-sync file:///var/svn/repos Copied properties for revision 0. $ svnsync init http://slave2.example.com/svn-proxy-sync file:///var/svn/repos Copied properties for revision 0. $ svnsync init http://slave3.example.com/svn-proxy-sync file:///var/svn/repos Copied properties for revision 0. # Perform the initial replication $ svnsync sync http://slave1.example.com/svn-proxy-sync Transmitting file data .... Committed revision 1. Copied properties for revision 1. Transmitting file data ....... Committed revision 2. Copied properties for revision 2. … $ svnsync sync http://slave2.example.com/svn-proxy-sync Transmitting file data .... Committed revision 1. Copied properties for revision 1. Transmitting file data ....... Committed revision 2. Copied properties for revision 2. … $ svnsync sync http://slave3.example.com/svn-proxy-sync Transmitting file data .... Committed revision 1. Copied properties for revision 1. Transmitting file data ....... Committed revision 2. Copied properties for revision 2. …
完成此操作后,我们配置主服务器的 post-commit
钩子脚本以在每个从服务器上调用 svnsync
#!/bin/sh # Post-commit script to replicate newly committed revision to slaves svnsync sync http://slave1.example.com/svn-proxy-sync > /dev/null 2>&1 & svnsync sync http://slave2.example.com/svn-proxy-sync > /dev/null 2>&1 & svnsync sync http://slave3.example.com/svn-proxy-sync > /dev/null 2>&1 &
每行末尾的额外部分不是必需的,但它们是一种偷偷摸摸的方式,可以允许同步命令在后台运行,这样 Subversion 客户端就不会永远等待提交完成。除了此 post-commit
钩子外,您还需要一个 post-revprop-change
钩子,这样,当用户(例如)修改日志消息时,从服务器也会收到该更改
#!/bin/sh # Post-revprop-change script to replicate revprop-changes to slaves REV=${2} svnsync copy-revprops http://slave1.example.com/svn-proxy-sync ${REV} > /dev/null 2>&1 & svnsync copy-revprops http://slave2.example.com/svn-proxy-sync ${REV} > /dev/null 2>&1 & svnsync copy-revprops http://slave3.example.com/svn-proxy-sync ${REV} > /dev/null 2>&1 &
我们这里遗漏的唯一事情是关于用户级锁(svn lock 类型的锁)的处理方式。锁在提交操作期间由主服务器强制执行;但是,关于锁的所有信息在读取操作(例如 svn update 和 svn status)期间由从服务器分发。因此,一个完全正常工作的代理设置需要将锁信息从主服务器完美地复制到从服务器。不幸的是,人们用来完成此复制的大多数机制都或多或少地存在缺陷[52]。许多团队根本不使用 Subversion 的锁定功能,因此这可能对您来说不是问题。遗憾的是,对于那些确实使用锁的团队,我们没有关于如何优雅地解决此缺陷的建议。
Apache 作为强大的 Web 服务器在其作用中提供的许多功能也可以用来增强 Subversion 的功能或安全性。Subversion 客户端能够使用 SSL(安全套接字层,前面已讨论过)。如果您的 Subversion 客户端构建为支持 SSL,它可以使用 https://
访问您的 Apache 服务器,并享受高质量的加密网络会话。
同样有用的是 Apache 和 Subversion 关系的其他功能,例如指定自定义端口(而不是默认的 HTTP 端口 80)或 Subversion 存储库应通过其访问的虚拟域名,或通过 HTTP 代理访问存储库。
最后,由于 mod_dav_svn 正在使用 WebDAV/DeltaV 协议的子集,因此可以通过第三方 DAV 客户端访问存储库。大多数现代操作系统(Win32、OS X 和 Linux)都具有将 DAV 服务器作为标准网络 “共享文件夹” 挂载的内置功能。这是一个复杂的话题,但在实现后也很奇妙。有关详细信息,请阅读 附录 C,WebDAV 和自动版本控制。
请注意,可以对 mod_dav_svn 进行许多其他小的调整,这些调整过于晦涩难懂,无法在本节中提及。有关 mod_dav_svn 响应的所有 httpd.conf
指令的完整列表,请参见 名为“指令”的部分。