这份文档描述了 Subversion 1.4。如果您正在运行更新版本的 Subversion,我们强烈建议您访问 https://svnbook.subversion.org.cn/ 并查阅与您的 Subversion 版本相对应的版本。

Subversion 实战

现在是时候从抽象转向具体了。在本节中,我们将展示 Subversion 被使用的真实示例。

Subversion 仓库 URL

在本书中,Subversion 使用 URL 来标识 Subversion 仓库中版本化的文件和目录。在大多数情况下,这些 URL 使用标准语法,允许在 URL 中指定服务器名称和端口号。

$ svn checkout http://svn.example.com:9834/repos
…

但是 Subversion 处理 URL 的方式有一些值得注意的细微差别。例如,包含 file:// 访问方法(用于本地仓库)的 URL 必须根据惯例,要么具有 localhost 的服务器名称,要么根本没有服务器名称。

$ svn checkout file:///path/to/repos
…
$ svn checkout file://127.0.0.1/path/to/repos
…

此外,在 Windows 平台上使用 file:// 方案的用户需要使用非官方的“标准”语法来访问与客户端当前工作驱动器不同的驱动器上的仓库。以下两种 URL 路径语法中的任何一种都将在仓库所在的驱动器为 X 的情况下有效。

C:\> svn checkout file:///X:/path/to/repos
…
C:\> svn checkout "file:///X|/path/to/repos"
…

在第二种语法中,您需要引用 URL,以使竖线字符不被解释为管道。此外,请注意,URL 使用正斜杠,即使 Windows 上路径的本机(非 URL)形式使用反斜杠。

注意

Subversion 的 file:// URL 无法像典型的 file:// URL 一样在普通 Web 浏览器中使用。当您尝试在普通 Web 浏览器中查看 file:// URL 时,它会通过直接检查文件系统来读取并显示该位置文件的內容。但是,Subversion 的资源存在于虚拟文件系统中(参见 名为“仓库层”的部分),您的浏览器将无法理解如何与该文件系统交互。

最后,应该注意的是,Subversion 客户端会像 Web 浏览器一样自动对 URL 进行编码。例如,如果 URL 包含空格或高 ASCII 字符

$ svn checkout "http://host/path with space/project/españa"

…那么 Subversion 将转义不安全的字符,并表现得好像您键入了

$ svn checkout http://host/path%20with%20space/project/espa%C3%B1a

如果 URL 包含空格,请确保将其放在引号内,以便您的 shell 将整个内容视为 svn 程序的单个参数。

工作副本

您已经阅读了有关工作副本的信息;现在我们将演示 Subversion 客户端如何创建和使用它们。

Subversion 工作副本是您本地系统上的普通目录树,包含文件集合。您可以随意编辑这些文件,如果它们是源代码文件,您可以像往常一样从它们编译您的程序。您的工作副本是您自己的私人工作区:在您明确告诉它这样做之前,Subversion 永远不会合并其他人的更改,也不会将您自己的更改提供给他人。您甚至可以拥有同一项目的多个工作副本。

在您对工作副本中的文件进行了一些更改并验证了它们正常工作后,Subversion 为您提供了“发布”您的更改到与您一起处理项目的其他人(通过写入仓库)的命令。如果其他人发布了自己的更改,Subversion 为您提供了将这些更改合并到您的工作目录中的命令(通过从仓库读取)。

工作副本还包含一些额外的文件,这些文件由 Subversion 创建和维护,以帮助它执行这些命令。特别是,工作副本中的每个目录都包含一个名为 .svn 的子目录,也称为工作副本 管理目录。每个管理目录中的文件帮助 Subversion 识别哪些文件包含未发布的更改,以及哪些文件相对于其他人的工作过时了。

典型的 Subversion 仓库通常会保存多个项目的的文件(或源代码);通常,每个项目都是仓库文件系统树中的一个子目录。在这种安排中,用户的典型工作副本将通常对应于仓库的特定子树。

例如,假设您有一个包含两个软件项目 paintcalc 的仓库。每个项目都位于自己的顶级子目录中,如 图 1.6,“仓库的文件系统” 所示。

图 1.6. 仓库的文件系统

The repository's filesystem

要获得工作副本,您必须 检出仓库的某些子树。(术语“检出”听起来可能与锁定或预留资源有关,但它没有;它只是为您创建项目的私人副本。)例如,如果您检出 /calc,您将获得这样的工作副本。

$ svn checkout http://svn.example.com/repos/calc
A    calc/Makefile
A    calc/integer.c
A    calc/button.c
Checked out revision 56.

$ ls -A calc
Makefile  integer.c  button.c  .svn/

