命名分支与多个存储库


130

我们目前正在相对较大的代码库上使用Subversion。每个发行版都有自己的分支,并且针对主干执行修复,并使用以下命令迁移到发行分支中:svnmerge.py

我相信现在是时候进行更好的源代码控制了,并且我与Mercurial玩了一段时间。

尽管使用Mercurial管理这样的发布结构似乎有两个流派。每个发行版都有自己的存储库,并针对发行版分支进行修复,然后将其推送到主分支(以及任何其他较新的发行版分支。),或者在单个存储库(或多个匹配副本)中使用命名分支。

在这两种情况下,似乎我都可能使用诸如移植之类的方法来选择更改以包含在发行分支中。

我问你; 每种方法的相对优点是什么?

Answers:


129

最大的区别是分支名称在历史记录中的记录方式。使用命名分支,分支名称将嵌入每个变更集中,因此将成为历史记录的不变部分。对于克隆,将不会永久记录特定变更集的来源。

这意味着克隆非常适合您不想记录分支名称的快速实验,而命名分支则适合长期分支(“ 1.x”,“ 2.x”和类似名称)。

还要注意,单个存储库可以轻松容纳Mercurial中的多个轻量级分支。可以将此类存储库中的分支添加书签,以便您可以轻松地再次找到它们。假设您已经如下所示克隆了公司存储库:

[a] --- [b]

您砍掉并制作[x][y]

[a] --- [b] --- [x] --- [y]

这意味着当有人将[c][d]放入存储库时,因此当您拉入时,将获得如下历史记录图:

            [x] --- [y]
           /
[A B C D]

在单个存储库中有两个主管。您的工作副本将始终反映单个更改集,即所谓的工作副本父更改集。使用以下方法检查:

% hg parents

假设它报告[y]。你可以看到头

% hg heads

这将报告[y][d]。如果您要将存储库更新为的干净签出[d],则只需执行以下操作([d]用的修订号代替[d]):

% hg update --clean [d]

然后,您将看到该hg parents报告[d]。这意味着您的下一次提交将具有[d]父项。因此,您可以修复在main分支中发现的错误并创建更改集[e]

            [x] --- [y]
           /
[a] --- [b] --- [c] --- [d] --- [e]

[e]仅推送变更集,您需要执行

% hg push -r [e]

[e]变更集哈希在哪里。默认情况下,hg push将简单地比较资料库和看到[x][y][e]丢失,但您可能不希望共享[x][y]呢。

如果该错误修正也对您有影响,则要将其与功能分支合并:

% hg update [y]
% hg merge

这将使您的存储库图看起来像这样:

            [x] --- [y] ----------- [z]
           ///
[a] --- [b] --- [c] --- [d] --- [e]

[z]之间的合并在哪里?您可能还选择了放弃分支:[y][e]

% hg strip [x]

这个故事的主要目的是:一个克隆可以轻松代表多个发展轨迹。对于不使用任何扩展名的“ plain hg”,这始终是正确的。不过,书签扩展名是一个很大的帮助。它将允许您为变更集分配名称(书签)。在上述情况下,您需要在开发头上有一个书签,在上游头上有一个书签。书签可以使用Mercurial 1.6 进行推拉,并已成为Mercurial 1.8的内置功能。

如果您选择制作两个克隆,那么在制作[x]and 之后,您的开发克隆将看起来像这样[y]

[a] --- [b] --- [x] --- [y]

您的上游克隆将包含:

[a] --- [b] --- [c] --- [d]

您现在可以注意到该错误并进行修复。hg update由于上游克隆已准备就绪,在这里您不必这样做。您提交并创建[e]

[a] --- [b] --- [c] --- [d] --- [e]

要将错误修正包括在您的开发克隆中,您可以将其放入其中:

[a] --- [b] --- [x]-[y]
           \
            [c] --- [d] --- [e]

并合并:

[a] --- [b] --- [x] --- [y] --- [z]
           \ /
            [c] --- [d] --- [e]

该图可能看起来有所不同,但结构相同,最终结果也相同。使用克隆,您必须减少精神记录。

