130

我们目前在相对较大的代码库上使用 subversion。每个版本都有自己的分支,并且针对主干执行修复并使用迁移到发布分支svnmerge.py

我相信是时候进行更好的源代码控制了,而且我已经玩弄 Mercurial 有一段时间了。

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

在任何一种情况下,我似乎都可能使用像移植这样的东西来挑选更改以包含在发布分支中。

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

4

6 回答 6

129

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

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

另请注意,单个存储库可以轻松容纳 Mercurial 中的多个轻量级分支。可以将此类存储库中的分支添加为书签,以便您可以轻松地再次找到它们。假设您已经克隆了公司存储库,它看起来像这样:

[a] --- [b]

您破解并制作[x]and [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]作为父提交。因此,您可以修复您在主分支中注意到的错误并创建变更集[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 中的标准分支页面。

于 2009-05-21T00:27:38.467 回答
29

我认为您希望将整个历史记录在一个仓库中。产生短期回购是为了短期实验,而不是发布等重大事件。

Mercurial 的失望之一是似乎没有简单的方法来创建一个短命的分支,玩弄它,放弃它,然后收集垃圾。树枝是永恒的。我很同情永远不想放弃历史,但超级便宜的一次性分支是git我非常希望在hg.

于 2009-05-21T00:57:32.183 回答
14

你应该两者都做。

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

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

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

该图可能看起来不同,但它具有相同的结构并且最终结果是相同的。

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

这应该很明显为什么我说“从一个回购开始”。那个单一的 repo 是您唯一需要在任何版本中查找任何变更集的地方。您可以在发布分支上进一步标记变更集以进行版本控制。它在概念上清晰而简单,并使系统管理员更简单,因为它是唯一必须始终可用和可恢复的东西。

但是,您仍然需要为每个需要构建和测试的分支/版本维护一个克隆。这很简单hg clone <main repo>#<branch> <branch repo>,然后hg pull在分支 repo 中只会拉取该分支上的新变更集(加上合并的早期分支上的祖先变更集)。

这种设置最适合单拉程序的 linux 内核提交模型(表现得像 Linus 勋爵不是很好吗。在我们公司,我们称之为角色integrator),因为主 repo 是开发人员唯一需要克隆的东西,并且拉马需要拉进去。分支仓库的维护纯粹是为了发布管理,可以完全自动化。开发人员永远不需要从/推送到分支仓库。


这是@mg 为这个设置重铸的示例。初始点:

[a] - [b]

当您进入 alpha 版本时,为发布版本创建一个命名分支,例如“1.0”。提交错误修复:

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

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

合并[m1]是此设置的关键。与可以有无限数量的 head 的开发者存储库不同,您不希望在主 repo 中有多个 head(除了前面提到的旧的、死的发布分支)。因此,每当您在发布分支上有新的变更集时,您必须立即将它们合并回默认分支(或更高版本的分支)。这保证了一个版本中的任何错误修复也包含在所有后续版本中。

同时,默认分支的开发继续朝着下一个版本:

          ------- [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 本身就是这样做的:主仓库 包含默认和稳定的分支,而稳定的仓库是稳定的分支克隆。但是,它不使用版本化分支,因为稳定分支上的版本标签足以满足其发布管理的目的。

于 2010-09-21T05:27:42.603 回答
5

据我所知,主要区别在于您已经说过:命名分支位于单个存储库中。命名分支将所有东西都放在一个地方。单独的存储库更小且易于移动。对此有两种观点的原因是没有明确的赢家。哪一方的论点对您最有意义,可能就是您应该接受的论点,因为很可能他们的环境与您的环境最相似。

于 2009-05-20T23:58:30.570 回答
2

我认为这显然是一个务实的决定,具体取决于当前情况,例如功能/重新设计的大小。我认为 fork 非常适合尚未担任提交者角色的贡献者加入开发团队,通过可忽略的技术开销证明他们的能力。

于 2009-11-19T13:17:37.293 回答
0

我真的建议不要对版本使用命名分支。这就是标签的真正用途。命名分支意味着持久的转移,如stable分支。

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

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

这将在default分支上创建一个新的、未命名的头,也就是。一个匿名分支,在 hg 中非常好。然后,您可以随时将错误修复提交合并回主要开发轨道。不需要命名分支。

于 2014-08-09T14:26:20.730 回答