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

锁定

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

让我们来看一个这种模型搁浅的现实例子。哈利和莎莉都是图形设计师,他们正在同一个项目上工作,为一家汽车修理店制作一些营销宣传资料。海报设计的一个核心部分是一张需要一些车身修理的汽车图片,存储在一个使用 PNG 图片格式的文件中。海报的布局几乎完成了,哈利和莎莉都对他们为损坏的汽车选择的特定照片感到满意——一辆天蓝色 1967 年的福特野马,左前挡泥板不幸地有点凹陷。

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

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

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

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

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

创建锁定

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

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

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

上面的示例演示了一些新事物。首先,请注意 Harry 将 --message (-m) 选项传递给了 svn lock。与 svn commit 类似,svn lock 命令可以通过 --message (-m) 或 --file (-F) 来接受注释,以描述锁定文件的理由。但是,与 svn commit 不同,svn 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
Working Copy Root Path: /home/harry/project
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(代表锁定),表示锁定令牌存在。

现在 Harry 已经锁定了 banana.jpg,Sally 就无法更改或删除该文件了。

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

但是 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 选项来覆盖自动释放锁定的行为。这最适合您想要提交更改,但仍计划进行更多更改,因此需要保留现有锁定的情况。您还可以通过设置 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
$

在这个例子中,Sally 不仅可以看到她的 foo.h 副本已过期,还可以看到她计划提交的两个修改文件之一在存储库中被锁定。O 符号代表 其他,这意味着该文件存在锁定,并且由其他人创建。如果她尝试提交,raisin.jpg 上的锁定将阻止提交。Sally 仍然想知道是谁锁定了文件,何时锁定以及为什么锁定。再次,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,则信息反映了存储库中对象的最新版本,任何提及锁定都描述了对象上的当前锁定。

因此,在这个特定示例中,Sally 可以看到 Harry 在 2 月 16 日锁定了该文件,以 进行快速调整。 现在已经是六月了,她怀疑 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 只需要将 --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: E195013: '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: warning: W160039: Unlock failed on 'raisin.jpg' (403 Forbidden)
$ svn unlock --force http://svn.example.com/repos/project/raisin.jpg
'raisin.jpg' unlocked.
$

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

仅仅解除锁可能还不够。在运行的示例中,Sally 可能不仅想要解除 Harry 遗忘已久的锁,而且还要重新锁定文件以供自己使用。她可以通过使用 svn unlock 以及 --force,然后使用 svn lock 来完成此操作,但是,在两个命令之间,其他人可能锁定了该文件。更简单的方法是 窃取 锁,这涉及在一步原子操作中解除锁并重新锁定文件。为此,Sally 将 --force 选项传递给 svn lock

$ svn lock raisin.jpg
svn: warning: W160035: Path '/project/raisin.jpg' is already locked by user 'h
arry' in filesystem '/var/svn/repos/db'
$ 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
Status against revision:     105
$ svn update
Updating '.':
  B  raisin.jpg
Updated to revision 105.
$ svn status
$

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

锁通信

我们已经了解了如何使用 svn locksvn unlock 来创建、释放、打破和窃取锁。这满足了对文件进行串行提交访问的目标。但是,如何解决更大的问题,即防止浪费时间呢?

例如,假设 Harry 锁定了一个图像文件,然后开始编辑它。与此同时,Sally 在千里之外想要做同样的事情。她没有想到要运行 svn status -u,所以她不知道 Harry 已经锁定了该文件。她花了几个小时编辑文件,当她尝试提交更改时,她发现文件被锁定了,或者她已经过时了。无论如何,她的更改都无法与 Harry 的更改合并。这两个人中必须有一个人丢弃自己的工作,浪费了很多时间。

Subversion 解决这个问题的方法是提供一种机制,在编辑开始 之前 提醒用户应该锁定文件。该机制是一个特殊的属性:svn:needs-lock。如果该属性附加到文件(无论其值如何,这都是无关紧要的),Subversion 将尝试使用文件系统级别的权限使文件只读——当然,除非用户显式地锁定了文件。当存在锁令牌(使用 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: warning: W160035: Path '/project/raisin.jpg' is already locked by user 'h
arry' in filesystem '/var/svn/repos/db'
$ 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 在这种情况下无能为力——归根结底,良好的沟通是不可替代的。[23]



[21] 对于哈利和莎莉的好莱坞同名人物来说,沟通也不失为良药。

[22] Subversion 目前不支持对目录进行锁定。

[23] 除了,也许,经典的瓦肯人精神融合。