命名分支实际上并没有出现在这里,因为它们是可选的。在我们改用命名分支之前,Mercurial本身是使用两个克隆开发多年的。除了“默认”分支外,我们还维护一个名为“稳定”的分支,并基于“稳定”分支进行发布。有关建议的工作流程的描述,请参见Wiki中的标准分支页面。


1
如果变更集来自其他用户,那将被记录下来,因此使用克隆也不错。推送新功能时,常常会很无聊地知道您是从单独的回购中做到这一点的。还有一个localbranch扩展,可以为您提供仅本地分支。克隆存储库时有用,这会带来较高的成本(时间/空间)。
约翰内斯·鲁道夫

2
引用:“克隆非常适合快速实验”-不,不是!如果您在回购中有几千个文件,该怎么办?克隆将花费一些时间(超过1分钟的任何时间),而分支切换仅需片刻(<1秒)。仍使用命名分支将污染变更日志。这不是死胡同吗?还是我想念什么?
seler 2011年

好的,塞勒;听起来像是对他原始论点的修改;如果您不需要多个完整副本的开销,或者您可以使用hg的符号链接/硬链接来减轻每个分支的单独本地工作副本的成本,则克隆是很好的选择。
沃伦·P

@seler:您很正确,如果代码很大,则克隆是不切实际的。书签就是解决方案。
马丁·盖斯勒

29

我想您希望将整个历史记录都存储在一个仓库中。产生短期回购是针对短期实验,而不是诸如发行之类的重大事件。

Mercurial的失望之一是,似乎没有简单的方法来创建一个短暂的分支,对其进行处理,放弃它并收集垃圾。分支是永远的。我很同情从未放弃的历史,但是超级便宜的一次性分支是git我真的很想看到的功能hg


20
您可以很容易地进行这样的功能分支:“ hg update”到您的分支点,编辑掉并“ hg commit”。您新创建了一条不同的开发线-新的提交将扩展此分支。使用“ hg clone -r”摆脱它,或通过“ hg strip”内联删除它。因此,请不要失望,也不要随您的功能要求而进入Mercurial邮件列表。
马丁·盖斯勒

8
看起来hg strip就是我想要的。为什么在线文档声称分支不能被删除?
诺曼·拉姆齐

11
另见本博客张贴有关如何水银有一个交代,在某种程度上,比-便宜的Git分支:stevelosh.com/blog/entry/2009/8/30/...
马丁盖斯勒

9
您可以使用来关闭命名分支hg ci --close-branch
安德烈·弗拉索夫斯基

3
@Norman Ramsey:当人们说不能删除分支时,它们意味着您不能更改嵌入在变更集中的分支名称。变更分支没有,它定义了一个分支。如果要将变更集“移动”到其他分支,则需要删除变更集并使用其他分支名称重新创建。
马丁·盖斯勒

14

您应该两者都做。

从@Norman接受的答案开始:每个发行版使用一个存储库和一个命名分支。

然后,每个发行分支有一个克隆用于构建和测试。

一个主要的注意事项是,即使您使用多个存储库,也应避免使用transplant在两个存储库之间移动变更集,因为1)它会更改哈希,并且2)当您的变更集之间存在冲突更改时,它可能会引入很难检测到的错误。移植和目标分支。您想改为执行通常的合并(并且不进行预合并:始终以可视方式检查合并),这将导致@mg在他的回答结尾说:

该图可能看起来有所不同,但结构相同,最终结果也相同。

更详细地说,如果您使用多个存储库,则“ trunk”存储库(或默认存储库,主存储库,开发库等)将包含所有存储库中的所有变更集。每个发行版/分支存储库只是主干中的一个分支,所有分支都以一种方式合并或以另一种方式合并回主干,直到您想将旧发行版留在后面。因此,在命名分支方案中,该主存储库和单个存储库之间的唯一真正区别仅仅是分支是否被命名。

这应该很清楚为什么我说“从一个仓库开始”。单个仓库是您需要查找任何发行版中任何变更集的唯一位置。您可以在发行分支上进一步标记变更集以进行版本控制。它在概念上清晰且简单,并且使系统管理员更加简单,因为这是绝对必须始终可用且可恢复的唯一内容。

