首先是关于合并如何工作以及适用于几乎所有版本控制系统的快速课程。
标准 Merge 是三路合并。在此,您将两个版本与两个版本的最后一个共同祖先进行比较。例如,我从主干分支。分支之前的主干上的版本是最后一个共同祖先。
目的是能够查看一个流上的更改与另一流上的更改。(流可以是特定的分支或主干)。例如,如果我从主干合并到分支,我想确保不会用主干上的那些行覆盖我的特定分支更改。
与最后一个共同祖先的比较允许您仅查看自该分支点以来在主干上发生的更改。
当我从以前的合并中倒退时,这会出现问题。例如,我完成了我的功能分支,并希望所有功能更改都回到主干上。这是一个问题,因为我已将所有主干更改合并到分支上。这意味着我的分支的流显示我对分支进行了更改,这些应该合并到主干中。但是,这些变化已经在主干上!要处理此问题,您可以强制进行双向合并。在这种情况下,您只是将两个单独流的头部相互比较,并将所有差异从一个流复制到另一个流。
现在 Subversion 如何处理合并:
首先,Subversion 喜欢cherry pick mergin。Subversion 允许您指定要合并的版本。例如,我有一个分支,发现了一个错误并在分支上修复了它。我现在可以合并包含我的错误修复的特定修订或修订集。当您在 Subversion 中挑选樱桃时,您正在执行三路合并,但您只考虑那些特定樱桃挑选版本中表示的更改,而不是主干或分支上的所有更改。
事实上,Subversion 几乎总是会进行cherry pick 合并,即使您没有指定cherry pick 修订版。svn:mergeinfo
Subversion 跟踪通过该属性合并了哪些修订。假设我在修订版 100 处分支,现在我将主干更改合并到我的分支中。主干上的最后一个修订版现在是修订版 150。Subversion 认为您正在从修订版 100(但不包括版本 100)到修订版 150 的所有主干版本中进行挑选。下次我从我的主干合并到我的分支(假设我的主干上的最后一个更改现在是 175),Subversion 将在主干上挑选从(但不包括 150)到修订版 175 的更改。
请注意,当我为合并指定樱桃选择修订时,我可以在我的分支和我的主干之间来回切换而不会出现任何问题。如果我从我的功能分支合并到我的主干,我可以在我实现该功能的功能分支上指定修订,并跳过我的功能分支上的更改,这些更改是从主干合并的结果。
只有当我让 Subversion 处理樱桃采摘时,问题才会出现。当我从我的主干合并到我的分支时,Subversion 会跟踪我的主干在我的分支上的修订。但是,Subversion 无法知道我的功能分支上的哪些修订是我的主干合并的结果。
因此,当我将我的功能分支合并回主干时,Subversion 将考虑所有未合并的修订——包括那些作为主干到分支合并结果的修订,并尝试将所有这些修订合并回我的主干。
为了处理这个问题,Subversion 有一个特殊的合并,称为重新集成合并。我将我在主干上的最后一组更改合并到我的分支中,然后当我将我的分支合并到我的主干中时,我希望我的主干和分支是相同的。因此,Subversion 想要进行双向合并并使主干和分支匹配。
在旧版本的 Subversion 中,我必须手动指定我正在使用命令--reintegration
上的参数进行这种双向合并。svn merge
Subversion 的合并跟踪也导致了重用功能分支的有趣问题。假设我做了最后一组从主干到分支的合并。主干上的最后一个修订版和 Subversion 中的最后一个修订版是修订版 100。当我将主干上的更改合并到我的分支上时,我现在在我的分支上创建了修订版 101。该svn:mergeinfo
分支上的属性显示我已将所有更改从主干合并到修订版 100 仅我的分支。
现在,我进行重新集成合并,提交更改,并在我的主干上创建修订版 102。
现在,我在我的主干上做了更多的工作(比如说修订版 103),我想将这些更改合并到我的功能分支中。
我从主干合并到功能分支的最后一个版本是什么?查看svn:mergeinfo
,我看到它是修订版 100。自从上次从主干合并到分支以来,主干上现在有两个新修订版尚未合并到我的功能分支中:修订版 102 和修订版 103。
因此,Subversion 会挑选并尝试将修订版 102 和修订版 103 中包含的更改合并到我的功能分支中。但是等等……修订版 102 是我所有的功能分支更改都被合并到主干上!我将把这些特性更改重新应用到我的特性分支上!
有两种方法可以解决这个问题: 方法一:一旦你进行了重新集成合并,你永远不应该再使用那个特性分支。删除它。把它锁在尼姑庵里。永远不要再说出它的名字。如果我再次需要那个特性分支,我应该从主干创建一个新的特性分支。
另一种方法是svn:mergeinfo
修改功能分支,让 Subversion 知道主干上的修订版 102 已经合并到我的功能分支中。您可以使用以下--record-only
参数执行此操作:
$ svn co $REPO/branches/feature/myproj
$ cd myproj
$ svn merge --record-only -r102 $REPO/trunk/myproj
如您所见,所有这些手动工作——知道何时使用该开关并了解在进行重新集成--reintegration
合并时会发生什么——导致了混乱。因此,较新版本的 Subversion(我相信是从 1.8 版开始)现在尝试为您处理这个问题。
如果您使用 Subversion 1.8 作为客户端,则无需指定--reintegration
参数。Subversion 将以编程方式确定您是在进行典型的三向合并还是重新集成合并,并采取相应的行动。
如果我尝试执行 Subversion 认为的重新集成合并,并且我还没有将所有主干修订合并到我的功能分支中,Subversion 会警告我并且不让我进行合并。如果我在重新集成合并后重用功能分支,Subversion 应该会检测到这一点并在重新集成后自动处理分支重用问题。(有时它并不能很好地工作。但是,Subversion 不会让您在重新集成合并后重用功能分支,但可能会给您一个错误,您可能必须手动进行--record-only
合并。)
那么在 Subversion 中,你应该如何做你的工作流程呢?
与 Git 不同的是,每个 bug 和功能都应该在自己的分支上,您至少需要将该 bug 修复或功能合并到多个分支上,您几乎可以在主干(或任何您喜欢的分支)上完成所有工作。在大多数 Subversion 商店中,仅针对候选版本进行分支。也就是说,我即将发布一个版本,我为该版本分支。一些开发人员致力于发布,其他开发人员继续他们的主干工作。
如果在主干上发现错误,则可以在主干上对其进行修复,并且可以将修复了该错误的那个修订或一组修订合并到发布分支中。或者,如果您在功能分支上发现需要在主干上修复的错误,您可以在功能分支上修复该错误并将该修订或一组修订合并到主干上。
它简单、直接且易于实施。
这并不意味着您不能为某个功能分支。事实上,有时你必须这样做。想象一个在未来深入实施的功能。您不希望在主干上进行这些更改。在这种情况下,创建特性分支,并定期将主干更改合并到该特性分支上。请记住,当您将该功能合并回主干时可能会出现问题。只要你明白发生了什么。没问题。
而且,如果您想疯狂地为每个功能和错误创建大量分支,您也可以这样做。但是,我建议您使用 Subversion 1.8,它会为您做到这一点。