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

高级合并

自动操作到此为止。迟早有一天,一旦您掌握了分支和合并的技巧,您将需要要求 Subversion 从一个地方到另一个地方合并特定的更改。为此,您将需要开始向svn merge传递更复杂的参数。下一部分将描述该命令的完整扩展语法,并讨论一些需要使用该语法的常见场景。

樱桃采摘

正如术语变更集经常在版本控制系统中使用一样,术语樱桃采摘也是如此。这个词是指从一个分支中选择一个特定变更集并将其复制到另一个分支的行为。樱桃采摘也可以指将一个分支中的特定变更集(不一定连续!)复制到另一个分支的行为。这与更典型的合并场景形成对比,在更典型的合并场景中,下一个连续的修订范围会自动复制。

为什么人们想只复制一个更改?这比你想象的更常见。例如,让我们回到过去,想象一下你还没有将你的私有功能分支合并回主干。在饮水机旁,你得知莎莉对主干上的integer.c做了一个有趣的更改。查看主干的提交历史记录,你发现她在修订版 355 中修复了一个重大错误,这个错误直接影响了你正在开发的功能。你可能还没有准备好将所有主干更改合并到你的分支中,但你肯定需要这个特定的错误修复才能继续你的工作。

$ svn diff -c 355 ^/calc/trunk

Index: integer.c
===================================================================
--- integer.c	(revision 354)
+++ integer.c	(revision 355)
@@ -147,7 +147,7 @@
     case 6:  sprintf(info->operating_system, "HPFS (OS/2 or NT)"); break;
     case 7:  sprintf(info->operating_system, "Macintosh"); break;
     case 8:  sprintf(info->operating_system, "Z-System"); break;
-    case 9:  sprintf(info->operating_system, "CP/MM");
+    case 9:  sprintf(info->operating_system, "CP/M"); break;
     case 10:  sprintf(info->operating_system, "TOPS-20"); break;
     case 11:  sprintf(info->operating_system, "NTFS (Windows NT)"); break;
     case 12:  sprintf(info->operating_system, "QDOS"); break;

就像你在之前的例子中使用svn diff来检查修订版 355 一样,你也可以将相同的选项传递给svn merge

$ svn merge -c 355 ^/calc/trunk
--- Merging r355 into '.':
U    integer.c

$ svn status
M       integer.c

你现在可以执行通常的测试程序,然后再将此更改提交到你的分支。提交之后,Subversion 会标记 r355 为已合并到分支,这样,以后的神奇合并,即同步你的分支与主干,就会知道要跳过 r355。(将相同的更改合并到同一个分支几乎总是会导致冲突!)

$ cd my-calc-branch

$ svn propget svn:mergeinfo .
/trunk:341-349,355

# Notice that r355 isn't listed as "eligible" to merge, because
# it's already been merged.
$ svn mergeinfo ^/calc/trunk --show-revs eligible
r350
r351
r352
r353
r354
r356
r357
r358
r359
r360

$ svn merge ^/calc/trunk
--- Merging r350 through r354 into '.':
 U   .
U    integer.c
U    Makefile
--- Merging r356 through r360 into '.':
 U   .
U    integer.c
U    button.c

这种从一个分支复制(或回传)错误修复到另一个分支的用例,也许是樱桃采摘更改最流行的原因;它经常出现,例如,当团队维护软件的发布分支时。(我们在名为“发布分支”的部分中讨论了这种模式。)

[Warning] 警告

您是否注意到,在最后一个例子中,合并调用导致应用了两个不同的合并范围?svn merge命令将两个独立的补丁应用于您的工作副本,以跳过变更集 355,因为您的分支已经包含了它。这本身并没有错,只是它有可能使冲突解决更加棘手。如果第一个更改范围创建了冲突,您必须以交互方式解决它们,才能使合并过程继续并应用第二个更改范围。如果您推迟了第一波更改的冲突,整个合并命令将退出并显示错误消息。[26]