但是随后,您仍然需要为每个分支/版本维护一个克隆,您需要对其进行构建和测试。您可以轻松地做到这一点hg clone <main repo>#<branch> <branch repo>,然后hg pull在分支仓库中,回购将仅提取该分支上的新变更集(以及合并的较早分支上的祖先变更集)。

此设置最适合单个Puller的linux内核提交模型(像Lord Linus那样行事感觉很好。在我们公司,我们称为Role Integrator),因为主仓库是开发人员唯一需要克隆的东西,并且拉马需要拉入。分支存储库的维护仅用于发布管理,并且可以完全自动化。开发人员永远不需要从分支存储库/推入分支存储库。


这是针对此设置重铸的@mg示例。初始点:

[a] - [b]

进入Alpha版本后,为发行版本创建一个命名分支,例如“ 1.0”。提交错误修复程序:

[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]

(1.0)不是真正的变更集,因为直到您提交,命名分支才存在。(您可以进行简单的提交,例如添加标签,以确保正确创建了命名分支。)

合并[m1]是此设置的关键。与开发人员存储库中的头数不限的开发人员不同,您不希望主仓库中有多个头(除了前面提到的旧的,死发行的分支外)。因此,只要发行分支上有新的变更集,就必须立即将它们合并回默认分支(或更高版本的分支)。这样可以确保一个更高版本中的所有错误修复程序也都包含在所有更高版本中。

同时,在默认分支上的开发将继续进行到下一个版本:

          ------- [c] - [d]
         /
[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]

和往常一样,您需要在默认分支上合并两个头:

          ------- [c] - [d] -------
         /                         \
[a] - [b] ------------------ [m1] - [m2]
         \                 /
          (1.0) - [x] - [y]

这是1.0分支克隆:

[a] - [b] - (1.0) - [x] - [y]

现在是添加下一个发行分支的练习。如果是2.0,那么它肯定会默认分支。如果是1.1,则可以选择分支1.0或默认值。无论如何,1.0上的任何新变更集都应首先合并到下一个分支,然后再合并为默认值。如果没有冲突,这可以自动完成,只会导致一个空合并。


我希望这个例子能使我早先的观点清楚。总而言之,此方法的优点是:

  1. 包含完整变更集和版本历史记录的单一权威存储库。
  2. 清晰,简化的发布管理。
  3. 面向开发人员和集成商的清晰,简化的工作流程。
  4. 简化工作流迭代(代码审查)和自动化(自动空合并)。

UPDATE hg本身就是这样做的主仓库 包含默认分支和稳定分支,而稳定仓库是稳定分支克隆。但是,它不使用版本化分支,因为稳定分支上的版本标记足以满足其发布管理的需要。


5

据我所知,主要的区别是您已经说过了:命名分支位于单个存储库中。命名分支将所有内容都集中在一个地方。单独的存储库较小,易于移动。之所以有两种观点,是因为没有明确的赢家。无论哪一方的论点对您来说最有意义,可能都是您应该采用的论点,因为它们的环境可能与您的环境最相似。


2

我认为这显然是一个务实的决定,具体取决于当前情况,例如功能/重新设计的大小。我认为对于那些尚未担任贡献者角色的贡献者来说,Fork确实非常有用,因为他们可以通过精通的技术开销证明他们的才华。


0

我真的建议不要在版本中使用命名分支。这就是标签的真正用途。命名分支的目的是为了持久转移,就像stable分支一样。

那么,为什么不只使用标签呢?一个基本的例子:

  • 开发发生在单个分支上
  • 每当创建发行版时,您都相应地对其进行标记
  • 从那里继续发展
  • 如果您在某个版本中有一些错误要修复(或其他任何错误),则只需更新为其标记,进行更改并提交即可

这将在default分支上又名创建一个新的未命名的头。一个匿名分支,在hg中完全可以使用。然后,您可以随时将错误修正提交合并回主开发轨道。无需命名分支。


这很大程度上取决于您的过程。例如,Web应用程序在稳定/测试/开发分支层次结构中可以很好地工作。在构建桌面软件时,我们通常在维护中具有一个开发分支(默认)以及一到三个(!)不同的分支。很难预测我们何时需要重新访问分支,并且让分支跟踪major.minor版本具有一定的优雅。
詹姆斯·埃默顿
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.