本手册用于描述 Subversion 1.2。如果您运行的是更新版本的 Subversion,我们强烈建议您访问 https://svnbooks.subversion.org.cn/,并查阅适合您 Subversion 版本的版本。
Subversion 的“复制-修改-合并”模型在用户协作处理由基于行的文本文件(如程序源代码)组成的项目时最为有效。但是,正如在 何时需要锁定 中所讨论的那样,有时必须使用“锁定-修改-解锁”模型来代替 Subversion 的标准并发模型。当文件包含二进制数据时,通常难以或不可能合并两个由不同用户并行进行的更改集。为此,Subversion 1.2 及更高版本提供了一个称为 锁定 的功能,在其他版本控制系统中通常被称为“保留检出”。
Subversion 的锁定功能有两个主要目标
序列化对资源的访问。允许用户获取对存储库中文件的独占更改权。如果 Harry 保留了更改 foo.jpg
的权利,那么 Sally 就不能对其进行更改。
帮助沟通。防止用户在无法合并的更改上浪费时间。如果 Harry 保留了更改 foo.jpg
的权利,那么 Sally 就应该很容易注意到这一点,并避免修改该文件。
Subversion 的锁定功能目前仅限于文件——尚不能保留对整个目录树的访问权限。
在 Subversion 存储库中,锁定是一段元数据,它授予一个用户对文件的独占访问权限。这个用户被称为 锁定所有者。每个锁定还有一个唯一的标识符,通常是一个长的字符序列,称为 锁定令牌。存储库在单独的表中管理锁定,并在提交操作期间强制执行锁定。如果任何提交事务试图修改或删除文件(或删除文件的父节点),存储库将要求两条信息
用户身份验证。执行提交的客户端必须验证为锁定所有者。
软件授权。用户的工工作副本必须将锁定令牌与提交一起发送,以证明它确切地知道它正在使用哪个锁定。
为了演示,需要举一个例子。假设 Harry 决定更改一张 JPEG 图片。为了防止其他人提交对该文件的更改,他使用 svn lock 命令锁定存储库中的文件
$ svn lock banana.jpg --message "Editing file for tomorrow's release." 'banana.jpg' locked by user 'harry'. $ 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: 2005-03-15 12:43:04 -0600 (Tue, 15 Mar 2005) Text Last Updated: 2005-06-08 19:23:07 -0500 (Wed, 08 Jun 2005) Properties Last Updated: 2005-06-08 19:23:07 -0500 (Wed, 08 Jun 2005) Checksum: 3b110d3b10638f5d1f4fe0f436a5a2a5 Lock Token: opaquelocktoken:0c0f600b-88f9-0310-9e48-355b44d4a58e Lock Owner: harry Lock Created: 2005-06-14 17:20:31 -0500 (Tue, 14 Jun 2005) Lock Comment (1 line): Editing file for tomorrow's release.
在前面的示例中,演示了许多新内容。首先,请注意 Harry 将 --message
选项传递给了 svn lock。与 svn commit 类似,svn lock 命令可以接受注释(通过 --message (-m)
或 --file (-F)
),以描述锁定文件的原因。但是,与 svn commit 不同,svn lock 不会通过启动您首选的文本编辑器来要求提供消息。锁定注释是可选的,但仍然建议使用,以帮助沟通。
其次,锁定尝试成功。这意味着该文件尚未被锁定,并且 Harry 拥有该文件的最新版本。如果 Harry 的工作副本文件过时,存储库将拒绝该请求,迫使 Harry svn update 并重新尝试锁定命令。
另请注意,在存储库中创建锁定后,工作副本已缓存有关该锁定的信息——最重要的是锁定令牌。锁定令牌的存在至关重要。它使工作副本有权在以后使用该锁定。 svn status 命令在文件旁边显示一个 K
(代表 locKed),表示锁定令牌存在。
现在,Harry 已经锁定了 banana.jpg
,Sally 就无法更改或删除该文件了
$ whoami 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)
但是,Harry 在修改了香蕉的黄色阴影后,能够将更改提交到该文件。这是因为他验证为锁定所有者,并且他的工作副本持有正确的锁定令牌
$ whoami 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
的目录中的三十个文件,因为他不知道需要更改哪些文件。最后他只对四个文件进行了更改。当他运行 svn commit images 时,该过程仍会释放所有三十个锁定。
可以使用 svn commit 的 --no-unlock
选项来覆盖这种自动释放锁定的行为。这最适合那些您想提交更改但仍计划进行更多更改,因此需要保留现有锁定的情况。这种行为也可以通过在运行时 config
文件(参见 名为“运行时配置区域”的部分)中设置 no-unlock = yes
来半永久地调整。
当然,锁定文件并不意味着您必须提交对其的更改。可以使用简单的 svn unlock 命令随时解除锁定
$ svn unlock banana.c 'banana.c' unlocked.
当提交由于其他人的锁定而失败时,了解这些锁定很容易。最简单的方法是 svn status --show-updates
$ whoami sally $ svn status --show-updates 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: 2005-01-25 12:43:04 -0600 (Tue, 25 Jan 2005) Lock Token: opaquelocktoken:fc2b4dee-98f9-0310-abf3-653ff3226e6b Lock Owner: harry Lock Created: 2005-02-16 13:29:18 -0500 (Wed, 16 Feb 2005) 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 日锁定了该文件,目的是“进行快速调整”。现在已经是 6 月份了,她怀疑 Harry 可能已经忘记了这个锁定。她可能会打电话给 Harry 投诉,并要求他解除锁定。如果他无法接听电话,她可能会尝试自己强制解除锁定,或者要求管理员这样做。
存储库锁定并非神圣不可侵犯;它不仅可以被创建者解除,也可以被任何人解除。当除了原始锁定创建者之外的其他人破坏了锁定时,我们称之为 解除锁定。
从管理员的角度来看,解除锁定很简单。 svnlook 和 svnadmin 程序能够直接从存储库中显示和删除锁定。(有关这些工具的更多信息,请参见 名为“管理员工具箱”的部分。)
$ svnadmin lslocks /usr/local/svn/repos Path: /project2/images/banana.jpg UUID Token: opaquelocktoken:c32b4d88-e8fb-2310-abb3-153ff1236923 Owner: frank Created: 2005-06-15 13:29:18 -0500 (Wed, 15 Jun 2005) Expires: Comment (1 line): Still improving the yellow color. Path: /project/raisin.jpg UUID Token: opaquelocktoken:fc2b4dee-98f9-0310-abf3-653ff3226e6b Owner: harry Created: 2005-02-16 13:29:18 -0500 (Wed, 16 Feb 2005) 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'.
更有趣的选择是允许用户通过网络解除彼此的锁定。为此,只需将 --force
传递给解除锁定命令即可
$ whoami sally $ svn status --show-updates 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 命令,而没有 lock token。为了直接从仓库中移除锁,她需要传递一个 URL 到 svn unlock 命令。她第一次尝试解锁 URL 失败,因为她无法以锁所有者的身份进行身份验证(也没有 lock token)。但是,当她传递 --force
选项时,身份验证和授权要求将被忽略,远程锁被解除。
当然,仅仅解除锁可能还不够。在本例中,Sally 可能不仅想解除 Harry 的长期遗忘的锁,还想将文件重新锁定以供自己使用。她可以通过运行 svn unlock --force 然后运行 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'.
无论锁被解除还是被窃取,Harry 都可能会感到惊讶。Harry 的工作副本仍然包含原始的 lock token,但该锁不再存在。lock token 被称为 失效 的。lock token 代表的锁要么已被解除(不再存在于仓库中),要么已被窃取(被不同的锁替换)。无论哪种方式,Harry 都可以通过运行 svn status 命令来联系仓库并查看。
$ whoami harry $ svn status K raisin.jpg $ svn status --show-updates B 32 raisin.jpg $ svn update B raisin.jpg $ svn status $
如果仓库锁被解除,则 svn status --show-updates 命令会在文件旁边显示一个 B
(Broken)符号。如果存在一个新的锁来代替旧的锁,则会显示一个 T
(sTolen)符号。最后,svn update 命令会发现任何失效的 lock token 并将其从工作副本中删除。
我们已经看到了如何使用 svn lock 和 svn unlock 命令来创建、解除、解除和窃取锁。这满足了对文件进行串行提交访问的目标。但更大的问题是如何防止浪费时间呢?
例如,假设 Harry 锁定了一个图像文件,然后开始编辑它。与此同时,在千里之外,Sally 也想做同样的事情。她没有想到要运行 svn status --show-updates 命令,因此她并不知道 Harry 已经锁定了文件。她花了好几个小时编辑文件,当她尝试提交更改时,发现要么文件被锁定,要么她已经过时了。无论哪种方式,她的更改都无法与 Harry 的更改合并。这两个人中必须有人丢弃自己的工作,浪费了大量时间。
Subversion 对这个问题的解决方案是提供一种机制,以便在 编辑 开始之前提醒用户应该锁定文件。
这种机制是一个特殊的属性,svn:needs-lock
。如果该属性附加到一个文件(值无关紧要),则该文件将具有只读权限。当用户锁定文件并收到 lock token 时,文件将变为读写权限。当锁被解除时(要么显式解除,要么通过提交解除),文件将再次变为只读权限。
因此,理论上,如果图像文件附加了此属性,那么 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: 2005-06-08 07:29:18 -0500 (Thu, 08 June 2005) Lock Comment (1 line): Making some tweaks. Locking for the next two hours.
作为“最佳实践”,鼓励用户和管理员将 svn:needs-lock
属性附加到任何无法上下文合并的文件。这是鼓励良好的锁定习惯和防止浪费努力的主要技术。
请注意,此属性是一个与锁定系统独立工作的通信工具。换句话说,任何文件都可以被锁定,无论是否存在此属性。反过来,此属性的存在并不会让仓库在提交时需要锁定。
这个系统也不是完美的。即使文件具有此属性,只读提醒也不总是有效。有时,应用程序会“劫持”只读文件,让用户可以静默地编辑并保存文件。不幸的是,Subversion 对此无能为力。