2. Subversion 的版本控制

We've mentioned already that Subversion is a modern, network-aware version control system. As we described in 第 1 节 “版本控制基础概念” (our high-level version control overview), a repository serves as the core storage mechanism for Subversion's versioned data, and it's via working copies that users and their software programs interact with that data. In this section, we'll begin to introduce the specific ways in which Subversion implements version control.

2.1. Subversion 版本库

Subversion implements the concept of a version control repository much as any other modern version control system would. Unlike a working copy, a Subversion repository is an abstract entity, able to be operated upon almost exclusively by Subversion's own libraries and tools. As most of a user's Subversion interactions involve the use of the Subversion client and occur in the context of a working copy, we spend the majority of this book discussing the Subversion working copy and how to manipulate it. For the finer details of the repository, though, check out 第 5 章 版本库管理.

2.2. 修订版本

A Subversion client commits (that is, communicates the changes made to) any number of files and directories as a single atomic transaction. By atomic transaction, we mean simply this: either all of the changes are accepted into the repository, or none of them is. Subversion tries to retain this atomicity in the face of program crashes, system crashes, network problems, and other users' actions.

Each time the repository accepts a commit, this creates a new state of the filesystem tree, called a revision. Each revision is assigned a unique natural number, one greater than the number assigned to the previous revision. The initial revision of a freshly created repository is numbered 0 and consists of nothing but an empty root directory.

图 1.6 “Tree changes over time”可以更形象的描述版本库,想象有一组修订号,从 0 开始,从左到右,每一个修订号有一个目录树挂在它下面,每一个树好像是一次提交后的版本库快照

图 1.6. Tree changes over time

Tree changes over time

2.3. 版本库的地址

Subversion client programs use URLs to identify versioned files and directories in Subversion repositories. For the most part, these URLs use the standard syntax, allowing for server names and port numbers to be specified as part of the URL.

  • http://svn.example.com/svn/project
  • http://svn.example.com:9834/repos

Subversion repository URLs aren't limited to only the http:// variety. Because Subversion offers several different ways for its clients to communicate with its servers, the URLs used to address the repository differ subtly depending on which repository access mechanism is employed. 表 1.1 “版本库访问 URL” describes how different URL schemes map to the available repository access methods. For more details about Subversion's server options, see 第 6 章 服务配置.

表 1.1. 版本库访问 URL

模式 访问方法
file:/// 直接版本库访问(本地磁盘)
http:// 通过配置Subversion的Apache服务器的WebDAV协议
https:// Same as http://, but with SSL encryption
svn:// 通过定制的协议访问 svnserve 服务器
svn+ssh:// Same as svn://, but through an SSH tunnel

Subversion's handling of URLs has some notable nuances. For example, URLs containing the file:// access method (used for local repositories) must, in accordance with convention, have either a server name of localhost or no server name at all:

  • file:///var/svn/repos
  • file://localhost/var/svn/repos

同样,在 Windows 平台下使用 file:// 模式时需要使用一个非正式的标准语法来访问本机上不在同一个磁盘分区中的版本库。下面的任意一个 URL 路径语法都可以工作,其中的 X 表示版本库所在的磁盘分区:

  • file:///X:/var/svn/repos
  • file:///X|/var/svn/repos

Note that a URL uses forward slashes even though the native (non-URL) form of a path on Windows uses backslashes. Also note that when using the file:///X|/ form at the command line, you need to quote the URL (wrap it in quotation marks) so that the vertical bar character is not interpreted as a pipe.

[注意] 注意

也必须意识到 Subversion 的 file:// URL 不能在普通的 web 服务器中工作。当你尝试在 web 服务器查看一个 file:// URL 时,它会通过直接检测文件系统读取和显示那个位置的文件内容,但是 Subversion 的资源存在于虚拟文件系统(见第 1.1 节 “版本库层”)中,你的浏览器不会理解怎样读取这个文件系统。

The Subversion client will automatically encode URLs as necessary, just like a web browser does. For example, the URL http://host/path with space/project/españa — which contains both spaces and upper-ASCII characters — will be automatically interpreted by Subversion as if you'd provided http://host/path%20with%20space/project/espa%C3%B1a. If the URL contains spaces, be sure to place it within quotation marks at the command line so that your shell treats the whole thing as a single argument to the program.