需要注意的是:虽然svn diffsvn merge在概念上非常相似,但在许多情况下它们的语法确实不同。请务必阅读第 9 章,Subversion 完整参考中的相关内容了解详细信息,或者使用svn help查询。例如,svn merge需要一个工作副本路径作为目标,即它应该应用生成的补丁的位置。如果没有指定目标,它会假设您正在尝试执行以下常见操作之一

  • 您想将目录更改合并到当前工作目录中。

  • 您想将特定文件中的更改合并到当前工作目录中存在的同名文件中。

如果您正在合并目录,并且没有指定目标路径,svn merge会假设第一种情况,并尝试将更改应用到您的当前目录。如果您正在合并文件,并且该文件(或同名文件)存在于当前工作目录中,svn merge会假设第二种情况,并尝试将更改应用到本地同名文件。

合并语法:全面披露

您已经看到了一些svn merge命令的例子,并且您即将看到更多例子。如果您对合并的具体工作原理感到困惑,您并不孤单。许多用户(尤其是那些刚接触版本控制的用户)最初对该命令的正确语法以及如何以及何时使用该功能感到困惑。但不要害怕,这个命令实际上比您想象的要简单得多!有一个非常简单的技巧可以理解svn merge的具体行为。

造成混淆的主要原因是该命令的名称。术语合并在某种程度上表示分支被合并在一起,或者正在进行某种神秘的数据混合。事实并非如此。这个命令的更好名称可能是svn diff-and-apply,因为这就是它所做的一切:比较两个仓库树,并将差异应用于工作副本。

如果您使用svn merge在分支之间执行基本的更改复制,它通常会自动执行正确的事情。例如,以下命令

$ svn merge ^/calc/branches/some-branch

将尝试将some-branch上的所有更改复制到您的当前工作目录中,该目录很可能是一个与该分支共享一些历史记录的工作副本。该命令足够智能,可以只复制您的工作副本中还没有的更改。如果您每周重复执行此命令一次,它只会复制自上次合并以来最新的分支更改。

