锁定

Subversion的拷贝-修改-合并版本控制模型的关键是其合并算法,也就是如何处理多个用户修改同时修改一个文件产生冲突时的算法。Subversion本身只提供了一个这样的算法,其三方区别算法可以足够聪明的的行粒度的数据处理,Subversion也支持使用外置比较工具(“外置 diff3”一节中有描述),有一些可以做得非常好,或许可以提供以单词或字母粒度的算法。但是,这些工具的共同点是基于文本的,当你讨论非文本文件格式时,这看起来有一点残酷。如果你无法找到一个工具支持这种类型的合并,你的拷贝-修改-合并模型就会遇到麻烦。

让我们看一个使用这个模型的真实例子,Harry和Sally是同一个项目的图形设计师,汽车技工的间接营销。海报的设计一个小车,需要一些主要部分的工作,使用PNG文件格式。海报的布局几乎完成,Harry和Sally都看上了一个从损坏小车得到的特别照片—一个1967的淡蓝色的Ford Mustang,挡泥板有一些溅迹。

现在,作为图像设计的惯例,计划的改变导致车的颜色很重要,所以Sally将工作拷贝更新到HEAD,启动图形编辑软件,修改图像将车的颜色修改为樱桃红,同时Harry那一天特别有灵感,所以决定如果这个车受到更大的撞击可能会有更好的效果。他也更新到HEAD,然后在车挡风玻璃上制作了一些裂痕,他设法在Sally完成前结束修改,因为受到自己不可阻挡天赋的鼓舞,提交了图像。没过多久,Sally结束了她的工作,尝试提交。但是如我们所料,Subversion提交失败,告诉Sally她的图像已经过期了。

这里就是麻烦的地方,如果Harry和Sally修改的是文本文件,她只需要简单得更新工作拷贝,接收Harry的修改。在最坏的情况下,他们会修改文件的同一部分,Sally需要人工解决冲突。但是现在不是文本文件—而是二进制图像,没法估计合并的结果会是什么样子的,已存的软件不可能从基线图像分离出Harry和Sally的工作,并组合出一个挡风玻璃坏掉0的红色Mustang。

很显然,如果能够将Harry和Sally的工作串行话事情会变得平滑,也就是说Harry可以等到Sally的红车然后再画上破坏的挡风玻璃,或者Sally在破坏之后改变颜色。就像在“拷贝-修改-合并 方案”一节讨论的,如果Harry和Sally之间有完美的交流,就不会有这种问题发生。[15]但是作为一种版本控制系统,实际上是一种交流的形式,使得软件遵循非并行编辑的串行化也不是一件坏事,这里Subversion实现了锁定-修改-解锁模型,这里我们要讨论Subversion的锁定特性,与其他版本控制系统的“保留检出”机制类似。

Subversion 的锁定特性为两个主要目的服务:

当我们引用Subversion锁定特性时,这是在讨论一个处理版本化文件的行为特性[16](声明对一个文件排他性修改特权),包括对文件的锁定和解锁(释放排他性修改权限),察看包括文件被谁锁定的报告,以及提醒企图修改锁定文件的用户。在本小节,我们会覆盖锁定特性的大部分内容。

创建锁定

在Subversion的版本库,一个是一份元数据,可以排它赋予某个用户修改权,这个用户被称作锁的拥有者。每个锁都有一个唯一标识,通常是一长串字符,叫做锁令牌。版本库管理锁,控制着锁的创建,权限控制和删除。如果提交包含了修改或者删除锁

为了描述锁的产生,我们回到前面那个关于多个图形设计师共同工作的例子,Harry决定修改一个JPEG图像,为了防止其他用户此时提交这个文件的修改(也是警告别人他正在修改它),他使用svn lock命令锁定了版本库中的这个文件:

$ svn lock banana.jpg -m "Editing file for tomorrow's release."
'banana.jpg' locked by user 'harry'.
$

前一个例子描述了许多新事物,第一,注意Harry在svn lock中使用了--message (-m)选项,类似于svn commitsvn lock命令可以有描述锁定原因的注释(通过--message (-m)--file (-F))。然而不像svn commitsvn lock不会自动强制启动你喜欢的编辑器,锁定注释是可选的,但是为了方便交流我们还是推荐使用。