左侧空白处字母 A 的列表表明 Subversion 将许多项目添加到您的工作副本中。您现在拥有仓库 /calc 目录的个人副本,以及一个额外的条目——.svn——它包含 Subversion 所需的额外信息,如前所述。

假设您对 button.c 进行了一些更改。由于 .svn 目录记录了文件的原始修改日期和内容,因此 Subversion 可以识别出您更改了该文件。但是,在您明确告诉它之前,Subversion 不会公开您的更改。发布更改的行为更常被称为 提交(或 签入)更改到仓库。

要将您的更改发布到其他人,您可以使用 Subversion 的 commit 命令。

$ svn commit button.c -m "Fixed a typo in button.c."
Sending        button.c
Transmitting file data .
Committed revision 57.

现在您对 button.c 的更改已提交到仓库,并附带了一条描述您更改的说明(即您修复了一个拼写错误)。如果另一个用户检出了 /calc 的工作副本,他们将在文件的最新版本中看到您的更改。

假设您有一个合作者 Sally,她与您同时检出了 /calc 的工作副本。当您提交对 button.c 的更改时,Sally 的工作副本保持不变;Subversion 仅在用户请求时修改工作副本。

要使她的项目更新,Sally 可以要求 Subversion 更新她的工作副本,方法是使用 Subversion 的 update 命令。这将把您的更改以及自她检出以来提交的任何其他更改合并到她的工作副本中。

$ pwd
/home/sally/calc

$ ls -A 
.svn/ Makefile integer.c button.c

$ svn update
U    button.c
Updated to revision 57.

来自 svn update 命令的输出表明 Subversion 更新了 button.c 的内容。请注意,Sally 不需要指定要更新哪些文件;Subversion 使用 .svn 目录中的信息以及仓库中的进一步信息来确定哪些文件需要更新。

版本

一个 svn commit 操作将对任意数量的文件和目录的更改发布为一个原子事务。在您的工作副本中,您可以更改文件的內容;创建、删除、重命名和复制文件和目录;然后将一组完整的更改作为原子事务提交。

通过“原子事务”,我们的意思仅仅是:所有更改都发生在仓库中,或者根本没有发生。Subversion 试图在程序崩溃、系统崩溃、网络问题和其他用户的行为面前保持这种原子性。

每次仓库接受提交时,都会创建一个新的文件系统树状态,称为 版本。每个版本都被分配一个唯一的自然数,比前一个版本的大一。新创建的仓库的初始版本编号为零,并且仅包含一个空的根目录。

图 1.7,“仓库” 说明了一种可视化仓库的好方法。想象一个从左到右延伸的版本号数组,从 0 开始。每个版本号都有一个文件系统树悬挂在其下方,并且每个树都是仓库在提交之后的样子的一次“快照”。

图 1.7. 仓库

The repository

重要的是要注意,工作副本并不总是对应于仓库中的任何单个版本;它们可能包含来自多个不同版本的版本。例如,假设您从最新版本为 4 的仓库检出一个工作副本

calc/Makefile:4
     integer.c:4
     button.c:4

目前,这个工作目录与仓库中的版本 4 准确对应。但是,假设您对 button.c 进行了一些更改并提交了该更改。假设没有其他提交发生,您的提交将创建仓库的版本 5,您的工作副本将变成这样

calc/Makefile:4
     integer.c:4
     button.c:5

假设在这一点上,Sally 对 integer.c 进行了一些更改,并创建了版本 6。如果您使用 svn update 来更新您的工作副本,那么它将变成这样

calc/Makefile:6
     integer.c:6
     button.c:6

Sally 对 integer.c 的更改将出现在您的工作副本中,而您对 button.c 的更改仍将保留。在本例中,Makefile 的文本在修订版 4、5 和 6 中完全相同,但 Subversion 将用修订版 6 标记您的工作副本中的 Makefile,以指示它仍然是最新的。因此,在您对工作副本的顶层执行干净的更新后,它通常将与存储库中的一个修订版完全对应。

工作副本如何跟踪存储库

对于工作目录中的每个文件,Subversion 在 .svn/ 管理区域中记录两条基本信息

  • 您的工作文件所基于的修订版(称为文件的 工作修订版),以及

  • 一个时间戳,记录本地副本上次由存储库更新的时间。

有了这些信息,Subversion 通过与存储库通信,可以判断工作文件处于以下四种状态中的哪一种

未更改且是最新的

该文件在工作目录中未更改,并且自其工作修订版以来,该文件没有对存储库进行任何更改。对该文件的 svn commit 不会做任何事情,对该文件的 svn update 也不会做任何事情。

本地更改且是最新的

该文件已在工作目录中更改,并且自上次更新以来,该文件没有对存储库进行任何更改。存在尚未提交到存储库的本地更改,因此对该文件的 svn commit 将成功发布您的更改,而对该文件的 svn update 不会做任何事情。

