Mercurial:命名分支与多个存储库

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

我相信现在已经到了更好的源代码控制的时候了,而且我已经在Mercurial玩了一段时间了。

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

在任何情况下,似乎我可能会使用像移植一样樱桃变化包含在发布分支。

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

最大的区别是分支名称是如何logging在历史中的。 通过命名分支,分支名称被embedded到每个变更集中,从而成为历史的一部分。 有了克隆,就不会有永久logging特定变更集的来源。

这意味着克隆对于不想logging分支名称的快速实验非常有用,并且命名分支对于长期分支(“1.x”,“2.x”和类似的分支)是有利的。

还要注意,单个存储库可以轻松地容纳Mercurial中的多个轻量级分支。 这样的资源库分支可以添加书签,以便您可以轻松地再次find它们。 假设您看起来像这样克隆了公司存储库:

 [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]的版本号replace[d] ):

 % hg update --clean [d] 

然后你会看到, hg parents报告[d] 。 这意味着你的下一个提交将有[d]作为父项。 因此,您可以修复您在主分支中发现的错误并创build变更集[e]

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

要仅推送changeset [e] ,您需要这样做

 % hg push -r [e] 

其中[e]是变更集散列。 默认情况下, hg push会简单地比较这些存储库,看到[x][y][e]丢失,但是你可能不想共享[x][y]

如果错误修正也会影响到您,您想要将其与您的function分支合并:

 % hg update [y] % hg merge 

这会让你的版本库看起来像这样:

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

其中[z][y][e]之间的合并。 你也可以select把分支扔掉:

 % hg strip [x] 

这个故事的主要观点是:一个克隆可以很容易地代表几个发展轨道。 没有使用任何扩展名的情况下,这一直是“纯HG”。 书签扩展是一个很大的帮助,虽然。 它将允许您将名称(书签)分配给变更集。 在上面的情况下,您需要在开发人员头上和上游头上的书签。 书签可以通过Mercurial 1.6进行推送和拉取 ,并且已经成为Mercurial 1.8的一个内置function。

如果你select了两个克隆,那么你的开发克隆在做[x][y]之后就会看起来像这样:

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

你的上游克隆将包含:

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

你现在注意到这个bug并修复它。 这里你不需要hg update因为上游的克隆已经可以使用了。 您提交并创build[e]

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

要将bug修复包含在开发克隆中,请将其放在此处:

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

并合并:

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

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

命名的分支机构并没有真正进入这里,因为它们是非常可选的。 在开始使用命名分支之前,Mercurial本身是使用两个克隆开发的。 除了“默认”分支之外,我们还维护一个名为“stable”的分支,并根据“stable”分支进行发布。 请参阅wiki中的标准分支页面,以获取推荐工作stream程的说明。

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

Mercurial的失望之一是,似乎没有简单的方法来创build一个短暂的分支,玩它,放弃它,并收集垃圾。 分支是永远的。 我同情永远不想放弃历史,但超便宜的一次性分支是一个gitfunction,我真的很想看到在hg

你应该这样做。

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

然后,每个发布分支有一个克隆用于构build和testing。

需要注意的一点是,即使使用多个存储库,也应该避免使用transplant来移动它们之间的更改集,因为1)它会更改散列,2)它可能会引入在更改之间发生冲突时很难检测到的错误你移植和目标分支。 你想做的是平常的合并,而不是先前的:总是在视觉上检查合并),这将导致@mg在他的答案结束时说:

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

更详细地说,如果您使用多个存储库,则“主干”存储库(或默认,主要,开发,任何)包含所有存储库中的所有更改集。 每个发行版/分支库都是简单的一个分支,所有的分支都被合并回主干,直到你想放弃旧版本。 因此,在指定分行scheme中,主要回购和单一回购之间的唯一真正区别仅仅是分行是否被命名。

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

但是,您仍然需要为每个分支/发行版维护一个您需要构build和testing的克隆。 这是微不足道的,因为你可以hg clone <main repo>#<branch> <branch repo> ,然后在分支回购hg pull只会拉动该分支上的新变更(加上先前分支合并的祖先变更集)。

这个设置最适合单个拉取者的linux内核提交模式(像Linus领主那样行事不是很好,在我们公司我们称之为angular色集成者 ),因为主要的回购是开发者需要克隆的唯一东西,拉拔者需要拉入。 维护分支回购纯粹是为了发布pipe理,可以完全自动化。 开发人员从不需要从分支机构回购。


这里是@ mg的例子,为这个设置重写。 初始点:

 [a] - [b] 

在发布alpha版本时,为发布版本创build一个命名分支,比如说“1.0”。 提交bug修复:

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

(1.0)不是一个真正的变更集,因为命名分支不存在,直到你提交。 (你可以做一个简单的提交,如添加一个标签,以确保正确创build命名分支。)

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

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

  ------- [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,你可以select分支1.0或默认。 无论如何,1.0上的任何新变更集都应该先合并到下一个分支,然后再默认。 如果没有冲突,这可以自动完成,只导致一个空的合并。


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

  1. 包含完整变更集和版本历史logging的单一权威储存库。
  2. 清除和简化版本pipe理。
  3. 清晰和简化的开发人员和集成商的工作stream程。
  4. 方便工作stream迭代(代码评论)和自动化(自动空合并)。

更新hg本身是这样做的 : 主要的回购包含默认和稳定的分支,而稳定的回购是稳定的分支克隆。 但是,它不使用版本分支,因为稳定分支上的版本标签对于发布pipe理的目的来说已经足够了。

就我所知,主要的区别是你已经说过:命名分支在一个存储库中。 命名的分支在一个地方有一切方便。 单独的回购更小,易于移动。 有两个学派的原因是没有明确的赢家。 无论哪一方的论点对你最有意义的,可能是你应该去的那个人,因为他们的环境可能与你最相似。

我认为这显然是一个务实的决定,取决于当前的情况,例如function的大小/重新devise。 我认为叉子对于尚未提交angular色的贡献者来说是非常好的,他们可以通过certificate他们的才能和可忽略的技术开销来join开发者团队。

我真的build议不要使用命名分支的版本。 这是真正的标签。 命名分支是为了持久的转移,就像一个stable分支。

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

  • 开发发生在一个分支上
  • 无论何时创build发行版,都会相应地标记它
  • 发展从那里继续
  • 如果你在某个版本中有一些bug修复(或者其他),你只需要更新它的标签,进行修改并提交

这将在default分支上创build一个新的未命名头,也就是说。 一个匿名的分支,这在hg中是完美的。 然后你可以在任何时候将bugfix提交回主开发轨道。 不需要命名分支。