是时候从抽象转到具体了,在本小节,我们会展示一个 Subversion 真实使用的例子。
正如我们在整本书里描述的,Subversion 使用 URL 来识别 Subversion 版本库中的版本化资源,通常情况下,这些 URL 使用标准的语法,允许服务器名称和端口作为 URL 的一部分:
$ svn checkout http://svn.example.com:9834/repos …
但 是Subversion 处理 URL 的一些细微的不同之处需要注意,例如,使用 file:// 访问方法的
URL (用来访问本地版本库)必须与习惯一致,可以包括一个 localhost 服务器名或者没有服务器名:
$ svn checkout file:///var/svn/repos … $ svn checkout file://localhost/var/svn/repos …
同样,在 Windows 平台下使用 file://
模式时需要使用一个非正式的“标准”语法来访问本机上不在同一个磁盘分区中的版本库。下面的任意一个 URL
路径语法都可以工作,其中的 X 表示版本库所在的磁盘分区:
C:\> svn checkout file:///X:/var/svn/repos … C:\> svn checkout "file:///X|/var/svn/repos" …
在第二个语法里,你需要使用引号包含整个 URL,这样竖线字符才不会被解释为管道。当然,也要注意 URL 使用普通的斜线而不是 Windows 本地(不是 URL)的反斜线。
|
注意 |
|---|---|
|
也必须意识到 Subversion 的 |
最后,必须注意 Subversion 的客户端会根据需要自动编码 URL,这一点和一般的 web 浏览器一样,举个例子,如果一个 URL 包含了空格或是一个字符编码大于 128 的 ASCII 字符:
$ svn checkout "http://host/path with space/project/españa"
Subversion会封装这些不安全的字符,并且会像你输入了这些字符一样工作:
$ svn checkout http://host/path%20with%20space/project/espa%C3%B1a
如果 URL 包含空格,一定要使用引号,这样你的脚本才会把它做一个单独的 svn 参数。
In Subversion 1.6, a new caret (^) notation was
introduced as a shorthand for “the URL of the repository's root
directory”. For example:
$ svn list ^/tags/bigsandwich/
In this example, we're specifying a URL for the
/tags/bigsandwich directory in the root of the
repository. Note that this URL syntax only works when
your current working directory is a working copy—the commandline
client knows the repository's root URL by looking at the working copy's
metadata.
你已经阅读过了关于工作副本的内容;现在我们要讲一讲客户端怎样建立和使用它。
一个 Subversion工 作副本是你本地机器上的一个普通目录,保存着一些文件,你可以任意的编辑文件,而且如果是源代码文件,你可以像平常一样编译,你的工作副本是你的私有工作区,在你明确的做了特定操作之前,Subversion 不会把你的修改与其他人的合并,也不会把你的修改展示给别人,你甚至可以拥有同一个项目的多个工作副本。
当你在工作副本作了一些修改并且确认它们工作正常之后,Subversion 提供了一个命令可以“发布”你的修改给项目中的其他人(通过写到版本库),如果别人发布了各自的修改,Subversion 提供了手段可以把这些修改与你的工作目录进行合并(通过读取版本库)。
工作副本也包括一些由 Subversion 创建并维护的额外文件,用来协助执行命令。通常情况下,你的工作副本的每个文件夹都有一个以
.svn
为名的文件夹,也被叫做工作副本的管理目录,这个目录里的文件能够帮助 Subversion
识别哪些文件做过修改,哪些文件相对于别人的工作已经过期。
一个典型的 Subversion 版本库经常包含许多项目的文件(或者说源代码),通常每一个项目都是版本库的子目录,在这种布局下,一个用户的工作副本往往对应版本库的的一个子目录。
举一个例子,你的版本库包含两个软件项目,paint 和
calc。每个项目在它们各自的顶级子目录下,见图 1.6 “版本库的文件系统”。
为了得到一个工作副本,你必须检出(check
out)版本库的一个子树(术语“check
out”听起来像是锁定或者保留资源,实际上不是,只是简单的得到一个项目的私有拷贝),举个例子,检出
/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 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 以及版本库的进一步信息决定哪些文件需要更新。
svn commit 操作可以作为一个原子事务,发布任意数量文件和目录的修改。在你的工作副本里,你可以改变文件内容, 删除, 改名,以及复制文件和目录,然后作为一个原子事务提交。
原子事务的意思是:要么所有的改变发生,要么都不发生。Subversion 努力保持原子性,以应对程序错误, 系统错误, 网络问题和其它用户行为。
每当版本库接受了一个提交,文件系统进入了一个新的状态,叫做一次修订(revision),每一个修订版本被赋予一个独一无二的自然数,一个比一个大,初始修订号是 0,只创建了一个空目录,没有任何内容。
图 1.7 “版本库”可以更形象的描述版本库,想象有一组修订号,从 0 开始,从左到右,每一个修订号有一个目录树挂在它下面,每一个树好像是一次提交后的版本库“快照”。
需要特别注意的是,工作副本并不一定对应版本库中的单个修订版本,他们可能包含多个修订版本的文件。举个例子,你从版本库检出一个工作副本,最近的修订号是 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
来表明它是最新的,所以你在工作副本顶级目录作一次干净的更新,会使得所有内容对应版本库的同一修订版本。
对于工作副本的每一个文件,Subversion在管理区域.svn/记录两项关键的信息:
作为工作文件基准的版本(叫做文件的工作版本)
本地副本最近一次被版本库更新的时间戳。
给定这些信息,通过与版本库通讯,Subversion可以告诉我们工作文件是处于如下四种状态的那一种:
文件在工作目录里没有修改,在工作版本之后没有修改提交到版本库。svn commit 操作不做任何事情,svn update 不做任何事情。
在工作目录已经修改,从基本修订版本之后没有修改提交到版本库。本地修改没有提交,因此 svn commit 会成功提交,svn update 不做任何事情。
这个文件在工作目录没有修改,但在版本库中已经修改了。这个文件最终将更新到最新版本,成为当时的公共修订版本。svn commit 不做任何事情,svn update 将会取得最新的版本到工作副本。
这个文件在工作目录和版本库都得到修改。一个 svn commit 将会失败,这个文件必须首先更新,svn update 命令会合并公共和本地修改,如果 Subversion 不可以自动完成,将会让用户解决冲突。
这看起来需要记录很多事情,但是 svn status 命令可以告诉你工作拷贝中文件的状态,关于此命令更多的信息,请看第 4.3.1 节 “查看你的修改概况”。
作为一个普遍原理,Subversion 努力做到尽可能的灵活,一个特殊的灵活特性就是让工作拷贝包含不同工作修订版本的文件和目录,不幸的是,这个灵活性会让许多新用户感到迷惑。如果上一个混合修订版本的例子让你感到困惑,这里是一个为何有这种特性和如何利用这个特性的基础介绍。
Subversion 有一个基本原则就是一个“推”动作不会导致“拉”,反之亦然,因为你准备好了提交你的修改并不意味着你已经准备好了从其他人那里接受修改。如果你的新的修改还在进行,svn update 将会优雅的合并版本库的修改到你的工作副本,而不会强迫将修改发布。
这个规则的主要副作用就是,工作副本需要记录额外的信息来追踪混合修订版本,并且也需要能容忍这种混合,当目录本身也是版本化的时候情况更加复杂。
举个例子,假定你有一个工作副本,修订版本号是10。你修改了 foo.html,然后执行
svn
commit,在版本库里创建了修订版本15。当成功提交之后,许多用户希望工作副本完全变成修订版本15,但是事实并非如此。修订版本从10到15会发生任何修改,可是客户端在运行
svn update 之前不知道版本库发生了怎样的改变,svn commit
不会拖出任何新的修改。另一方面,如果 svn commit
会自动下载最新的修改,可以使得整个工作副本成为修订版本15—但是,那样我们会打破“推”和“拉”完全分开的原则。因此,Subversion
客户端最安全的方式是标记一个文件— foo.html
—为修订版本15,工作副本余下的部分还是修订版本10。只有运行 svn update
才会下载最新的修改,整个工作副本被标记为修订版本15。
事实上,每次运行 svn
commit,你的工作拷贝都会进入混合多个修订版本的状态,刚刚提交的文件会比其他文件有更高的修订版本号。经过多次提交(其间没有更新),你的工作副本会完全是混合的修订版本。即使只有你一个人使用版本库,你依然会见到这个现象。为了检查混合工作修订版本,可以使用
svn status 命令的选项 --verbose (详细信息见第 4.3.1 节 “查看你的修改概况”)。
通常,新用户对于工作副本的混合修订版本一无所知,这会让人糊涂,因为许多客户端命令对于所检验条目的修订版本很敏感。例如 svn log 命令显示一个文件或目录的历史修改信息(见第 5.1 节 “产生历史修改列表”),当用户对一个工作副本对象调用这个命令,他们希望看到这个对象的整个历史信息。但是如果这个对象的修订版本已经相当老了(通常因为很长时间没有运行 svn update),此时会显示比这个对象更老的历史。
如果你的项目十分复杂,有时候你会发现强制工作副本的一部分“回溯”到过去非常有用(或者更新到过去的某个修订版本),你将在第 2 章 基本使用学习到如何这样做。或许你很希望测试某一子目录下某一子模块的早期版本,又或是要测试一个 bug 什么时候发生,这是版本控制系统像“时间机器”的一个方面—这个特性允许工作副本的任何一个部分在历史中前进或后退。
无论你如何在工作副本中利用混合修订版本,这种灵活性还是有限制的。
首先,你不可以提交一个不是完全最新的文件或目录,如果有个新的版本存在于版本库,你的删除操作会被拒绝,这防止你不小心破坏你没有见到的东西。
第二,如果目录已经不是最新的了,你不能提交一个目录的元数据更改。你将会在第 3 章 高级主题学习附加“属性”,一个目录的工作修订版本定义了许多条目和属性,因而对一个过期的版本提交属性会破坏一些你没有见到的属性。