2.锁定

Posted on Posted in 4.高级主题

锁定

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的工作,并组合出一个挡风玻璃坏掉的红色Mustang。

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

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

  • 顺序访问资源。允许用户得到一个排他的修改文件权,这个用户可以确定不可合并的修改不会被浪费—他对这个修改的提交会成功。

  • 辅助交流。通过要求用户对某个版本化对象串行工作,用户可以知道对象正在被别人修改,这样可以防止浪费精力和时间去修改一个不可合并和提交的对象。

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

“锁定”的三种含义

在本小节,和几乎本书的每一个地方“lock”和“locking”描述了一种避免用户之间冲突提交的排他机制,但是佷不幸,Subversion中还有另外两种锁,因此需要在本书格外关心。

第一种是工作拷贝锁,Subversion内部用来防止不同客户端同时操作同一份工作拷贝的锁,这种锁使用svn status输出中第三列出现的L表示,可以使用svn cleanup删除,“有时你只需要清理”一节有介绍。

第二种,数据库锁,在Berkeley DB后端内部使用,防止多个程序访问数据库发生冲突,一个导致版本库“楔住”的错误发生后产生,“Berkeley DB 恢复”一节有描述。

在发生问题之前你完全可以忘记上面两种锁,在本书,“锁定”意味着第一种锁,除非是在从上下文中十分明确或明确指出的。

创建锁定

在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不会自动强制启动你喜欢的编辑器,锁定注释是可选的,但是为了方便交流我们还是推荐使用。

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

就像你看到的,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的缩写),表明了拥有锁定令牌。

关于锁定令牌

一个锁不是一个认证令牌,而是一个授权令牌,这个令牌不是一个受保护的秘密,事实上,任何人都可以通过svn info URL发现这个唯一令牌。一个锁定令牌只有在工作拷贝中才有特别的意义,它是锁定建立在这个工作拷贝的证据,而不是其它用户在其他地方,仅仅检验锁定拥有者还不能防止出现意外。

例如,你在办公室电脑上锁定了一个文件,或许修改正在进行中。很有可能在你的家用计算机上的一个工作拷贝(或别的Subversion客户端)里你又不小心修改了同一个文件,仅仅因为检验了你就是锁定的拥有者。换句话说,锁定令牌防止你通过一个Subversion相关软件的工作破坏另一个的工作。(在我们的例子里,如果你真的需要在另一个工作拷贝修改这个文件,你必须打破锁定再重新锁定文件。)

现在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 /usr/local/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 /usr/local/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会注意到所有死掉的锁定并且把它们从工作拷贝中删除掉。

锁定策略

不同的系统有不同的锁定限制程度的观念。有些人认为锁定必须不顾任何代价的严格执行,只有原始的创建者和管理员可以释放。他们认为如果有人打破了锁定,混乱就会放任,锁定就完全失去了意义。另外一些人认为锁定是第一个和最首要的交流工具,如果用户经常的打破别人的锁定,代表了团队的文化失败和软件之外的问题。

Subversion缺省是比较“宽松的”方式,但也允许管理员创建钩子脚本来建立严格的控制策略。具体来说,pre-lock和pre-unlock钩子允许管理员决定什么时候创建和释放锁定。根据锁定是否已经存在,这两个钩子脚本可以决定是否允许特定用户打破或窃取锁定。也有post-lock和post-unlock钩子,可以用来发送锁定动作的通知邮件。关于版本库钩子的更多信息可以看“实现版本库钩子”一节

锁定交流

我们已经见到了如何利用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] 除非是,或许一个经典的火神精神融合。