本文档尚未完工,内容可能发生很大变化,可能无法准确描述 Apache™ Subversion® 软件的任何已发布版本。将此页面添加书签或向他人推荐此页面可能不是一个好主意。请访问 https://svnbooks.subversion.org.cn/ 获取本书的稳定版本。

锁定

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

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

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

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

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

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

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

创建锁定

在 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 commit 类似,svn 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
Working Copy Root Path: /home/harry/project
URL: http://svn.example.com/repos/project/banana.jpg
Relative URL: ^/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: 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'
$

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

$ 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
Relative URL: ^/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 日锁定该文件是为了 快速调整。 现在已经是六月了,她怀疑哈利可能忘记了锁定。她可能会打电话给哈利抱怨并要求他释放锁定。如果他无法接听,她可能会尝试强行解除锁定或要求管理员这样做。

解除和窃取锁定

存储库锁定不是神圣的 - 在 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'.
$

更有趣的选择是允许用户通过网络相互解除锁定。为此,莎莉只需在 svn 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: 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.
$

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

仅仅解除锁定可能还不够。在正在运行的示例中,莎莉可能不仅想要解除哈利早已忘记的锁定,还想要为自己重新锁定文件。她可以通过在 svn unlock 中使用 --force 然后在后面使用 svn lock 来完成此操作,但有可能其他人会在两个命令之间锁定该文件。更简单的方法是 窃取 锁定,这涉及在一项原子操作中解除锁定并重新锁定文件。为此,莎莉向 svn lock 传递 --force 选项。

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

无论锁定是解除还是窃取,哈利都可能会感到意外。哈利的工作副本仍然包含原始的锁定令牌,但该锁定不再存在。据说锁定令牌已 失效。锁定令牌所代表的锁定要么已被解除(不再存在于存储库中),要么已被窃取(被其他锁定替换)。无论哪种情况,哈利都可以通过要求 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 来创建、释放、解除和窃取锁定。这满足了将对文件的提交访问权限串行化的目标。但是,对于防止浪费时间这一更大的问题呢?

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

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 在这种情况下无能为力 - 归根结底,良好的沟通才是最好的解决方案。[30]



[28] 对于 Harry 和 Sally 的好莱坞同名角色来说,沟通也不失为一种良药。

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

[30] 除非,也许,是经典的 Vulcan 心灵感应。

TortoiseSVN 官方中文版 1.14.7 发布