Secondly, the lock attempt succeeded. This means that the file wasn't already locked, and that Harry had the latest version of the file. If Harry's working copy of the file had been out-of-date, the repository would have rejected the request, forcing Harry to svn update and reattempt the locking command. The locking command would also have failed if the file had already been locked by someone else.

就像你看到的,svn lock打印了锁定成功的确认信息。此时,通过svn statussvn info的输出我们可以看到文件已经锁定。

$ svn status
     K banana.jpg

$ svn info banana.jpg
Path: banana.jpg
Name: banana.jpg
URL: http://svn.example.com/repos/project/banana.jpg
Repository UUID: edb2f264-5ef2-0310-a47a-87b0ce17a8ec
Revision: 2198
Node Kind: file
Schedule: normal
Last Changed Author: frank
Last Changed Rev: 1950
Last Changed Date: 2006-03-15 12:43:04 -0600 (Wed, 15 Mar 2006)
Text Last Updated: 2006-06-08 19:23:07 -0500 (Thu, 08 Jun 2006)
Properties Last Updated: 2006-06-08 19:23:07 -0500 (Thu, 08 Jun 2006)
Checksum: 3b110d3b10638f5d1f4fe0f436a5a2a5
Lock Token: opaquelocktoken:0c0f600b-88f9-0310-9e48-355b44d4a58e
Lock Owner: harry
Lock Created: 2006-06-14 17:20:31 -0500 (Wed, 14 Jun 2006)
Lock Comment (1 line):
Editing file for tomorrow's release.

$

svn info命令不会联系版本库,当对工作拷贝路径应用svn info命令时,可以揭示令牌的一个重要事实—它们缓存在工作拷贝。有锁定令牌是非常重要的,这给了工作拷贝权利利用这个锁的能力。svn status会在文件后面显示一个K(locKed的缩写),表明了拥有锁定令牌。

现在Harry已经锁定了banana.jpg,Sally不能修改或删除这个文件:

$ svn delete banana.jpg
D         banana.jpg
$ svn commit -m "Delete useless file."
Deleting       banana.jpg
svn: Commit failed (details follow):
svn: DELETE of
'/repos/project/!svn/wrk/64bad3a9-96f9-0310-818a-df4224ddc35d/banana.jpg':
423 Locked (http://svn.example.com)
$

但是,当完成了香蕉的黄色渐变,就可以提交文件的修改,因为认证为锁定的拥有者,也因为他的工作拷贝有正确的锁定令牌:

$ svn status
M    K banana.jpg
$ svn commit -m "Make banana more yellow"
Sending        banana.jpg
Transmitting file data .
Committed revision 2201.
$ svn status
$

需要注意到提交之后,svn status显示工作拷贝已经没有锁定令牌了,这是svn commit的标准行为方式—它会遍历工作拷贝(或者从目标列表,如果有列表的话),并且作为提交的一部分发送所有遇到的锁定令牌到服务器。当提交完全成功,前面用到的所有版本库锁定都会被释放—即使是没有提交的文件。这样的原因是不鼓励用户滥用锁定,或者是长时间的保持锁定。例如,假定Harry不小心锁定了images目录的30个文件,因为他不确定要修改什么文件,他最后只修改了四个文件,当他运行svn commit images,会释放所有的30个锁定。

自动释放锁定的特性可以通过svn commit--no-unlock选项关闭,当你要提交文件,同时期望继续修改而必须保留锁定时非常有用。这个特性也可以半永久性的设定,方法是设置运行中config文件(见“运行配置区”一节)的no-unlock = yes

当然,锁定一个文件不会强制一个人要提交修改,任何时候都可以通过运行svn unlock命令释放锁定:

$ svn unlock banana.c
'banana.c' unlocked.

发现锁定

最明显的方式就是因为锁定而不能提交一个文件,最简单的方式是svn status --show-updates

$ svn status -u
M              23   bar.c
M    O         32   raisin.jpg
       *       72   foo.h
Status against revision:     105
$

在这个例子里,Sally可以见到不仅她的foo.h是过期的,而且发现两个计划要提交的文件被锁定了。O符号表示其他人所订了文件。如果她尝试提交,raisin.jpg的锁定会阻止她,Sally会纳闷谁锁定了文件,什么时候,为什么。再一次,svn info拥有答案:

$ svn info http://svn.example.com/repos/project/raisin.jpg
Path: raisin.jpg
Name: raisin.jpg
URL: http://svn.example.com/repos/project/raisin.jpg
Repository UUID: edb2f264-5ef2-0310-a47a-87b0ce17a8ec
Revision: 105
Node Kind: file
Last Changed Author: sally
Last Changed Rev: 32
Last Changed Date: 2006-01-25 12:43:04 -0600 (Sun, 25 Jan 2006)
Lock Token: opaquelocktoken:fc2b4dee-98f9-0310-abf3-653ff3226e6b
Lock Owner: harry
Lock Created: 2006-02-16 13:29:18 -0500 (Thu, 16 Feb 2006)
Lock Comment (1 line):
Need to make a quick tweak to this image.
$

就像svn info可以检验工作拷贝的对象,它也可以检验版本库的对象,如果svn info的主要参数是工作拷贝路径,所有工作拷贝的缓存信息都会显示,发现了锁定就意味着工作拷贝拥有锁定令牌(如果一个文件被另一个用户在另一个工作拷贝锁定,工作拷贝路径上运行svn info不会显示锁定信息)。如果svn info的主参数是URL,就会反映版本库中最新版本的对象信息,任何对锁定的提及描述了当前对象的锁定。

所以在这个特定的例子里,Sally可以看到Harry在二月十六日为了“做修改”而锁定了这个文件,现在已经六月了,她怀疑他可能是忘记了这个锁定,她会打电话给Harry去询问他应该释放这个锁定,如果他不再,她就要自己强制解除这个锁定或者是找管理员去做。

解除和偷窃锁定

版本库锁定并不是神圣不可侵犯的,在Subversion的缺省配置状态,不只是创建者可以释放锁定,任何人都可以。当有其他人期望消灭锁定时,我们称之为打破锁定。

从管理员的位子上很容易打破锁定,svnlooksvnadmin程序都有能力从版本库直接显示和删除锁定。(关于这些工具的信息可以看“管理员的工具箱”一节。)

$ svnadmin lslocks /var/svn/repos
Path: /project2/images/banana.jpg
UUID Token: opaquelocktoken:c32b4d88-e8fb-2310-abb3-153ff1236923
Owner: frank
Created: 2006-06-15 13:29:18 -0500 (Thu, 15 Jun 2006)
Expires: 
Comment (1 line):
Still improving the yellow color.

Path: /project/raisin.jpg
UUID Token: opaquelocktoken:fc2b4dee-98f9-0310-abf3-653ff3226e6b
Owner: harry
Created: 2006-02-16 13:29:18 -0500 (Thu, 16 Feb 2006)
Expires: 
Comment (1 line):
Need to make a quick tweak to this image.

$ svnadmin rmlocks /var/svn/repos /project/raisin.jpg
Removed lock on '/project/raisin.jpg'.
$

更有趣的选项是允许用户互相打破锁定,为此,Sally只需要使用unlock命令的--force选项:

$ svn status -u
M              23   bar.c
M    O         32   raisin.jpg
       *       72   foo.h
Status against revision:     105
$ svn unlock raisin.jpg
svn: 'raisin.jpg' is not locked in this working copy
$ svn info raisin.jpg | grep URL
URL: http://svn.example.com/repos/project/raisin.jpg
$ svn unlock http://svn.example.com/repos/project/raisin.jpg
svn: Unlock request failed: 403 Forbidden (http://svn.example.com)
$ svn unlock --force http://svn.example.com/repos/project/raisin.jpg
'raisin.jpg' unlocked.
$

Sally初始的unlock命令失败了,因为她直接在自己的工作拷贝上运行了svn unlock,而这里没有锁定令牌。为了直接从版本库删除锁定,她需要给svn unlock传递URL参数,她的这一次尝试又失败了,因为她不是锁定的拥有者(也没有锁定令牌)。当她使用了--force选项后,认证和授权的要求被忽略了,远程的锁定被打破了。

当然,简单的打破锁定也许还不够,在这个例子里,Sally不仅想要打破Harry遗忘的锁定,她也希望自己重新锁定。她可以通过运行svn unlock --force紧接着svn lock,但是有可能有人在这两次命令之间锁定了文件,最简单的方式是窃取这个锁定,将打破和重新锁定变成一种原子操作,为此需要运行svn lock--force选项:

$ svn lock raisin.jpg
svn: Lock request failed: 423 Locked (http://svn.example.com)
$ svn lock --force raisin.jpg
'raisin.jpg' locked by user 'sally'.
$

在任何情况下,无论锁定被打破还是窃取,Harry都会感到惊讶。Harry的工作拷贝还保留有原来的锁定令牌,但是锁定已经不存在了,锁定令牌可以说已经死掉了。锁定令牌指代的锁定被打破(版本库中不再存在)或者是窃取了(被另一个锁定代替了),任何一种情况下,Harry都可以使用svn status询问版本库:

$ svn status
     K raisin.jpg
$ svn status -u
     B         32   raisin.jpg
$ svn update
  B  raisin.jpg
$ svn status
$

如果版本库锁定被打破了,svn status --show-updates会在文件旁边显示一个B (Broken)。如果有一个新的锁,就会显示一个T (sTolen)符号。最终,svn update会注意到所有死掉的锁定并且把它们从工作拷贝中删除掉。

锁定交流

我们已经见到了如何利用svn locksvn unlock来创建、释放、打破和窃取锁定,这就满足了顺序访问文件的要求,但是浪费时间这个大问题该如何呢?

例如,假定Harry锁定了一个图片,并开始编辑。同时,几英里之外的Sally希望做同样的工作,她没想到运行svn status --show-updates,她不知道Harry已经锁定了文件。她花费了数小时来修改文件,当她真被提交时发现文件已经被锁定或者是她的文件已经过期了。她的修改不能和Harry的合并,他们中的一人需要抛弃自己的工作,许多时间被浪费了。

Subversion针对此问题的解决方案是提供一种机制,提醒用户在开始编辑以前必须锁定这个文件,这个机制就是提供一种特别的属性--svn:needs-lock。当有这个值时,除非用户锁定这个文件,否则文件一直是只读的。当得到一个锁定令牌(运行svn lock的结果),文件变成可读写,当释放这个锁后,文件又变成只读。

根据这个原理,如果一个图像文件有这个属性,Sally打开编辑文件就会立刻注意到有些特别,大多数程序会在打开只读文件时立刻警告,至少所有的程序会防止她保存修改,这提醒了她编辑之前需要锁定文件,这样她就发现了原来存在的锁定:

$ /usr/local/bin/gimp raisin.jpg
gimp: error: file is read-only!
$ ls -l raisin.jpg
-r--r--r--   1 sally   sally   215589 Jun  8 19:23 raisin.jpg
$ svn lock raisin.jpg
svn: Lock request failed: 423 Locked (http://svn.example.com)
$ svn info http://svn.example.com/repos/project/raisin.jpg | grep Lock
Lock Token: opaquelocktoken:fc2b4dee-98f9-0310-abf3-653ff3226e6b
Lock Owner: harry
Lock Created: 2006-06-08 07:29:18 -0500 (Thu, 08 June 2006)
Lock Comment (1 line):
Making some tweaks.  Locking for the next two hours.
$

提示

我们鼓励用户和管理员都应该给不能根据上下文的文件添加svn:needs-lock属性,这是鼓励好的锁定习惯和防止浪费的主要技术手段。

需要注意到这个属性是依赖于锁定系统的交流工具,不管是否有这个属性,文件都可以锁定。相反的,无论有没有这个属性,并不会要求提交需要首先锁定文件。

这个系统并不是毫无瑕疵,即使有这个属性,只读提醒也有可能失效。有些程序“偷偷的篡改了”文件的只读属性,悄无声息的允许用户编辑和保存文件,不幸的是,Subversion对此无能为力—即时到了现今,还是没有任何工具能够代替人与人的良好交流。[17]



[15] Communication wouldn't have been such bad medicine for Harry and Sally's Hollywood namesakes, either, for that matter.

[16] Subversion目前不允许锁定目录。

[17] Except, perhaps, a classic Vulcan mind-meld.