There is one notable exception to Subversion's handling of URLs which also applies to its handling of local paths in many contexts, too. If the final path component of your URL or local path contains an at sign (@), you need to use a special syntax—described in 第 2 节 “Peg 和实施修订版本”—in order to make Subversion properly address that resource.

In Subversion 1.6, a new caret (^) notation was introduced as a shorthand for the URL of the repository's root directory. For example, you can use the ^/tags/bigsandwich/ to refer to the URL of the /tags/bigsandwich directory in the root of the repository. Note that this URL syntax works only when your current working directory is a working copy—the command-line client knows the repository's root URL by looking at the working copy's metadata. Also note that when you wish to refer precisely to the root directory of the repository, you must do so using ^/ (with the trailing slash character), not merely ^.

2.4. Subversion 的工作副本

一个 Subversion工 作副本是你本地机器上的一个普通目录,保存着一些文件,你可以任意的编辑文件,而且如果是源代码文件,你可以像平常一样编译,你的工作副本是你的私有工作区,在你明确的做了特定操作之前,Subversion 不会把你的修改与其他人的合并,也不会把你的修改展示给别人,你甚至可以拥有同一个项目的多个工作副本。

当你在工作副本作了一些修改并且确认它们工作正常之后,Subversion 提供了一个命令可以发布你的修改给项目中的其他人(通过写到版本库),如果别人发布了各自的修改,Subversion 提供了手段可以把这些修改与你的工作目录进行合并(通过读取版本库)。

工作副本也包括一些由 Subversion 创建并维护的额外文件,用来协助执行命令。通常情况下,你的工作副本的每个文件夹都有一个以 .svn 为名的文件夹,也被叫做工作副本的管理目录,这个目录里的文件能够帮助 Subversion 识别哪些文件做过修改,哪些文件相对于别人的工作已经过期。

2.4.1. 工作副本的工作方式

For each file in a working directory, Subversion records (among other things) two essential pieces of information:

  • 作为工作文件基准的版本(叫做文件的工作版本)

  • 本地副本最近一次被版本库更新的时间戳。

给定这些信息,通过与版本库通讯,Subversion可以告诉我们工作文件是处于如下四种状态的那一种:

未修改且是当前的

文件在工作目录里没有修改,在工作版本之后没有修改提交到版本库。svn commit 操作不做任何事情,svn update 不做任何事情。

本地已修改且是当前的

在工作目录已经修改,从基本修订版本之后没有修改提交到版本库。本地修改没有提交,因此 svn commit 会成功提交,svn update 不做任何事情。

本地未修改,已过时

这个文件在工作目录没有修改,但在版本库中已经修改了。这个文件最终将更新到最新版本,成为当时的公共修订版本。svn commit 不做任何事情,svn update 将会取得最新的版本到工作副本。

本地已修改,已过时

这个文件在工作目录和版本库都得到修改。一个 svn commit 将会失败,这个文件必须首先更新,svn update 命令会合并公共和本地修改,如果 Subversion 不可以自动完成,将会让用户解决冲突。

2.4.2. 工作副本基本交互操作

一个典型的 Subversion 版本库经常包含许多项目的文件(或者说源代码),通常每一个项目都是版本库的子目录,在这种布局下,一个用户的工作副本往往对应版本库的的一个子目录。

举一个例子,你的版本库包含两个软件项目,paintcalc。每个项目在它们各自的顶级子目录下,见图 1.7 “版本库的文件系统”

图 1.7. 版本库的文件系统

版本库的文件系统

