本文档旨在描述 Subversion 1.6.x 系列。如果您运行的是其他版本的 Subversion,强烈建议您访问 https://svnbook.subversion.org.cn/,并参考适合您 Subversion 版本的文档。

锁定

Subversion 的复制-修改-合并版本控制模型的成败取决于其数据合并算法——特别是当这些算法在尝试解决由多个用户同时修改同一个文件引起的冲突时,其执行效率如何。Subversion 本身只提供了一种这样的算法:一种三路差异算法,该算法足够智能,可以以单行文本的粒度处理数据。Subversion 还允许您使用外部差异工具来补充其内容合并处理(如 “外部 diff3”部分“外部合并”部分 所述),其中一些工具可能做得更好,例如提供单词或单个文本字符的粒度。但这些算法有一个共同点,它们通常只适用于文本文件。当您开始谈论非文本文件格式的内容合并时,情况就开始变得非常糟糕。而当您找不到可以处理这种类型合并的工具时,您就会开始遇到复制-修改-合并模型的问题。

让我们看一个现实生活中的例子,说明这个模型是如何失败的。哈里和莎莉都是图形设计师,他们共同负责一个项目,为一家汽车修理店制作一些营销宣传资料。海报设计中一个关键元素是需要一些车身修复的汽车图像,该图像存储在一个使用 PNG 图像格式的文件中。海报布局几乎已经完成,哈里和莎莉都对他们为损坏的汽车选择的照片感到满意——一辆淡蓝色的 1967 年款福特野马,左前挡泥板不幸地有些变形。

现在,就像图形设计工作中常见的那样,计划发生了变化,导致汽车的颜色成为一个问题。因此,莎莉将她的工作副本更新到 HEAD,启动她的照片编辑软件,开始调整图像,使汽车现在变成了樱桃红。与此同时,哈里当天感觉特别有灵感,他觉得如果汽车看起来遭受了更大的撞击,图像会更有影响力。他也更新到 HEAD,然后在汽车的前挡风玻璃上画了一些裂缝。他设法在莎莉完成她的工作之前完成了他的工作,在欣赏他无可否认的才能的成果后,他提交了修改后的图像。不久之后,莎莉完成了汽车的新外观,并试图提交她的更改。但正如预期的那样,Subversion 提交失败,通知莎莉她的图像版本现在已经过时。

困难之处就在这里。如果哈里和莎莉修改的是一个文本文件,莎莉只需更新她的工作副本,同时接收哈里的更改。在最糟糕的情况下,他们可能修改了文件中的相同区域,莎莉将不得不手动解决冲突。但这些不是文本文件,它们是二进制图像。虽然描述人们期望这种内容合并的结果很简单,但几乎没有机会找到任何足够智能的软件,可以检查这些图形艺术家所使用的共同基线图像、哈里所做的更改和莎莉所做的更改,然后输出一张带有裂缝挡风玻璃的破损红色野马图像!

当然,如果哈里和莎莉将对图像的修改序列化,事情会进行得更加顺利——例如,如果哈里等到莎莉的汽车变成红色后才画他的挡风玻璃裂缝,或者如果莎莉调整了挡风玻璃已经破裂的汽车的颜色。正如 “复制-修改-合并解决方案”部分 所述,如果哈里和莎莉之间存在完美的沟通,大多数这类问题都会完全消失。 [19] 但是,由于版本控制系统实际上是沟通的一种形式,因此让该软件帮助序列化不可并行编辑工作绝非坏事。这就是 Subversion 实现锁定-修改-解锁模型的优势所在。这就是我们要谈论 Subversion 的 锁定功能的地方,它类似于其他版本控制系统的 保留签出 机制。

Subversion 的锁定功能最终是为了最大限度地减少浪费的时间和精力。通过允许用户以编程方式声称对存储库中文件的独占更改权,该用户可以合理地确信,他在不可合并更改上投入的任何精力都不会浪费——他提交这些更改将成功。此外,由于 Subversion 向其他用户传达了特定版本化对象正在进行序列化的事实,因此这些用户可以合理地预期该对象即将被其他人更改。然后,他们也可以避免将时间和精力浪费在不可合并的更改上,这些更改最终将无法提交,因为它们已过时。

在提到 Subversion 的锁定功能时,实际上是在谈论一系列行为,其中包括锁定版本化文件的能力 [20](声称对文件进行修改的独占权),解锁该文件(放弃对文件进行修改的独占权),查看有关哪些文件被锁定以及被谁锁定的报告,在强烈建议编辑前锁定文件的那些文件上添加注释,等等。在本节中,我们将介绍所有这些锁定功能的各个方面。

