本文档旨在描述 Subversion 1.4。如果您正在运行较新版本的 Subversion,我们强烈建议您访问 https://svnbook.subversion.org.cn/ 并查阅适合您的 Subversion 版本的书籍版本。
Subversion 的复制-修改-合并版本控制模型的成败取决于其数据合并算法,特别是这些算法在处理多个用户同时修改同一个文件时发生的冲突时的表现如何。Subversion 本身只提供一种这样的算法,即三方差异算法,该算法足够智能,可以以单行文本的粒度处理数据。Subversion 还允许您使用外部差异工具(如 “外部 diff3”部分 所述)来补充其内容合并处理,其中一些工具可能会做得更好,也许可以提供单词或单个文本字符的粒度。但这些算法的共同点是它们通常只适用于文本文件。当您开始谈论非文本文件格式的内容合并时,情况就开始变得很糟糕。当您找不到可以处理这种类型合并的工具时,您将开始遇到复制-修改-合并模型的问题。
让我们来看一个现实生活中这种模型陷入困境的例子。哈里和莎莉都是图形设计师,他们在同一个项目上工作,这是一个为汽车修理工制作的营销资料。海报的设计的核心是一个需要一些车身修理的汽车的图像,存储在一个使用 PNG 图像格式的文件中。海报的布局几乎完成了,哈里和莎莉都对他们为损坏的汽车选择的特定照片感到满意——一辆婴儿蓝色的 1967 年福特野马,左前翼子板不幸有点压扁。
现在,就像图形设计工作中常见的情况一样,计划发生了变化,导致汽车的颜色成为了一个问题。所以莎莉将她的工作副本更新到 HEAD
,启动她的照片编辑软件,然后开始调整图像,以便汽车现在是樱桃红色。与此同时,哈里当天感觉特别有灵感,他认为如果汽车看起来也遭受了更大的撞击,图像会更有影响力。他同样也更新到 HEAD
,然后在汽车的挡风玻璃上画了一些裂缝。他设法在他完成工作之前完成了他的工作,并且在欣赏了他不可否认的天赋的成果后,提交了修改后的图像。不久之后,莎莉完成了汽车的新饰面,并试图提交她的更改。但是,正如预期的那样,Subversion 提交失败了,通知莎莉现在她的图像版本已经过时了。
这就是困难所在。如果哈里和莎莉对文本文件进行修改,莎莉只需更新她的工作副本,并在过程中接收哈里的更改。在最糟糕的情况下,他们会修改文件的同一个区域,莎莉将不得不手动解决冲突。但这些不是文本文件,它们是二进制图像。虽然描述人们对这种内容合并结果的期望很简单,但几乎没有机会有任何软件足够智能,可以检查这些图形艺术家各自使用的共同基线图像、哈里所做的更改以及莎莉所做的更改,然后输出一张破裂的红色野马带有裂缝挡风玻璃的图像!
显然,如果哈里和莎莉序列化了他们对图像的修改,事情会进展得更顺利——例如,如果哈里等到莎莉的汽车变成了红色后才画他的挡风玻璃裂缝,或者如果莎莉调整了挡风玻璃已经破裂的汽车的颜色。正如 “复制-修改-合并解决方案”部分 所述,在哈里和莎莉之间存在完美沟通的情况下,大多数这些类型的问题都会完全消失。 [15] 但是,由于版本控制系统实际上是沟通的一种形式,因此让该软件促进不可并行编辑工作的序列化并不是一件坏事。这就是 Subversion 的锁定-修改-解锁模型实现发挥作用的地方。这就是我们讨论 Subversion 的 锁定功能的地方,它类似于其他版本控制系统中的“保留签出”机制。
Subversion 的锁定功能有两个主要目的
序列化对版本化对象的访问。通过允许用户以编程方式声明对存储库中文件的独占更改权,该用户可以合理地确信,投入在不可合并更改上的精力不会浪费——他提交这些更改将成功。
帮助沟通。通过提醒其他用户特定版本化对象的序列化正在生效,这些其他用户可以合理地预期该对象即将被其他人更改,并且他们也可以避免在不可合并更改上浪费时间和精力,而这些更改由于最终过时而无法提交。
当提到 Subversion 的锁定功能时,实际上指的是一个相当多样化的行为集合,其中包括锁定版本化文件 [16](声明对修改文件的独占权)、解锁该文件(放弃对修改的独占权)、查看哪些文件被锁定以及被谁锁定的报告、为强烈建议在编辑之前锁定的文件添加注释等。在本节中,我们将介绍锁定功能的这些方面。
在 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 status 和 svn 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 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: 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
的目录中随意锁定了三十个文件,因为他不知道需要更改哪些文件,但他只更改了其中四个文件,当他运行 svn commit images 时,该过程仍然会释放所有三十个锁。
可以使用 svn commit 的 --no-unlock
选项来覆盖自动释放锁的行为。此选项最适合在您想提交更改但仍计划进行更多更改并因此需要保留现有锁的情况。您还可以通过设置 no-unlock
运行时配置选项(请参阅 名为“运行时配置区域”的部分)将其设置为默认行为。
当然,锁定文件并不意味着必须对其进行更改提交。可以使用简单的 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 在 2 月 16 日锁定了该文件,原因是“进行快速调整”。现在已经是六月了,她怀疑他可能已经忘记了锁。她可以打电话给 Harry 投诉并要求他释放锁。如果他无法接听电话,她可以尝试自己强行解除锁,或请管理员来解除锁。
存储库锁不是神圣不可侵犯的——在 Subversion 的默认配置状态下,不仅创建锁的人,而且任何人都可以释放锁。当除了原始锁创建者以外的人员销毁锁时,我们称之为解除锁。
从管理员的角度来看,解除锁很简单。svnlook 和 svnadmin 程序可以直接从存储库中显示和删除锁。(有关这些工具的更多信息,请参阅 名为“管理员工具包”的部分。)
$ 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 只需要在解锁命令中传递 --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 的初始解锁尝试失败了,因为她在她对文件的副本上直接运行了 svn unlock,并且没有锁令牌。为了直接从存储库中删除锁,她需要将 URL 传递给 svn unlock。她第一次尝试解锁 URL 失败了,因为她无法以锁所有者的身份进行身份验证(也没有锁令牌)。但是,当她传递 --force
时,身份验证和授权要求会被忽略,远程锁会被解除。
仅仅解除锁可能还不够。在运行的示例中,Sally 不仅想解除 Harry 的久久忘记的锁,还想为自己重新锁定该文件。她可以通过依次运行 svn unlock --force 和 svn lock 来实现此目的,但存在一种可能性,即在两个命令之间有人可能锁定了该文件。更简单的方法是窃取锁,这涉及在一个原子步骤中解除并重新锁定该文件。为此,Sally 在 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
(已解除)符号。如果存在新的锁来替换旧的锁,则会显示一个 T
(已窃取)符号。最后,svn update 会注意到任何失效的锁令牌,并将其从工作副本中删除。
我们已经了解了如何使用 svn lock 和 svn unlock 来创建、释放、解除和窃取锁。这满足了对文件的提交访问进行序列化以实现并发控制的目标。但更广泛的问题是如何防止浪费时间呢?
例如,假设 Harry 锁定了一个图像文件,然后开始编辑它。同时,在千里之外,Sally 想做同样的事情。她没有想到运行 svn status --show-updates,所以她不知道 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: 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]