命名分支与多个存储库

Jam*_*ton 130 version-control mercurial branch dvcs

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

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

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

在任何一种情况下,似乎我可能会使用像移植一样的东西,以包含在发布分支中的cherrypick更改.

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

Mar*_*ler 129

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

这意味着克隆非常适合快速实验,您不希望记录分支名称,并且命名分支适用于长期分支("1.x","2.x"和类似).

另请注意,单个存储库可以轻松容纳Mercurial中的多个轻量级分支.这样的存储库内分支可以加入书签,以便您可以轻松地再次找到它们.假设您已经克隆了公司存储库,如下所示:

[a] --- [b]
Run Code Online (Sandbox Code Playgroud)

你砍掉了,[x]并且[y]:

[a] --- [b] --- [x] --- [y]
Run Code Online (Sandbox Code Playgroud)

平均而有人把[c][d]到库中,所以当你拉你得到一个历史曲线图是这样的:

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

这里有一个存储库中的两个头.您的工作副本将始终反映单个变更集,即所谓的工作副本父变更集.检查一下:

% hg parents
Run Code Online (Sandbox Code Playgroud)

让我们说它报道[y].你可以看到头部

% hg heads
Run Code Online (Sandbox Code Playgroud)

这将报告[y][d].如果您想要将您的存储库更新为干净的结帐[d],那么只需执行(替换[d]为修订号[d]):

% hg update --clean [d]
Run Code Online (Sandbox Code Playgroud)

然后,您将看到该hg parents报告[d].这意味着您的下一次提交将[d]作为父级.因此,您可以修复在主分支中发现的错误并创建变更集[e]:

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

[e]仅推送变更集,您需要这样做

% hg push -r [e]
Run Code Online (Sandbox Code Playgroud)

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

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

% hg update [y]
% hg merge
Run Code Online (Sandbox Code Playgroud)

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

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

[z]之间的合并在哪里.你也可以选择抛弃分支:[y][e]

% hg strip [x]
Run Code Online (Sandbox Code Playgroud)

我的主要观点是:单个克隆可以很容易地代表几个开发轨道.对于"普通hg"而言,这一直都是如此,不使用任何扩展.该书签扩展是一个很大的帮助,虽然.它允许您为变更集指定名称(书签).在上面的例子中,您将需要开发头上的书签和上游头上的书签.可以使用Mercurial 1.6 推送和提取书签,这些书签已成为Mercurial 1.8中的内置功能.

如果您已经选择了让两个克隆,开发克隆看起来会是这样做后[x][y]:

[a] --- [b] --- [x] --- [y]
Run Code Online (Sandbox Code Playgroud)

您的上游克隆将包含:

[a] --- [b] --- [c] --- [d]
Run Code Online (Sandbox Code Playgroud)

你现在注意到了这个bug并修复了它.在这里您没有必要,hg update因为上游克隆已准备好使用.您提交并创建[e]:

[a] --- [b] --- [c] --- [d] --- [e]
Run Code Online (Sandbox Code Playgroud)

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

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

和合并:

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

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

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

  • 参考:'克隆非常适合快速实验' - 不,它们不是!如果你在repo中有几千个文件怎么办?克隆将需要很长时间(任何时间超过1分钟),而分支切换片刻(<1秒).仍然使用命名分支将污染更改日志.这不是死路一条吗?或者我错过了什么? (2认同)

Nor*_*sey 29

我想你想要一个回购中的整个历史.产生一个短期回购是短期实验,而不是像发布这样的重大事件.

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

  • 您可以非常轻松地创建这样的功能分支:"hg update"到您的分支点,编辑并"hg commit".你有新的创建了一个不同的开发线 - 新的提交将扩展这个分支.使用"hg clone -r"去除它,或者通过"hg strip"将其删除.所以请不要失望,或者根据您的功能请求访问Mercurial邮件列表. (20认同)
  • 另请参阅此博客文章,了解Mercurial在某种程度上比git分支更便宜的解释:http://stevelosh.com/blog/entry/2009/8/30/a-guide-to-branching-在-善变/ (11认同)
  • 您可以使用`hg ci --close-branch`关闭一个命名分支. (9认同)
  • 看起来像`hg strip`就是我想要的.为什么在线文档声明分支无法删除? (8认同)
  • @Norman Ramsey:当人们说分支不能被删除时,它们意味着你不能改变嵌入在变更集中的分支名称.变更设置我们没有*在一个分支上,它*定义*一个分支.如果要将变换集"移动"到不同的分支,则需要删除变更集并使用不同的分支名称重新创建变更集. (3认同)

Geo*_*eng 14

你应该两个都做.

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

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

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

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

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

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

但是,您仍需要为每个分支/版本维护一个需要构建和测试的克隆.尽管如此,它是微不足道的hg clone <main repo>#<branch> <branch repo>,然后hg pull在分支repo中只会在该分支上拉出新的变更集(加上合并的早期分支上的祖先变更集).

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


这是@ mg的例子,为此设置重新制作.初始点:

[a] - [b]
Run Code Online (Sandbox Code Playgroud)

当你进入alpha版本时,为发行版本创建一个命名分支,比如说"1.0".提交错误修复:

[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]
Run Code Online (Sandbox Code Playgroud)

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

合并[m1]是此设置的关键.与可以有无限数量的磁头的开发人员存储库不同,您不希望在主回购中有多个磁头(除了之前提到的旧的死发布分支).因此,只要在发布分支上有新的更改集,就必须立即将它们合并回默认分支(或更高版本的分支).这可以保证一个版本中的任何错误修复也包含在所有后续版本中.

与此同时,默认分支的开发继续向下一个版本发展:

          ------- [c] - [d]
         /
[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]
Run Code Online (Sandbox Code Playgroud)

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

          ------- [c] - [d] -------
         /                         \
[a] - [b] ------------------ [m1] - [m2]
         \                 /
          (1.0) - [x] - [y]
Run Code Online (Sandbox Code Playgroud)

这是1.0分支克隆:

[a] - [b] - (1.0) - [x] - [y]
Run Code Online (Sandbox Code Playgroud)

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


我希望这个例子能够清楚地说明我的观点.总之,这种方法的优点是:

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

UPDATE hg本身就是这样做的:主repo 包含默认和稳定的分支,而稳定的repo是稳定的分支克隆.但是,它不使用版本化分支,因为稳定分支上的版本标签足以满足其发布管理目的.


dwc*_*dwc 5

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