创建锁定

在 Subversion 存储库中,锁定是授予一个用户对文件进行独占访问权限的元数据。该用户被称为 锁定所有者。每个锁定还具有一个唯一的标识符,通常是一长串字符,称为 锁定令牌。存储库管理锁定,最终处理它们的创建、强制和删除。如果任何提交事务试图修改或删除已锁定的文件(或删除该文件的父目录之一),存储库将要求两条信息——执行提交的客户端必须被认证为锁定所有者,并且锁定令牌必须作为提交过程的一部分提供,作为客户端知道其正在使用哪个锁定的证明。

为了演示锁定创建,让我们回到多个图形设计师共同处理同一个二进制图像文件的例子。哈里决定更改一个 JPEG 图像。为了防止其他人在他修改文件时提交对文件的更改(以及提醒他们他即将更改它),他使用 svn lock 命令锁定存储库中的文件。

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

前面的例子演示了一些新的东西。首先,请注意哈里将 --message (-m) 选项传递给了 svn lock。类似于 svn commitsvn lock 命令可以通过 --message (-m) 或 --file (-F) 来接收注释,以描述锁定文件的原因。但是,与 svn commit 不同的是,svn lock 不会通过启动您喜欢的文本编辑器来要求提供消息。锁定注释是可选的,但仍建议使用,以帮助沟通。

其次,锁定尝试成功。这意味着该文件尚未被锁定,并且哈里拥有该文件的最新版本。如果哈里的文件工作副本已过时,存储库将拒绝该请求,强制哈里进行 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 Root: http://svn.example.com/repos/project
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 status 命令在文件旁边显示一个 K(代表 locKed),表示锁定令牌存在。

现在哈利已经锁定了banana.jpg,莎莉就无法更改或删除该文件。

$ svn delete banana.jpg
D         banana.jpg
$ svn commit -m "Delete useless file."
Deleting       banana.jpg
svn: Commit failed (details follow):
svn: Server sent unexpected return value (423 Locked) in response to DELETE\
 request for '/repos/project/!svn/wrk/64bad3a9-96f9-0310-818a-df4224ddc35d/\
banana.jpg'
$

但是,哈利在调整了香蕉的黄色色调之后,能够将他的更改提交到该文件。这是因为他以锁定的所有者身份进行了身份验证,并且他的工作副本包含正确的锁定令牌。

$ 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 的标准行为——它会在工作副本(或者如果您提供了这样的列表,则是在目标列表中)中搜索本地修改,并将在此过程中遇到的所有锁定令牌作为提交事务的一部分发送到服务器。提交成功完成后,所有提到的存储库锁都会被释放——即使是在未提交的文件上。这样做是为了阻止用户在锁定方面马虎行事,或者长时间持有锁定。如果哈利不小心在名为images 的目录中锁定了 30 个文件,因为他不知道需要更改哪些文件,但只更改了其中的 4 个文件,当他运行svn commit images时,该过程仍将释放所有 30 个锁定。

可以使用 svn commit--no-unlock 选项来覆盖自动释放锁定的行为。此选项最适合在您想要提交更改但仍计划进行更多更改,因此需要保留现有锁定的情况下使用。您还可以通过设置 no-unlock 运行时配置选项(请参阅名为“运行时配置区域”的部分)来将此行为设为默认行为。

当然,锁定文件并不意味着要提交对该文件的更改。可以通过简单的 svn unlock 命令随时释放锁定。

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

发现锁定

当提交由于其他人的锁定而失败时,了解这些锁定非常容易。最简单的方法是运行svn status -u

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

在这个例子中,莎莉不仅可以看到她的foo.h 副本已过时,还可以看到她计划提交的两个已修改文件之一在存储库中被锁定。O 符号代表 其他,这意味着该文件存在锁定,并且由其他人创建。如果她尝试提交,raisin.jpg 上的锁定将阻止提交。莎莉想知道是谁创建了锁定,何时创建以及为什么创建。再次,svn info 有答案。

$ svn info ^/raisin.jpg
Path: raisin.jpg
Name: raisin.jpg
URL: http://svn.example.com/repos/project/raisin.jpg
Repository Root: http://svn.example.com/repos/project
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,则信息反映的是存储库中对象的最新版本,任何对锁定的提及都描述了对象的当前锁定。

因此,在这个特定例子中,莎莉可以看到哈利在 2 月 16 日锁定该文件是为了 快速调整一下。现在已经是 6 月份了,她怀疑哈利可能已经忘记了锁定。她可能会打电话给哈利抱怨并让他释放锁定。如果他不在,她可能会尝试强制解除锁定,或者要求管理员执行此操作。

