锁定

Subversion的“拷贝-修改-合并”模型是以行为基础(如程序源代码 )的项目最佳的协调方式,然而就像什么时候锁定是必需的里说过的,有时候有些人需要“锁定-修改-解锁”模型来代替Subversion的标准模型,当一个文件由二进制数据组成,合并两个修改通常是困难的,Subversion 1.2和以后的版本提供了锁定特性,也就是别的版本控制系统常说的“保留检出”。

Subversion的锁定特性有两个目标:

  • 顺序访问资源。允许用户得到一个排他的修改文件权,如果Harry保留了修改foo.jpg的权利,Sally将不可以提交这个文件的修改。

  • 辅助交流。防止用户进行不可合并的修改,如果Harry已经保留了修改foo.jpg的权利,然后Sally会很容易注意到这个事实而不会去修改这个文件。

Subversion的锁定特性当前限制到文件—还没有可能限制整个目录树的访问。

在Subversion的版本库,一个是一份元数据,可以排它赋予某个用户修改权,这个用户被称作锁的拥有者。每个锁都有一个唯一标识,通常是一长串字符,叫做锁令牌。版本库在一个单独的表里管理锁,提交时强制锁定检查,如果操作会修改或删除文件(或者是删除文件父目录),版本库会要求两份信息:

一个例子是按照顺序。为了描述方便,假定Harry决定修改一个JPEG图像,为了防止其他用户提交这个文件的修改,他使用svn lock命令锁定了版本库的这个文件:

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

$ 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: 2005-03-15 12:43:04 -0600 (Tue, 15 Mar 2005)
Text Last Updated: 2005-06-08 19:23:07 -0500 (Wed, 08 Jun 2005)
Properties Last Updated: 2005-06-08 19:23:07 -0500 (Wed, 08 Jun 2005)
Checksum: 3b110d3b10638f5d1f4fe0f436a5a2a5
Lock Token: opaquelocktoken:0c0f600b-88f9-0310-9e48-355b44d4a58e
Lock Owner: harry
Lock Created: 2005-06-14 17:20:31 -0500 (Tue, 14 Jun 2005)
Lock Comment (1 line):
Editing file for tomorrow's release.

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

第二,锁定成功了。这意味着文件已经锁定了,Harry有了文件最新的版本,如果Harry的工作拷贝文件不是最新的,版本库会拒绝请求,强制Harry执行svn update并重新运行锁定命令。

也要注意到在创建版本库的锁定之后,工作拷贝也缓存了锁定的信息—最重要的是锁定令牌。有锁定令牌是非常重要的,这给了工作拷贝权利利用这个锁的能力。svn status会在文件后面显示一个K(locKed的缩写),表明了拥有锁定令牌。

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

$ whoami
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)

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

$ whoami
harry

$ 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

$ whoami
sally

$ svn status --show-updates
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: 2005-01-25 12:43:04 -0600 (Tue, 25 Jan 2005)
Lock Token: opaquelocktoken:fc2b4dee-98f9-0310-abf3-653ff3226e6b
Lock Owner: harry
Lock Created: 2005-02-16 13:29:18 -0500 (Wed, 16 Feb 2005)
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去询问他应该释放这个锁定,如果他不再,她就要自己强制解除这个锁定或者是找管理员去做。

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

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

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

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

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

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

$ whoami
sally

$ svn status --show-updates
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询问版本库:

$ whoami
harry

$ svn status
     K raisin.jpg

$ svn status --show-updates
     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。如果这个属性附加到一个文件上(属性值无关紧要),这个文件将只读,当用户锁定了文件,并且接受了锁定令牌,文件成了可读写。当锁定被释放—明确的解锁或提交后的释放—文件重新回到只读状态。

根据这个原理,如果一个图像文件有这个属性,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: 2005-06-08 07:29:18 -0500 (Thu, 08 June 2005)
Lock Comment (1 line):
Making some tweaks.  Locking for the next two hours.

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

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

这个系统并不是毫无瑕疵,即使有这个属性,只读提醒也有可能失效。有些程序“偷偷的篡改了”文件的只读属性,悄无声息的允许用户编辑和保存文件,不幸的是,Subversion对此无能为力。