未更改但已过时

该文件在工作目录中未更改,但已在存储库中更改。该文件最终应该被更新,使其与最新的公共修订版保持一致。对该文件的 svn commit 不会做任何事情,而对该文件的 svn update 将将最新的更改合并到您的工作副本中。

本地更改且已过时

该文件已在工作目录和存储库中更改。对该文件的 svn commit 将失败,并出现“已过时”错误。该文件应先更新;svn update 命令将尝试将公共更改与本地更改合并。如果 Subversion 无法以合理的方式自动完成合并,它会将冲突留给用户来解决。

这听起来似乎需要跟踪很多东西,但是 svn status 命令将显示您工作副本中任何项目的狀態。有关该命令的更多信息,请参见 名为“查看更改概述”的部分

混合修订版工作副本

作为一般原则,Subversion 尽量灵活。一种特殊的灵活性是能够拥有一个工作副本,其中包含具有不同工作修订版号的混合文件和目录。不幸的是,这种灵活性往往会让很多新用户感到困惑。如果前面显示混合修订版的示例让您感到困惑,这里有一个关于该功能存在的原因以及如何使用它的入门知识。

更新和提交是分开的

Subversion 的基本规则之一是,“推送”操作不会导致“拉取”,反之亦然。仅仅因为您已准备好将新更改提交到存储库,并不意味着您已准备好接收来自其他人的更改。如果您还有正在进行的新更改,那么 svn update 应该优雅地将存储库更改合并到您的更改中,而不是强迫您发布它们。

此规则的主要副作用是,它意味着工作副本必须做额外的簿记工作来跟踪混合修订版,并能容忍混合修订版。由于目录本身也是版本化的,因此变得更加复杂。

例如,假设您有一个完全处于修订版 10 的工作副本。您编辑了 foo.html 文件,然后执行了 svn commit,这将在存储库中创建修订版 15。提交成功后,许多新用户会期望工作副本完全处于修订版 15,但事实并非如此!在修订版 10 和 15 之间,存储库中可能发生了任何数量的更改。客户端对存储库中的这些更改一无所知,因为您还没有运行 svn update,并且 svn commit 不会拉取新的更改。另一方面,如果 svn commit 确实 自动下载最新的更改,那么可以将整个工作副本设置为修订版 15,但这样就会违反“推送”和“拉取”是独立操作的基本规则。因此,Subversion 客户端可以做的唯一安全的事情是将一个文件标记为处于修订版 15——foo.html。工作副本的其余部分仍然处于修订版 10。只有通过运行 svn update 才能下载最新的更改,并将整个工作副本标记为修订版 15。

混合修订版很正常

事实上,每次您运行 svn commit 时,您的工作副本最终都会包含一些混合修订版。您刚刚提交的内容被标记为具有比其他所有内容更大的工作修订版。在进行了几次提交(中间没有更新)后,您的工作副本将包含一系列混合修订版。即使您是唯一使用存储库的人,您仍然会看到这种现象。要检查您的混合工作修订版,请使用 svn status --verbose 命令(有关更多信息,请参见 名为“查看更改概述”的部分)。

通常,新用户完全没有意识到他们的工作副本包含混合修订版。这可能会令人困惑,因为许多客户端命令对它们正在检查的项目的工作修订版很敏感。例如,svn log 命令用于显示文件或目录的更改历史记录(有关更多信息,请参见 名为“生成历史更改列表”的部分)。当用户在工作副本对象上调用此命令时,他们希望看到对象的整个历史记录。但是,如果对象的当前工作修订版非常旧(通常是因为 svn update 很久没有运行),那么将显示对象的 较旧 版本的历史记录。

混合修订版很有用

如果您的项目足够复杂,您会发现有时需要强制 回溯(或者,更新到比您已经拥有的修订版更早的修订版)您的工作副本的一部分到更早的修订版;您将在 第 2 章,基本用法 中学习如何做到这一点。也许您想测试子目录中包含的子模块的早期版本,或者也许您想弄清楚特定文件中的错误是什么时候第一次出现的。这是版本控制系统中的“时间机器”功能——允许您将工作副本的任何部分在历史记录中向前和向后移动的功能。

混合修订版存在限制

无论您如何在工作副本中使用混合修订版,这种灵活性都有其局限性。

首先,您不能提交尚未完全更新的文件或目录的删除操作。如果存储库中存在该项目的更新版本,则您的删除尝试将被拒绝,以防止您意外删除尚未看到的更改。

其次,您不能对目录提交元数据更改,除非它已完全更新。您将在 第 3 章,高级主题 中学习如何将“属性”附加到项目。目录的工作修订版定义了一组特定的条目和属性,因此对已过时目录提交属性更改可能会破坏您尚未看到的属性。