解除和窃取锁定

存储库锁定并非神圣不可侵犯——在 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'.
$

更有趣的选择是允许用户通过网络解除彼此的锁定。为此,莎莉只需将 --force 传递给 svn unlock 命令即可。

$ 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.
$

现在,莎莉最初的尝试解除锁定失败了,因为她直接在文件的 工作副本上运行了 svn unlock,并且没有锁定令牌。为了直接从存储库中删除锁定,她需要将 URL 传递给 svn unlock。她第一次尝试解除锁定 URL 失败了,因为她无法以锁定所有者的身份进行身份验证(她也没有锁定令牌)。但是,当她传递 --force 时,身份验证和授权要求将被忽略,远程锁定将被解除。

仅仅解除锁定可能还不够。在正在运行的例子中,莎莉可能不仅想要解除哈利早已忘记的锁定,还想为了自己的使用而重新锁定该文件。她可以通过使用带 --forcesvn unlock 以及随后的 svn lock 来实现这一点,但是,在两个命令之间,其他人可能会锁定该文件。更简单的方法是窃取锁定,这包括在一个原子步骤中解除和重新锁定文件。为此,莎莉将 --force 选项传递给 svn lock

$ 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'.
$

无论锁定是被解除还是被窃取,哈利都可能会感到意外。哈利的工作副本仍然包含原始的锁定令牌,但该锁定不再存在。据说锁定令牌是失效的。锁定令牌所代表的锁定要么被解除(不再存在于存储库中),要么被窃取(被另一个锁定替换)。无论哪种情况,哈利都可以通过要求 svn status 联系存储库来查看这一点。

$ svn status
     K  raisin.jpg
$ svn status -u
     B          32   raisin.jpg
Status against revision:     105
$ svn update
  B  raisin.jpg
Updated to revision 105.
$ svn status
$

如果存储库锁定被解除,则svn status --show-updates (-u) 会在文件旁边显示一个 B(已解除)符号。如果存在新的锁定来代替旧的锁定,则会显示一个 T(窃取)符号。最后,svn update 会注意到任何失效的锁定令牌,并将它们从工作副本中删除。

锁定通信

我们已经了解了如何使用 svn locksvn unlock 来创建、释放、解除和窃取锁定。这满足了对文件进行提交访问序列化这一目标。但如何解决更普遍的避免浪费时间的难题呢?

例如,假设哈利锁定了一个图像文件,然后开始编辑它。与此同时,莎莉在千里之外想要做同样的事情。她没有想到要运行svn status -u,因此她不知道哈利已经锁定了该文件。她花了几个小时编辑该文件,当她尝试提交更改时,她发现要么文件被锁定,要么她已经过时。无论哪种情况,她的更改都无法与哈利的更改合并。这两个人中必须有一个人放弃自己的工作,而且浪费了大量时间。

Subversion 解决这个问题的方法是提供一种机制来提醒用户在开始编辑之前应该锁定文件。该机制是一种特殊的属性:svn:needs-lock。如果该属性附加到文件(无论其值如何,这无关紧要),Subversion 会尝试使用文件系统级权限使文件只读——当然,除非用户明确锁定了该文件。当存在锁定令牌(由于使用了 svn lock)时,该文件将变为可读写。当释放锁定时,该文件将再次变为只读。

因此,理论上,如果图像文件附加了此属性,莎莉在打开该文件进行编辑时会立即注意到一些异常:许多应用程序会在打开只读文件进行编辑时立即提醒用户,几乎所有应用程序都会阻止她将更改保存到该文件。这会提醒她要在编辑之前锁定该文件,从而发现现有的锁定。

$ /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.
$
[Tip] 提示

鼓励用户和管理员将 svn:needs-lock 属性附加到任何无法进行上下文合并的文件。这是鼓励良好的锁定习惯和防止浪费精力的主要技术。

请注意,此属性是一种独立于锁定系统工作的通信工具。换句话说,任何文件都可以锁定,无论是否具有此属性。反之,此属性的存在不会使存储库在提交时需要锁定。

不幸的是,系统并非完美无缺。即使文件拥有该属性,只读提醒也可能无法始终有效。有时应用程序会发生故障并“劫持”只读文件,在用户不知情的情况下允许他们编辑并保存文件。Subversion 在这种情况下面临着很大的困难——归根结底,良好的沟通才是最好的解决办法。[21]



[19] 从某种意义上来说,沟通对于好莱坞明星哈里和莎莉来说也并非良药。

[20] Subversion 目前不支持对目录加锁。

[21] 当然,除了进行一次经典的瓦肯心灵融合。