如果您选择通过向svn merge命令提供要复制的特定修订范围来使用该命令的全部功能,该命令将接受三个主要参数

  1. 一个初始仓库树(通常称为比较的左侧

  2. 一个最终仓库树(通常称为比较的右侧

  3. 一个工作副本,用于接受差异作为本地更改(通常称为合并的目标

一旦指定了这三个参数,就会比较这两棵树,并将差异应用于目标工作副本作为本地修改。命令完成后,结果与您手动编辑文件或运行各种svn addsvn delete命令的结果没有区别。如果您喜欢结果,您可以提交它们。如果您不喜欢结果,您可以简单地svn revert所有更改。

svn merge的语法允许您以灵活的方式指定这三个必要的参数。以下是一些示例

$ svn merge http://svn.example.com/repos/branch1@150 \
            http://svn.example.com/repos/branch2@212 \
            my-working-copy

$ svn merge -r 100:200 http://svn.example.com/repos/trunk my-working-copy

$ svn merge -r 100:200 http://svn.example.com/repos/trunk

第一个语法显式地列出了所有三个参数,以URL@REV的形式命名每个树,并命名工作副本目标。第二个语法可以作为一种简写,用于您比较同一 URL 的两个不同修订版的情况。最后一个语法显示了工作副本参数是可选的;如果省略,它默认设置为当前目录。

虽然第一个例子显示了svn merge完整语法,但它需要非常谨慎地使用;它可能导致合并根本不记录任何svn:mergeinfo元数据。下一节将更详细地讨论这一点。

没有合并信息的合并

Subversion 尝试在它能够的情况下生成合并元数据,以使svn merge的未来调用更智能。但是,仍然有一些情况不会创建或更改svn:mergeinfo数据。请记住,在这些情况下要稍微注意一些

合并不相关的源

如果您要求svn merge比较两个彼此不相关的 URL,它仍然会生成并应用一个补丁到您的工作副本,但不会创建任何合并元数据。这两个源之间没有共同的历史记录,而未来智能的合并依赖于这种共同的历史记录。

从外部仓库合并

虽然可以运行这样的命令:svn merge -r 100:200 http://svn.foreignproject.com/repos/trunk,但生成的补丁也会缺少任何历史合并元数据。在撰写本文时,Subversion 没有办法在svn:mergeinfo属性中表示不同的仓库 URL。

使用--ignore-ancestry

如果将此选项传递给svn merge,它会导致合并逻辑以与svn diff相同的方式无脑地生成差异,忽略任何历史关系。我们将在本章后面的名为“注意或忽略祖先”的部分中讨论这一点。

应用来自目标自然历史的逆向合并

在本章的前面(名为“撤销更改”的部分)中,我们讨论了如何使用svn merge应用逆向补丁来回滚更改。如果使用这种技术来撤消对对象个人历史记录的更改(例如,将修订版 5 提交到主干,然后立即使用svn merge . -c -5回滚修订版 5),这种合并不会影响记录的合并信息。[27]

关于合并冲突的更多信息

就像 svn update 命令一样,svn merge 会将更改应用到你的工件目录。因此,它也能够创建冲突。然而,由 svn merge 产生的冲突有时不同,本节解释了这些差异。

首先,假设你的工件目录没有本地编辑。当你 svn update 到特定修订版时,服务器发送的更改将始终 干净地 应用到你的工件目录。服务器通过比较两棵树来生成增量:你的工件目录的虚拟快照,以及你感兴趣的修订版树。因为比较的左侧完全等于你已经拥有的内容,所以增量保证能够正确地将你的工件目录转换为右侧的树。

但是,svn merge 没有这样的保证,并且可能更加混乱:高级用户可以要求服务器比较 任何 两棵树,即使是与工件目录无关的树!这意味着存在很大的人为错误可能性。用户有时会比较错误的两棵树,从而创建一个无法干净应用的增量。svn merge 会尽力应用尽可能多的增量,但有些部分可能无法应用。就像 Unix patch 命令有时会抱怨 失败的块, 一样,svn merge 同样会抱怨 跳过的目标

$ svn merge -r 1288:1351 http://svn.example.com/myrepos/branch
U    foo.c
U    bar.c
Skipped missing target: 'baz.c'
U    glub.c
U    sputter.h

Conflict discovered in 'glorb.h'.
Select: (p) postpone, (df) diff-full, (e) edit,
        (mc) mine-conflict, (tc) theirs-conflict,
        (s) show all options: 

在前面的示例中,可能是这种情况:baz.c 存在于正在比较的分支的两个快照中,并且生成的增量想要更改文件的內容,但该文件不存在于工件目录中。无论情况如何,跳过 消息意味着用户很可能正在比较错误的两棵树;这是用户错误的典型标志。当这种情况发生时,很容易递归地恢复合并创建的所有更改 (svn revert . --recursive),删除恢复后留下的任何未版本控制的文件或目录,并使用不同的参数重新运行 svn merge

另外请注意,前面的示例显示了在 glorb.h 上发生的冲突。我们已经说过工件目录没有本地编辑:怎么可能发生冲突?同样,因为用户可以使用 svn merge 来定义和将任何旧增量应用到工件目录,所以该增量可能包含无法干净地应用到工作文件的文本更改,即使该文件没有本地修改。

另一个 svn updatesvn merge 之间的小区别是当发生冲突时创建的全文文件的名称。在 名为“解决任何冲突” 的部分中,我们看到更新会生成名为 filename.minefilename.rOLDREVfilename.rNEWREV 的文件。但是,当 svn merge 产生冲突时,它会创建三个名为 filename.workingfilename.leftfilename.right 的文件。在这种情况下,术语 leftright 描述了文件来自双树比较的哪一侧。无论如何,这些不同的名称将帮助你区分由于更新导致的冲突和由于合并导致的冲突。

阻止更改

有时,你可能不想自动合并某个特定的变更集。例如,也许你的团队的政策是在 /trunk 上进行新的开发工作,但在将更改回传到用于向公众发布的稳定分支时更加保守。在极端情况下,你可以手动从 trunk 到分支挑选单个变更集——只有那些足够稳定以通过测试的更改。但是,也许情况并非如此严格;也许大多数时候你只想让 svn merge 自动将大多数更改从 trunk 合并到分支。在这种情况下,你希望有一种方法来屏蔽一些特定的更改,即阻止它们被自动合并。

在 Subversion 1.6 中,阻止变更集的唯一方法是让系统相信该更改 已经 被合并。为此,使用 --record-only 选项调用合并命令

$ cd my-calc-branch

$ svn propget svn:mergeinfo .
/trunk:1680-3305

# Let's make the metadata list r3328 as already merged.
$ svn merge -c 3328 --record-only ^/calc/trunk

$ svn status
M       .

$ svn propget svn:mergeinfo .
/trunk:1680-3305,3328

$ svn commit -m "Block r3328 from being merged to the branch."
…

这种技术有效,但也有点危险。主要问题是我们没有明确区分 我已经有此更改我没有此更改,但目前不想要它。 的概念。我们实际上是在对系统撒谎,让它认为更改以前已被合并。这将责任推给了你——用户——让你记住更改实际上并没有合并,只是你目前不想要它。无法向 Subversion 请求 被阻止的变更集 列表。如果你想跟踪它们(以便你可以在将来取消阻止它们),你需要将它们记录在某个文本文件中,或者可能记录在某个自创的属性中。

保持已重新集成的分支处于活动状态

重新集成后,除了销毁并重新创建分支之外,还有一种替代方法。要了解它为什么有效,你需要了解为什么分支在重新集成后最初不适合进一步使用。

假设你在修订版 A 中创建了你的分支。在你的分支上工作时,你创建了一个或多个修订版,这些修订版对分支进行了更改。在将你的分支重新集成到 trunk 之前,你从 trunk 到你的分支进行了最后一次合并,并将这次合并的结果提交为修订版 B

当将你的分支重新集成到 trunk 中时,你会创建一个新的修订版 X,它会更改 trunk。在修订版 X 中对 trunk 所做的更改在语义上等效于你在修订版 AB 之间对你的分支所做的更改。

如果你现在尝试将 trunk 中的未决更改合并到你的分支中,Subversion 会认为修订版 X 中所做的更改有资格合并到分支中。但是,由于你的分支已经包含修订版 X 中所做的所有更改,合并这些更改会导致虚假冲突!这些冲突通常是树冲突,尤其是如果在分支处于开发阶段时,在分支或 trunk 上进行了重命名。

那么该怎么办?我们需要确保 Subversion 不会尝试将修订版 X 合并到分支中。这可以通过使用 --record-only 合并选项来实现,该选项是在 名为“阻止更改” 的部分中引入的。

要执行仅记录合并,请获取刚刚在修订版 X 中重新集成的分支的工件目录,并将修订版 X 从 trunk 合并到你的分支中,确保使用 --record-only 选项。

此合并使用在 名为“挑选” 的部分中介绍的挑选合并语法。继续使用来自 名为“重新集成分支” 的部分的正在运行的示例,其中修订版 X 是修订版 391

$ cd my-calc-branch
$ svn update
Updated to revision 393.
$ svn merge --record-only -c 391 ^/calc/trunk
$ svn commit -m "Block revision 391 from being merged into my-calc-branch."
Sending        .

Committed revision 394.

现在你的分支已准备好再次吸收来自 trunk 的更改。在将你的分支与 trunk 再次同步后,你甚至可以将分支第二次重新集成。如果需要,你可以进行另一个仅记录合并以使分支保持活动状态。反复执行。

现在也应该清楚为什么删除分支并重新创建分支具有与执行上述仅记录合并相同的效果。因为修订版 X 是新创建的分支的自然历史的一部分,所以 Subversion 永远不会尝试将修订版 X 合并到分支中,从而避免虚假冲突。

对合并敏感的日志和注释

任何版本控制系统的主要功能之一是跟踪谁更改了什么以及他们何时更改了它。 svn logsvn blame 命令只是为此提供的工具:当它们被调用到单个文件上时,它们不仅会显示影响该文件的更改集的历史记录,还会显示哪个用户编写了哪一行代码以及何时编写了它。

但是,当更改开始在分支之间复制时,事情就会变得复杂。例如,如果你要询问 svn log 关于你的功能分支的历史记录,它会显示曾经影响过该分支的每个修订版

$ cd my-calc-branch
$ svn log -q
------------------------------------------------------------------------
r390 | user | 2002-11-22 11:01:57 -0600 (Fri, 22 Nov 2002) | 1 line
------------------------------------------------------------------------
r388 | user | 2002-11-21 05:20:00 -0600 (Thu, 21 Nov 2002) | 2 lines
------------------------------------------------------------------------
r381 | user | 2002-11-20 15:07:06 -0600 (Wed, 20 Nov 2002) | 2 lines
------------------------------------------------------------------------
r359 | user | 2002-11-19 19:19:20 -0600 (Tue, 19 Nov 2002) | 2 lines
------------------------------------------------------------------------
r357 | user | 2002-11-15 14:29:52 -0600 (Fri, 15 Nov 2002) | 2 lines
------------------------------------------------------------------------
r343 | user | 2002-11-07 13:50:10 -0600 (Thu, 07 Nov 2002) | 2 lines
------------------------------------------------------------------------
r341 | user | 2002-11-03 07:17:16 -0600 (Sun, 03 Nov 2002) | 2 lines
------------------------------------------------------------------------
r303 | sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines
------------------------------------------------------------------------
r98 | sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines
------------------------------------------------------------------------

但这真的是对分支上发生的更改的准确描述吗?这里遗漏的是修订版 390、381 和 357 实际上是合并来自 trunk 的更改的结果。如果你详细查看其中一个日志,你将看不到构成分支更改的多个 trunk 变更集

$ svn log -v -r 390
------------------------------------------------------------------------
r390 | user | 2002-11-22 11:01:57 -0600 (Fri, 22 Nov 2002) | 1 line
Changed paths:
   M /branches/my-calc-branch/button.c
   M /branches/my-calc-branch/README

Final merge of trunk changes to my-calc-branch.

我们碰巧知道此合并到分支只是一个 trunk 更改的合并。我们如何才能看到这些 trunk 更改?答案是使用 --use-merge-history (-g) 选项。此选项会扩展那些作为合并一部分的 更改。

$ svn log -v -r 390 -g
------------------------------------------------------------------------
r390 | user | 2002-11-22 11:01:57 -0600 (Fri, 22 Nov 2002) | 1 line
Changed paths:
   M /branches/my-calc-branch/button.c
   M /branches/my-calc-branch/README

Final merge of trunk changes to my-calc-branch.
------------------------------------------------------------------------
r383 | sally | 2002-11-21 03:19:00 -0600 (Thu, 21 Nov 2002) | 2 lines
Changed paths:
   M /branches/my-calc-branch/button.c
Merged via: r390

Fix inverse graphic error on button.
------------------------------------------------------------------------
r382 | sally | 2002-11-20 16:57:06 -0600 (Wed, 20 Nov 2002) | 2 lines
Changed paths:
   M /branches/my-calc-branch/README
Merged via: r390

Document my last fix in README.

通过让日志操作使用合并历史记录,我们不仅会看到我们查询的修订版 (r390),还会看到与它一起出现的两个修订版——Sally 对 trunk 做的几个更改。这更完整地反映了历史记录!

svn blame 命令也接受 --use-merge-history (-g) 选项。如果忽略此选项,查看 button.c 的逐行注释的人可能会错误地认为你负责修复某个错误的那几行代码

$ svn blame button.c
…
   390    user    retval = inverse_func(button, path);
   390    user    return retval;
   390    user    }
…

虽然你在修订版 390 中确实提交了这三行代码,但其中两行实际上是在修订版 383 中由 Sally 编写的

$ svn blame button.c -g
…
G    383    sally   retval = inverse_func(button, path);
G    383    sally   return retval;
     390    user    }
…

现在我们知道该真正指责谁来编写这两行代码了!

注意到或忽略祖先

在与 Subversion 开发人员交谈时,你很可能会听到他们提到 祖先 这个词。这个词用于描述存储库中两个对象之间的关系:如果它们彼此相关,则其中一个对象被称为另一个对象的祖先。

例如,假设您提交了版本 100,其中包括对文件 foo.c 的更改。那么 foo.c@99foo.c@100祖先。另一方面,假设您在版本 101 中提交了 foo.c 的删除操作,然后在版本 102 中添加了一个同名的新文件。在这种情况下,foo.c@99foo.c@102 似乎相关(它们具有相同的路径),但实际上是存储库中完全不同的对象。它们没有共享历史记录或 祖先关系

提出这一点的原因是为了指出 svn diffsvn merge 之间的重要区别。前者命令忽略祖先关系,而后者命令对此非常敏感。例如,如果您要求 svn diff 比较 foo.c 的版本 99 和 102,您将看到基于行的差异;diff 命令只是盲目地比较两个路径。但是,如果您要求 svn merge 比较相同的两个对象,它会注意到它们不相关,首先尝试删除旧文件,然后添加新文件;输出将显示删除操作,然后是添加操作。

D    foo.c
A    foo.c

大多数合并都涉及比较相互之间存在祖先关系的树;因此,svn merge 默认采用这种行为。但是,有时您可能希望 merge 命令比较两个不相关的树。例如,您可能已经导入了两个源代码树,它们代表软件项目的不同供应商版本(参见 名为“供应商分支”的部分)。如果您要求 svn merge 比较这两个树,您将看到整个第一棵树被删除,然后是整个第二棵树的添加!在这种情况下,您需要 svn merge 仅执行基于路径的比较,忽略文件和目录之间的任何关系。将 --ignore-ancestry 选项添加到您的 merge 命令中,它将像 svn diff 一样运行。(反之,--notice-ancestry 选项将导致 svn diffsvn merge 命令一样运行。)

合并和移动

一个常见的愿望是对源代码进行重构,尤其是在基于 Java 的软件项目中。文件和目录会被四处移动和重命名,这通常会导致对项目中所有人的重大干扰。听起来像是使用分支的完美案例,不是吗?只需创建一个分支,重新排列东西,然后将分支合并回主干,对吧?

唉,这种情况目前运行得不太好,被认为是 Subversion 目前的弱点之一。问题是 Subversion 的 svn update 命令不像它应该的那样健壮,特别是在处理复制和移动操作时。

当您使用 svn copy 复制文件时,存储库会记住新文件来自哪里,但它未能将这些信息传递给正在运行 svn updatesvn merge 的客户端。它不是告诉客户端 将您已经拥有的该文件复制到这个新位置,而是发送了一个全新的文件。这会导致问题,特别是因为重命名文件时也会发生同样的事情。Subversion 一个鲜为人知的事实是,它缺乏 真正的重命名——svn move 命令仅仅是 svn copysvn delete 的集合。

例如,假设在您私有分支上工作时,您将 integer.c 重命名为 whole.c。实际上,您在分支中创建了一个新文件,它是原始文件的副本,并且删除了原始文件。同时,回到 trunk 上,Sally 已经提交了一些对 integer.c 的改进。现在您决定将您的分支合并到主干

$ cd calc/trunk

$ svn merge --reintegrate ^/calc/branches/my-calc-branch
--- Merging differences between repository URLs into '.':
D   integer.c
A   whole.c
U   .

乍一看这似乎没什么问题,但它可能也不是您或 Sally 预期的。合并操作已删除 integer.c 文件的最新版本(包含 Sally 的最新更改),并盲目添加了您的新 whole.c 文件——它是 版本 integer.c 的副本。最终效果是,将您的 重命名 合并到主干已从最新版本中删除了 Sally 的最新更改!

这不是真正的数据丢失。Sally 的更改仍然存在于存储库的历史记录中,但这可能并不明显。这个故事的寓意是,在 Subversion 改进之前,要非常小心地将副本和重命名从一个分支合并到另一个分支。

防止幼稚的客户端提交合并

如果您刚刚将服务器升级到 Subversion 1.5 或更高版本,那么 1.5 之前的 Subversion 客户端可能会搞乱您的自动合并跟踪,这存在重大风险。为什么呢?当 1.5 之前的 Subversion 客户端执行 svn merge 时,它根本不会修改 svn:mergeinfo 属性的值。因此,后续提交尽管是合并的结果,但不会告诉存储库关于重复更改的信息——这些信息丢失了。之后,当 合并感知 客户端尝试自动合并时,它们很可能会遇到由于重复合并而导致的各种冲突。

如果您和您的团队依赖 Subversion 的合并跟踪功能,您可能希望配置您的存储库以阻止旧客户端提交更改。执行此操作的简单方法是检查 start-commit 挂钩脚本中的 功能 参数。如果客户端报告自己具有 mergeinfo 功能,则挂钩脚本可以允许提交开始。如果客户端没有报告该功能,则让挂钩拒绝提交。 示例 4.1,“合并跟踪守门员 start-commit 挂钩脚本” 提供了此类挂钩脚本的示例。

示例 4.1. 合并跟踪守门员 start-commit 挂钩脚本

#!/usr/bin/env python
import sys

# The start-commit hook is invoked before a Subversion txn is created
# in the process of doing a commit.  Subversion runs this hook
# by invoking a program (script, executable, binary, etc.) named
# 'start-commit' (for which this file is a template)
# with the following ordered arguments:
#
#   [1] REPOS-PATH   (the path to this repository)
#   [2] USER         (the authenticated user attempting to commit)
#   [3] CAPABILITIES (a colon-separated list of capabilities reported
#                     by the client; see note below)

capabilities = sys.argv[3].split(':')
if "mergeinfo" not in capabilities:
  sys.stderr.write("Commits from merge-tracking-unaware clients are "
                   "not permitted.  Please upgrade to Subversion 1.5 "
                   "or newer.\n")
  sys.exit(1)
sys.exit(0)

有关挂钩脚本的更多信息,请参见 名为“实现存储库挂钩”的部分

关于合并跟踪的最后一句话

底线是 Subversion 的合并跟踪功能具有极其复杂的内部实现,而 svn:mergeinfo 属性是用户可以了解该机制的唯一窗口。由于该功能相对较新,因此可能会出现一些边缘情况和潜在的意外行为。

有时合并信息会出现在您不希望被操作触及的文件上。有时,合并信息根本不会生成,而您希望它生成。此外,合并信息元数据的管理有一套完整的分类法和行为,例如 显式隐式 合并信息,有效无效 修订版本,合并信息 省略 的特定机制,甚至来自父目录到子目录的 继承

我们选择不在本书中介绍这些详细主题,原因有二。首先,对于普通用户来说,详细程度绝对是压倒性的。其次,随着 Subversion 的不断改进,我们认为普通用户 不应该 理解这些概念;它们最终会作为烦人的实现细节逐渐淡出人们的视线。也就是说,如果您喜欢这类东西,您可以在 CollabNet 网站上发布的一篇论文中获得精彩的概述:http://www.collab.net/community/subversion/articles/merge-info.html.

目前,如果您想避免自动合并中的错误和奇怪行为,CollabNet 文章建议您坚持以下简单的最佳实践。

  • 对于短期功能分支,请按照 名为“基本合并”的部分 中所述的简单过程进行操作。

  • 对于长期存在的发布分支(如 名为“常见分支模式”的部分 中所述),仅在分支的根目录上执行合并,而不是在子目录上执行合并。

  • 永远不要将合并合并到工作副本中,这些工作副本具有混合的工作修订版本号,或者具有 切换 的子目录(如 名为“遍历分支”的部分 中的下一部分所述)。合并目标应该是代表存储库中 单个 位置的单个时间点的单个工作副本。

  • 永远不要直接编辑 svn:mergeinfo 属性;使用 svn merge--record-only 选项来实现对元数据的所需更改(如 名为“阻止更改”的部分 中所示)。

  • 始终确保您对所有合并源具有完全的读取权限,并且目标工作副本没有稀疏目录。



[26] 至少,在撰写本文时,Subversion 1.6 是这样做的。这种行为可能会在未来版本的 Subversion 中得到改进。

[27] 有趣的是,在回滚这样的修订版本之后,我们将无法使用 svn merge . -c 5 重新应用该修订版本,因为合并信息已将 r5 列为已应用。我们将不得不使用 --ignore-ancestry 选项来使合并命令忽略现有的合并信息!