命名分支与多个存储库

时间:2009-05-20 23:20:48

标签: version-control mercurial branch dvcs

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

迁移到发布分支

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

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

在任何一种情况下,似乎我可能会使用像移植一样的东西来改变发布分支。

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

6 个答案:

答案 0 :(得分:129)

最大的区别在于如何在历史记录中记录分支名称。对于命名分支,分支名称在每个变更集中都是 embedded ,因此将成为历史记录中不可变的部分。对于克隆,没有永久记录特定变更集的来源。

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

另请注意,单个存储库可以轻松容纳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]作为父级。因此,您可以修复您在主分支中发现的错误并创建变更集[e]

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

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

% hg push -r [e]

其中[e]是变更集哈希。默认情况下,hg push只会比较存储库并发现缺少[x][y][e],但您可能不想共享[x]和{ {1}}。

如果错误修复也会影响您,您希望将其与您的功能分支合并:

[y]

这将使您的存储库图形如下所示:

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

其中% hg update [y] % hg merge [z][y]之间的合并。你也可以选择抛弃分支:

[e]

我的主要观点是:单个克隆可以很容易地代表几个开发轨道。对于“普通hg”而言,这一直是不正确的,不使用任何扩展名。不过,bookmarks extension是一个很好的帮助。它允许您为变更集指定名称(书签)。在上面的例子中,您将需要开发头上的书签和上游头上的书签。使用Mercurial 1.6可以推送和拉取书签,它已成为Mercurial 1.8中的内置功能。<​​/ p>

如果您选择制作两个克隆,那么在制作% hg strip [x] [x]之后,您的开发克隆就会如下所示:

[y]

您的上游克隆将包含:

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

您现在会注意到该错误并进行修复。在这里,您不必使用[a] --- [b] --- [c] --- [d] ,因为上游克隆已准备好使用。您提交并创建hg update

[e]

要在开发克隆中包含错误修复,请将其拉​​入其中:

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

并合并:

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

图表可能看起来不同,但结构相同,最终结果相同。使用克隆你不得不做一点心理记账。

这里的命名分支并没有真正出现,因为它们是非常可选的。在我们切换到使用命名分支之前,Mercurial本身是使用两个克隆开发的。除了'default'分支之外,我们还维护一个名为'stable'的分支,并根据'stable'分支进行发布。有关推荐工作流程的说明,请参阅Wiki中的standard branching页面。

答案 1 :(得分:29)

我认为你想要一个回购中的整个历史。产生短期回购的是短期实验,而不是发布等重大事件。

Mercurial令人失望的一个原因是,似乎没有简单的方法来创建一个短命的分支,玩它,放弃它,并收集垃圾。分支是永远的。我同情永远不想放弃历史,但超便宜的,一次性的分支是git功能,我真的希望在hg中看到。

答案 2 :(得分:14)

你应该两者

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

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

一个关键的注意事项是,即使您使用多个存储库,也应该避免使用transplant在它们之间移动更改集,因为1)它会更改哈希值; 2)它可能会引入很难检测到的错误您移植的变更集与目标分支之间存在冲突的变化。你想要做通常的合并(并且没有premerge:总是直观地检查合并),这将导致@mg在他的答案结尾处说的话:

  

图表看起来可能不同,但结构相同,最终结果相同。

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

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

但是,您仍需要为每个分支/版本维护一个需要构建和测试的克隆。这是微不足道的,因为你可以hg clone <main repo>#<branch> <branch repo>,然后分支仓库中的hg pull将只在该分支上拉出新的变更集(加上合并的早期分支上的祖先变更集)。

这个设置最适合单个拉拔器的Linux内核提交模型(像Linus这样的行为感觉不错。在我们公司,我们称之为集成商 ),因为主要的回购是开发人员需要克隆的唯一东西,拉拔器需要拉入。分支回购的维护纯粹是为了发布管理,可以完全自动化。开发人员永远不需要从/推送到分支回购。


这是@ 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. 促进工作流迭代(代码审查)和自动化(自动空合并)。

  5. UPDATE hg本身does thismain repo包含默认和稳定分支,stable repo是稳定分支克隆。但是,它不使用版本化分支,因为稳定分支的版本标签足以满足其发布管理目的。

答案 3 :(得分:5)

据我所知,主要区别在于您已经说明过:命名分支在一个存储库中。命名分支在一个地方拥有一切便利。独立的回购更小,易于移动。有两种思想流派的原因是没有明显的赢家。无论哪一方的论点对你来说最有意义,都可能是你应该选择的那个,因为它们的环境很可能与你的环境最相似。

答案 4 :(得分:2)

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

答案 5 :(得分:0)

我真的建议不要在版本中使用命名分支。这真的是标签的用途。命名分支用于长期转移,例如stable分支。

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

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

这将在default分支上创建一个新的,未命名的头。一个匿名的分支,在hg中完全没问题。然后,您可以在任何时候将错误修复提交合并回主开发轨道。不需要命名分支。