本文档旨在描述 Subversion 1.4。如果您正在运行更新版本的 Subversion,我们强烈建议您访问 https://svnbook.subversion.org.cn/ 并查阅适合您 Subversion 版本的书籍。
由于 Subversion 版本库的整体设计和其依赖的技术都非常简单,创建和配置版本库是一个相当简单的任务。您需要做出一些初步的决策,但在任何给定设置中涉及到 Subversion 版本库的实际工作都是非常简单的,如果您发现自己要设置多个这样的东西,就会倾向于无脑重复。
不过,您需要事先考虑一些事情,例如
您希望哪些数据存在您的版本库(或版本库)中,以及这些数据将如何组织?
您的版本库将存放在哪里,以及如何访问?
您需要哪些类型的访问控制和版本库事件报告?
您想使用哪些可用的数据存储类型?
在本节中,我们将尝试帮助您回答这些问题。
虽然 Subversion 允许您移动版本化的文件和目录而不会丢失任何信息,甚至提供将整个版本化历史记录从一个版本库移动到另一个版本库的方法,但这样做可能会极大地扰乱经常访问版本库并期望某些位置有东西的人的工作流程。因此,在创建新的版本库之前,尝试提前预见未来;在将数据置于版本控制之下之前提前计划。通过有意识地“布局”您的版本库或版本库及其版本化的内容,您可以避免许多未来出现的问题。
假设作为版本库管理员,您将负责为多个项目提供版本控制系统支持。您的第一个决定是使用单个版本库保存多个项目,还是为每个项目提供单独的版本库,或者在这两者之间进行一些折衷。
使用单个版本库保存多个项目有很多好处,最明显的是缺少重复维护。单个版本库意味着只有一组钩子程序,一个需要定期备份的东西,一个需要转储和加载的东西(如果 Subversion 发布了不兼容的新版本),等等。此外,您还可以轻松地在项目之间移动数据,而不会丢失任何历史版本信息。
使用单个版本库的缺点是,不同的项目可能对版本库事件触发器有不同的需求,例如需要将提交通知电子邮件发送到不同的邮件列表,或者对什么构成合法提交以及什么不构成合法提交有不同的定义。当然,这些问题并非不可克服——但这意味着您所有的钩子脚本都必须对您的版本库的布局很敏感,而不是假设整个版本库都与一组人相关联。此外,请记住 Subversion 使用版本库全局修订版本号。虽然这些数字没有任何特殊的魔力,但有些人仍然不喜欢这样一个事实,即使他们最近没有对他们的项目进行任何更改,但版本库中最年轻的修订版本号仍在不断上升,因为其他项目正在积极添加新的修订版本。 [27]
也可以采用折衷的方法。例如,可以根据项目之间的关联程度对项目进行分组。您可能有一些版本库,每个版本库中包含少量项目。这样,可能希望共享数据的项目可以轻松地做到这一点,并且随着新的修订版本添加到版本库,至少开发人员知道这些新的修订版本至少与使用该版本库的每个人相关联。
在决定如何根据版本库组织您的项目之后,您可能还希望考虑版本库本身内的目录层次结构。由于 Subversion 使用常规目录复制来进行分支和标记(参见 第 4 章,分支和合并),Subversion 社区建议您为每个项目根目录选择一个版本库位置——包含与该项目相关数据的“最顶层”目录——然后在该根目录下创建三个子目录:trunk,表示主项目开发所在的目录;branches,表示在其中创建主开发线的各种命名分支的目录;tags,表示创建的树快照的集合,这些快照可能会被销毁,但永远不会被更改。 [28]
例如,您的版本库可能看起来像
/
calc/
trunk/
tags/
branches/
calendar/
trunk/
tags/
branches/
spreadsheet/
trunk/
tags/
branches/
…
请注意,每个项目根目录在您的版本库中的位置并不重要。如果您每个版本库只保存一个项目,那么将每个项目根目录放在该项目所属版本库的根目录中是合理的。如果您有多个项目,您可能希望将它们按组排列在版本库中,例如将具有相似目标或共享代码的项目放在同一个子目录中,或者可能只是按字母顺序对它们进行分组。这样的安排可能看起来像
/
utils/
calc/
trunk/
tags/
branches/
calendar/
trunk/
tags/
branches/
…
office/
spreadsheet/
trunk/
tags/
branches/
…
以您认为合适的方式布置您的版本库。Subversion 不期望也不强制执行特定的布局——在其眼中,目录就是目录。最终,您应该选择满足在该版本库中保存的项目的开发人员需求的版本库安排。
为了完全公开,我们将提到另一种非常常见的布局。在这种布局中,trunk、tags 和 branches 目录位于版本库的根目录中,您的项目位于这些目录下的子目录中,例如
/
trunk/
calc/
calendar/
spreadsheet/
…
tags/
calc/
calendar/
spreadsheet/
…
branches/
calc/
calendar/
spreadsheet/
…
这种布局没有特别错误,但对于您的用户来说可能感觉直观或不直观。特别是在大型的多项目情况下,有很多用户,这些用户可能只熟悉版本库中的一两个项目。但是,将项目作为分支的兄弟姐妹关系往往会淡化项目个体性,而将重点放在将所有项目作为一个整体进行考虑。不过,这是一个社会问题。我们喜欢我们最初建议的安排,纯粹出于实际原因——当有一个单独的版本库路径包含该项目和该项目唯一的整个历史记录——过去、现在、已标记和已分支——时,更容易询问(或修改或迁移到其他地方)单个项目的整个历史记录。
在创建 Subversion 版本库之前,您需要回答的明显问题是它将存放在哪里。这与涉及如何访问版本库(通过 Subversion 服务器或直接访问)、由谁访问(您公司防火墙后面的用户或整个开放互联网上的用户)、您将在 Subversion 周围提供哪些其他服务(版本库浏览界面、基于电子邮件的提交通知等)、您的数据备份策略,等等。许多其他问题紧密相关。
我们在 第 6 章,服务器配置 中介绍了服务器选择和配置,但我们在这里要简要说明的是,这些其他问题中的一些答案可能会有影响,迫使您在决定将版本库放在哪里时做出决定。例如,某些部署方案可能需要通过多台计算机从远程文件系统访问版本库,在这种情况下(正如您将在下一节中读到的),您对版本库后端数据存储的选择实际上就成了一个不可选择的选择,因为只有可用的后端之一可以在这种情况下工作。
解决每种可能的部署 Subversion 的方法既不可能,也不在本手册的范围之内。我们只是鼓励您使用这些页面和其他资源作为参考材料来评估您的选择,并提前计划。
从 1.1 版开始,Subversion 为每个版本库使用的底层数据存储类型提供了两种选择——通常称为“后端”或有点令人困惑地称为“(版本化)文件系统”。一种类型的数据存储将所有内容保存在 Berkeley DB(或 BDB)数据库环境中;使用这种类型的数据存储的版本库通常被称为“BDB 支持”的版本库。另一种类型将数据存储在普通的平面文件中,使用自定义格式。Subversion 开发人员已经习惯于将这种后一种数据存储机制称为FSFS [29]——一种版本化的文件系统实现,它直接使用本地操作系统文件系统——而不是通过数据库库或其他抽象层——来存储数据。
表 5.1,“版本库数据存储比较” 对 Berkeley DB 和 FSFS 版本库进行了比较概述。
表 5.1 版本库数据存储比较
| 类别 | 特性 | Berkeley DB | FSFS |
|---|---|---|---|
| 可靠性 | 数据完整性 | 如果正确部署,则非常可靠;Berkeley DB 4.4 带来了自动恢复 | 旧版本有一些很少出现但会破坏数据的错误 |
| 对中断的敏感性 | 非常敏感;崩溃和权限问题会导致数据库“卡住”,需要进行日志恢复操作 | 相当不敏感 | |
| 可访问性 | 可以从只读挂载中使用 | 否 | 是 |
| 平台无关的存储 | 否 | 是 | |
| 可以跨网络文件系统使用 | 一般来说,不能 | 是 | |
| 组权限处理 | 对用户 umask 问题很敏感;最好只被一个用户访问 | 绕过 umask 问题 | |
| 可扩展性 | 版本库磁盘使用情况 | 更大(特别是如果日志文件没有被清除) | 更小 |
| 修订版本树数量 | 数据库;没有问题 | 一些较旧的本地文件系统在单个目录中包含数千个条目时无法很好地扩展 | |
| 包含许多文件的目录 | 更慢 | 更快 | |
| 性能 | 签出最新修订版本 | 没有明显区别 | 没有明显区别 |
| 大型提交 | 总体上更慢,但成本在提交的整个生命周期内摊销 | 总体上更快,但最终化延迟可能会导致客户端超时 |
这两种后端类型各有优缺点。两者并没有哪个更“官方”,尽管从 Subversion 1.2 开始,较新的 FSFS 是默认的数据存储。两者都足够可靠,可以信赖它们来存储您的版本化数据。但是,正如您在 表格 5.1,“存储库数据存储比较” 中看到的,FSFS 后端在支持的部署场景方面提供了更多的灵活性。更高的灵活性意味着您需要更加努力地寻找将它错误部署的方法。这些原因,再加上不使用 Berkeley DB 意味着系统中少了一个组件,在很大程度上解释了为什么如今几乎所有人在创建新的存储库时都使用 FSFS 后端。
幸运的是,大多数访问 Subversion 存储库的程序对正在使用的后端数据存储一无所知。您甚至不必坚持您最初选择的数据存储——如果您后来改变主意,Subversion 提供了将存储库数据迁移到使用不同后端数据存储的另一个存储库的方法。我们将在本章后面的内容中详细讨论这一点。
以下小节将更详细地介绍可用的数据存储类型。
在 Subversion 的初始设计阶段,开发人员决定使用 Berkeley DB,原因有很多,包括它的开源许可证、事务支持、可靠性、性能、API 简洁性、线程安全性、对游标的支持等等。
Berkeley DB 提供真正的交易支持,这可能是它最强大的功能。访问您的 Subversion 存储库的多个进程不必担心意外地覆盖彼此的数据。事务系统提供的隔离使得对于任何给定的操作,Subversion 存储库代码看到的是数据库的静态视图——而不是一个不断被其他进程更改的数据库——并且可以根据该视图做出决策。如果做出的决策恰好与另一个进程正在做的事情冲突,整个操作将被回滚,就好像它从未发生过一样,并且 Subversion 会优雅地针对数据库的新的、更新的(但仍然是静态的)视图重新尝试该操作。
Berkeley DB 的另一个很棒的功能是 热备份——在不将数据库“离线”的情况下备份数据库环境的能力。我们将在 名为“存储库备份”的部分 中讨论如何备份您的存储库,但能够在没有任何停机时间的情况下创建存储库的完全功能副本的好处应该是显而易见的。
在正确使用的情况下,Berkeley DB 也是一个非常可靠的数据库系统。Subversion 使用 Berkeley DB 的日志记录功能,这意味着数据库首先将要进行的任何修改的描述写入磁盘日志文件,然后进行修改本身。这样做是为了确保如果出现任何问题,数据库系统可以备份到以前的 检查点——日志文件中已知不损坏的位置——并重放事务,直到数据恢复到可使用状态。有关 Berkeley DB 日志文件的更多信息,请参见 名为“管理磁盘空间”的部分。
但是,每一朵玫瑰都有它的刺,因此我们必须注意到 Berkeley DB 的一些已知限制。首先,Berkeley DB 环境不可移植。您不能简单地将一个在 Unix 系统上创建的 Subversion 存储库复制到 Windows 系统上并期望它能正常工作。虽然 Berkeley DB 数据库格式的大部分与体系结构无关,但环境的其他方面并非如此。其次,Subversion 以一种在 Windows 95/98 系统上无法运行的方式使用 Berkeley DB——如果您需要在 Windows 机器上存放一个由 BDB 支持的存储库,请坚持使用 Windows 2000 或更高版本。
虽然 Berkeley DB 承诺在满足特定规范的网络共享上表现正确,[30] 但大多数网络文件系统类型和设备实际上 不 满足这些要求。并且在任何情况下,您都不能允许位于网络共享上的 BDB 支持的存储库被该共享的多个客户端同时访问(这往往是存储库位于网络共享上的主要原因)。
如果您尝试在不兼容的远程文件系统上使用 Berkeley DB,结果是不可预测的——您可能会立即看到神秘的错误,或者在几个月后才发现您的存储库数据库已细微损坏。对于需要位于网络共享上的存储库,您应该认真考虑使用 FSFS 数据存储。
最后,由于 Berkeley DB 是一个直接链接到 Subversion 的库,因此它比典型的关系数据库系统更容易受到中断的影响。例如,大多数 SQL 系统都有一个专用服务器进程来协调对所有表的访问。如果访问数据库的程序由于某种原因崩溃,数据库守护进程会注意到连接丢失并清理任何残留的混乱。并且由于数据库守护进程是唯一访问表的进程,因此应用程序无需担心权限冲突。然而,Berkeley DB 并非如此。Subversion(以及使用 Subversion 库的程序)直接访问数据库表,这意味着程序崩溃可能会使数据库处于暂时不一致、无法访问的状态。发生这种情况时,管理员需要请求 Berkeley DB 恢复到检查点,这有点令人讨厌。除了崩溃的进程外,其他一些因素也会导致存储库“卡住”,例如程序在数据库文件的所有权和权限方面发生冲突。
Berkeley DB 4.4 为 Subversion 带来了(到 Subversion 1.4 及更高版本)自动且透明地恢复需要恢复的 Berkeley DB 环境的能力。当 Subversion 进程附加到存储库的 Berkeley DB 环境时,它使用一些进程统计机制来检测以前进程的任何不干净断开连接,执行任何必要的恢复,然后继续执行,就好像什么都没发生一样。这并不能完全消除存储库卡住的实例,但它确实大大减少了从卡住状态中恢复所需的人为干预。
因此,虽然 Berkeley DB 存储库非常快且可扩展,但最好由作为单个用户运行的单个服务器进程使用——例如 Apache 的 httpd 或 svnserve(请参见 第 6 章,服务器配置)——而不是通过 file:// 或 svn+ssh:// URL 作为多个不同的用户访问它。如果直接将 Berkeley DB 存储库用作多个用户,请务必阅读 名为“支持多种存储库访问方法”的部分。
在 2004 年年中,出现了第二种类型的存储库存储系统,它根本不使用数据库。FSFS 存储库将与修订相关的更改存储在一个文件中,因此存储库的所有修订都可以在一个包含编号文件的子目录中找到。事务在单独的子目录中以单个文件的形式创建。完成后,事务文件将被重命名并移动到修订目录,从而保证提交是原子的。并且由于修订文件是永久且不变的,因此存储库也可以在“热”状态下备份,就像 BDB 支持的存储库一样。
FSFS 修订文件描述了修订的目录结构、文件内容以及相对于其他修订树中文件的增量。与 Berkeley DB 数据库不同,这种存储格式在不同的操作系统之间是可移植的,并且对 CPU 架构不敏感。由于没有使用日志记录或共享内存文件,因此存储库可以安全地通过网络文件系统访问,并在只读环境中进行检查。缺乏数据库开销也意味着存储库的总大小会稍微小一些。
FSFS 也具有不同的性能特征。在提交包含大量文件的目录时,FSFS 能够更快地追加目录条目。另一方面,FSFS 将文件的最新版本作为对早期版本的增量写入,这意味着检出最新的树比获取存储在 Berkeley DB HEAD 修订中的完整文本要慢一些。FSFS 在完成提交时也存在更长的延迟,这在极端情况下会导致客户端在等待响应时超时。
然而,最重要的区别是 FSFS 在出现问题时对“卡住”的免疫力。如果使用 Berkeley DB 数据库的进程遇到权限问题或突然崩溃,数据库可能会处于不可用状态,直到管理员恢复它。如果相同的场景发生在使用 FSFS 存储库的进程上,存储库根本不会受到影响。最糟糕的是,一些事务数据会遗留下来。
反对 FSFS 的唯一真正论点是,与 Berkeley DB 相比,它的相对不成熟。与拥有多年历史、拥有自己的专用开发团队,现在又拥有 Oracle 强大的名称的 Berkeley DB 不同,[31] FSFS 是一个更新得多的工程。在 Subversion 1.4 之前,它仍然存在一些非常严重的数据完整性错误,这些错误虽然只在极少数情况下才会触发,但确实会发生。也就是说,FSFS 迅速成为一些最大的公共和私有 Subversion 存储库的首选后端,并承诺为整个 Subversion 降低入门门槛。
[27] 无论是出于无知还是对如何获得合法软件开发指标的错误考虑,全局修订号都是一件愚蠢的事情,不 应该成为您决定如何组织项目和存储库时要权衡的事情。
[28] trunk、tags 和 branches 三重奏有时被称为“TTB 目录”。
[29] 如果 Jack Repenning 有话要说的话,它通常被发音为“fuzz-fuzz”。(然而,本书假定读者正在思考“eff-ess-eff-ess”。)
[30] Berkeley DB 要求底层文件系统实现严格的 POSIX 锁定语义,更重要的是,能够将文件直接映射到进程内存中。
[31] Oracle 在 2006 年情人节收购了 Sleepycat 及其旗舰软件 Berkeley DB。