To get a working copy, you must check out some subtree of the repository. (The term check out may sound like it has something to do with locking or reserving resources, but it doesn't; it simply creates a working copy of the project for you.) For example, if you check out /calc, you will get a working copy like this:

$ 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  button.c integer.c .svn/
$

列表中的 A 表示 Subversion 增加了一些条目到工作副本,你现在有了一个 /calc 的个人拷贝,有一个附加的目录—.svn—保存着前面提及的 Subversion 需要的额外信息。

假定你修改了 button.c,因为 .svn 目录记录着文件的修改日期和原始内容,Subversion 可以告诉你已经修改了文件,然而,在你明确告诉它之前,Subversion 不会将你的改变公开,将改变公开的操作被叫做提交(committing,或者是检入)修改到版本库。

将你的修改发布给别人,你可以使用 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 可以通过使用 svn update 命令,要求 Subversion 更新她的工作副本。这将结合你和所有其他人在她上次更新之后的改变到她的工作副本。

$ pwd
/home/sally/calc
$ ls -A
Makefile button.c integer.c .svn/
$ svn update
U    button.c
Updated to revision 57.
$

svn update 命令的输出表明 Subversion 更新了 button.c 的内容,注意,Sally 不必指定要更新的文件,subversion 利用 .svn 以及版本库的进一步信息决定哪些文件需要更新。

2.4.3. 混合版本的工作副本

As a general principle, Subversion tries to be as flexible as possible. One special kind of flexibility is the ability to have a working copy containing files and directories with a mix of different working revision numbers. Subversion working copies do not always correspond to any single revision in the repository; they may contain files from several different revisions. For example, suppose you check out a working copy from a repository whose most recent revision is 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 会把他的 Makefile 的修订号设为 6 来表明它是最新的,所以你在工作副本顶级目录作一次干净的更新,会使得所有内容对应版本库的同一修订版本。

2.4.3.1. 更新和提交是分开的

One of the fundamental rules of Subversion is that a push action does not cause a pull nor vice versa. Just because you're ready to submit new changes to the repository doesn't mean you're ready to receive changes from other people. And if you have new changes still in progress, svn update should gracefully merge repository changes into your own, rather than forcing you to publish them.

这个规则的主要副作用就是,工作副本需要记录额外的信息来追踪混合修订版本,并且也需要能容忍这种混合,当目录本身也是版本化的时候情况更加复杂。

举个例子,假定你有一个工作副本,修订版本号是10。你修改了 foo.html,然后执行 svn commit,在版本库里创建了修订版本15。当成功提交之后,许多用户希望工作副本完全变成修订版本15,但是事实并非如此。修订版本从10到15会发生任何修改,可是客户端在运行 svn update 之前不知道版本库发生了怎样的改变,svn commit 不会拖出任何新的修改。另一方面,如果 svn commit 会自动下载最新的修改,可以使得整个工作副本成为修订版本15—但是,那样我们会打破完全分开的原则。因此,Subversion 客户端最安全的方式是标记一个文件— foo.html —为修订版本15,工作副本余下的部分还是修订版本10。只有运行 svn update 才会下载最新的修改,整个工作副本被标记为修订版本15。

2.4.3.2. 混合修订版本很常见

事实上,每次运行 svn commit,你的工作拷贝都会进入混合多个修订版本的状态,刚刚提交的文件会比其他文件有更高的修订版本号。经过多次提交(其间没有更新),你的工作副本会完全是混合的修订版本。即使只有你一个人使用版本库,你依然会见到这个现象。为了检查混合工作修订版本,可以使用 svn status 命令的选项 --verbose (详细信息见第 4.3.1 节 “查看你的修改概况”)。

通常,新用户对于工作副本的混合修订版本一无所知,这会让人糊涂,因为许多客户端命令对于所检验条目的修订版本很敏感。例如 svn log 命令显示一个文件或目录的历史修改信息(见第 5.2 节 “产生历史修改列表”),当用户对一个工作副本对象调用这个命令,他们希望看到这个对象的整个历史信息。但是如果这个对象的修订版本已经相当老了(通常因为很长时间没有运行 svn update),此时会显示比这个对象更老的历史。

2.4.3.3. 混合版本很有用

如果你的项目十分复杂,有时候你会发现强制工作副本的一部分回溯到过去非常有用(或者更新到过去的某个修订版本),你将在第 2 章 基本使用学习到如何这样做。或许你很希望测试某一子目录下某一子模块的早期版本,又或是要测试一个 bug 什么时候发生,这是版本控制系统像时间机器的一个方面—这个特性允许工作副本的任何一个部分在历史中前进或后退。

2.4.3.4. 混合版本有限制

无论你如何在工作副本中利用混合修订版本,这种灵活性还是有限制的。

首先,你不可以提交一个不是完全最新的文件或目录,如果有个新的版本存在于版本库,你的删除操作会被拒绝,这防止你不小心破坏你没有见到的东西。

第二,如果目录已经不是最新的了,你不能提交一个目录的元数据更改。你将会在第 3 章 高级主题学习附加属性,一个目录的工作修订版本定义了许多条目和属性,因而对一个过期的版本提交属性会破坏一些你没有见到的属性。