【问题标题】:mercurial merging just the changes from a branchmercurial 仅合并来自分支的更改
【发布时间】:2018-08-04 00:53:36
【问题描述】:

我可以使用“graft”将我在特定提交中所做的相同更改从一个分支复制到另一个分支。我想做的是类似的事情,除了我想从分支复制所有更改。

也就是说,我从一个称为分支 A 的分支开始。我在分支 A 上创建了一个名为 feat1 的新分支,它正在添加一个新功能。我在 feat1 中进行了几次提交,然后将其合并回分支 A。

我想将feat1分支中所做的所有更改合并到另一个分支中 分支我将调用分支 B。但我不想将分支 A 中的所有内容合并到分支 B,只是合并在 feat1 分支过程中所做的更改。我认为我可以使用一系列移植来完成此操作,我的 feat1 分支中的每个提交一个,但我认为应该有更好的方法。有没有标准的方法来完成这个?

我目前正在使用 Mercurial,尽管我计划在某个时候过渡到 git,所以我想知道如何在任一系统中进行操作。

【问题讨论】:

    标签: git merge mercurial


    【解决方案1】:

    (注意:这是交叉发布到,所以我们需要一个涵盖两者的答案。我也很抱歉这有多长,但是有很多概念需要理解。如果这是 TL;DR,请跳到最后一部分,避免所有复制。但请注意,您必须向上阅读才能看到我在说什么。)

    Git 和 Mercurial 的合并方式相同。1 特别是,分支名称无关紧要。重要的不是名称;重要的是提交图


    1有很多微小的差异,甚至有一些比较大的差异,但我这里的意思是总体想法是相同的。 Git 还支持 Git 所谓的快进合并的概念,这根本不是合并。这个概念在 Mercurial 存储库中毫无意义,根本不可能发生:Mercurial 不执行快进。运行hg merge <commit-specifier> 大致相当于运行git merge --no-ff <commit-specifier>


    在 Git 和 Mercurial 中,每个提交都以某种方式记录其父提交的 ID,或者(在合并提交的情况下)其父提交的 ID(对于 Mercurial,恰好两个,对于 Git,两个或更多)。因为每个提交都有自己的唯一 ID,并且每个提交都记录了它的父级或父级,我们可以绘制一个图——它以 tree 开始,这更简单,我将在此处说明——这些提交:

    A  <--B  <--C
    

    这里我们有一个简单的存储库,其中只有三个提交。提交C 是最新的。它有一个单亲B,所以C 记住B 的ID。 B 同样会记住A 的 ID。 (注意A 不知道BB 不知道C:箭头只会向后移动。)

    任何提交都无法更改(这在 Git 中更是如此,但在 Mercurial 中也几乎如此)。所以我们不需要把箭头画成箭头;我们可以只使用连接线,只要我们记住 VCS 必须向后跟随它们。这很方便,因为我们现在可能决定添加一个新的提交 D,其父级是 B 而不是 C

    A--B--C
        \
         D
    

    这是 Mercurial 和 Git 趋于分歧的地方:在 Mercurial 中,我们经常(故意)通过将提交 D 放在不同的分支上来实现这个结果。 Mercurial 记录进行任何提交的分支的实际名称,因此从那时起,Mercurial 就知道 D 位于分支 dev 或其他任何位置。 Git 使用完全不同的方案:Git 不知道也不关心提交的位置; Git 通过让名称记住 last 提交来查找提交,并按照两个系统的方式向后工作,按照连接提交的内部箭头。

    因此,在 Mercurial 中,我们可能会说:

    default:   A--B--C
                   \
        dev:        D
    

    每个提交都在与我们提交提交所在行对应的分支上。但在 Git 中,我们可能会切换到像这样绘制它们:

         C   <-- master
        /
    A--B
        \
         D   <-- dev
    

    即名称master标识提交C,名称dev标识提交D。提交AB 现在在两个 分支上。

    此时,Mercurial 用户可能会说 Git 简直是疯了,许多人会同意他们的看法。但是 Mercurial 用户在这里并没有摆脱困境,因为我们也可以在 Mercurial 中这样做:我们可以将提交 D 放在分支 default 上,但仍然将 B 作为其父级。那就是:

             A--B--C
    default:     \
                  D
    

    在 Mercurial 中也有效!而在现代 Mercurial 中,我们可以设置两个 书签 来记住提交 CD,我们的情况与 Git 中相同。

    因此,在两个系统中了解提交图的工作原理很重要。由于 Mercurial 的分支系统更加死板,因此您通常可以在没有这种了解的情况下侥幸逃脱 - 但最终,您确实需要了解它。


    现在,让我们看一下您的文字描述,然后绘制图表,因为重要的是 图表

    我从一个名为分支 A 的分支开始。我在分支 A 上创建了一个名为 feat1 的新分支,它正在添加一个新功能。我在 feat1 中进行了几次提交,然后将其合并回分支 A。

    我想将在 feat1 分支中所做的所有更改合并到另一个分支中,我将其称为分支 B。但我不想将分支 A 中的所有内容合并到分支 B,只是在整个过程中所做的更改壮举1分支。我认为我可以使用一系列移植来完成此操作,我的 feat1 分支中的每个提交一个,但我认为应该有更好的方法。有没有标准的方法来完成这个?

    这里少了一点,因为在 Mercurial 中,您几乎可以肯定真的default 开头,就像 Git 用户以 master 开头一样。让我们在default 上绘制至少一个提交,然后关注branchAfeat1branchB

    default:  A
               \
    branchA:    B--C-...--M
                    \    /
    feat1:           D--E
    

    提交M 是您的合并,通过运行hg checkout branchA; hg merge feat1 完成。

    合并的工作方式是,它不是查看分支名称,而是查看提交图。在M 存在之前,图表显示为:

    ...--C--...
          \
           D--E
    

    我在这里输入...,因为在C 之后可能有一些提交,例如FF-G-H 或其他。让我们假设有。对 Mercurial 的影响不如对 Git 重要,因为在 Git 中,这强制 Git 进行真正的合并而不是快进非合并,但它有助于说明如何合并作品。让我们只使用一个提交F

    ...--C--F
          \
           D--E
    

    为了实现合并,两个 VCS 在此时查看图表。他们采用当前提交(在本例中为 F)和请求的(“其他”或 --theirs)提交,在本例中为 E——并在图中向后搜索最佳共同祖先提交。从图中可以明显看出该提交:它是提交 C!

    因此,此时,两个 VCS 都执行以下逻辑等效操作:

    • diff commit C vs commit F,找出我们改变了什么;
    • diff commit C vs commit E,找出他们改变了什么;
    • 结合这两组更改,将它们应用到C的内容;
    • 如果一切顺利,使用F 作为第一个父级,E 作为第二个父级,进行新的合并提交M: p>

      ...--C--F---M
            \    /
             D--E
      

    所以,既然你有了这个,让我们继续你的文本的下一部分:

    我想将feat1分支中所做的所有更改合并到另一个分支中,我将调用分支B。

    这个新分支branchB 并不是凭空出现的。您必须创建它。这里的方法在 Git 和 Mercurial 中有点不同,但最终结果是一样的……嗯,大部分是一样的。

      1234563会点。然后运行git branch branchB &lt;commit-specifier&gt;,名称branchB 现在指向该提交。
    • 在 Mercurial 中,您现在签出一些现有的提交:hg update -r &lt;rev&gt; 或类似的。选择其中一个提交,也许是A,在这种情况下我们可以使用hg update default。然后运行hg branch branchB; hg commit 进行new 提交,这将创建branchB

    在这两个系统中,如果该分支上没有提交,则该分支不能存在;但在 Git 中,任何提交都可以在 许多 分支上,因此我们可以通过将 branchB 指向提交 A 来使其存在。在 Mercurial 中,提交仅在 one 分支上,因此我们需要进行新的提交以使 branchB 存在。所以图片有点不同。

    这是 Git 图片:

    A   <-- master, branchB
     \
      B--C--F---M   <-- branchA
          \    /
           D--E   <-- feat1
    

    这是 Mercurial 的一个:

    branchB:   N
              /
    default: A
              \
    branchA:   B--C--F---M
                   \    /
    feat1:          D--E
    

    此时,我们可以在 Git 中进行无偿提交,只是为了使图片匹配(除了由于 Git 分支名称 move,我们将它们放在右侧,箭头指向图;Mercurial 分支名称是固定不变的,所以我们可以让它们位于左侧并永远标记它们的提交,只要我们的图无论如何都不会变得很大)。 Git 将写入新的提交 N2move 当前分支名称以指向新的提交。

    但我不想将分支 A 中的所有内容合并到分支 B,只是合并在 feat1 分支过程中所做的更改。我认为我可以使用一系列移植来完成此操作,我的 feat1 分支中的每个提交一个,但我认为应该有更好的方法。有没有标准的方法来完成这个?

    在 Git 和 Mercurial 中,实际上您必须复制提交。图提交;您在feat1 上所做的提交DE 被卡在了它们所在的位置。 分支名称是移动(Git)还是​​固定固定(Mercurial)都没有关系,因为提交本身是不可变的。

    在 Mercurial 中,hg graft 复制提交是正确的。您可以使用单个命令复制所有 feat1 提交。因为提交特定于一个特定的分支,所以很容易复制 all feat1 提交。

    在 Git 中,git cherry-pick 命令复制提交。您也可以使用单个命令在此处复制您想要的所有提交,但是因为提交 D-E 位于 两个 分支上——它们同时位于 feat1 和上em> branchA 现在——你需要使用更复杂的说明符。 Git 方便的说明符(可能不是那么方便)使用图形操作:feat1~2..feat1 将同时指定提交 DE,在这种情况下,branchA^..feat1 也是如此。 Mercurial 也可以做到这一点,但您在 Mercurial 中并不经常需要


    2请注意,我们必须git checkout branchB 并且可能使用git commit --allow-empty--allow-empty 标志告诉 Git 它应该进行新的提交,即使 index - 这是一个特定于 Git 的概念; Mercurial 没有索引——与当前提交完全匹配。 git checkout 步骤的副作用是将名称 HEAD 附加到分支名称,以便 Git 知道 哪个名称是当前分支。 (Mercurial 将当前分支名称记录在一个名为 dirstate 的隐藏数据结构中,与 Git 的索引不同,您不需要知道它。)


    避免所有的复制

    由于您的目标是避免使用hg graftgit cherry-pick,让我们考虑如何实现这一目标。请注意,这并不总是可能的,即使它可能的,有时也需要大量的远见和计划。但关键是:Git 和 Mercurial 都使用 graph 中的 merge base 来合并各种功能。

    让我们从上面获取最终的 Mercurial 图表,其中两个 feat1 提交必须被嫁接才能影响提交 N

    branchB:   N
              /
    default: A
              \
    branchA:   B--C--F---M
                   \    /
    feat1:          D--E
    

    现在,假设当我们去创建分支feat1,然后实现该功能时,我们事先知道我们希望能够只运行hg update branchA; hg merge feat1; hg update branchB; hg merge feat1

    要完成这项工作,我们必须让feat1 上的第一次提交在图中的较早提交之后。具体来说,它必须在一些提交之后,该提交是branchA branchB 的提示的祖先。提交C 距离branchB 太远了:它不是branchA 上提交N 的祖先。

    理想的提交是branchAbranchB 第一次重新组合在一起的那个。也就是说,它是其哈希 ID Git 将通过运行 git merge-base branchA branchB 拼出的提交:最近(在图形方面)提交 是两个提示的祖先提交。

    请注意,因为我们还没有开始feat1,所以我们现在必须实际拥有的只是:

    branchB:   N
              /
    default: A
              \
    branchA:   B--C
    

    Mercurial 没有特别方便的工具来查找所需的哈希 ID,3 但由于 Mercurial 提交已牢固地钉在其分支上,因此通常非常明显。在这种情况下,这是提交A,这是default 上的最后一次提交。所以我们可以:

    hg update default
    hg branch feat1
    

    然后编写并提交我们的代码:

    branchB:   N
              /
    default: A
             |\
    feat1:   | D--E
              \
    branchA:   B--C
    

    同时提交F 像以前一样进入branchA,所以我们hg update branchB 并发现我们现在有:

    branchB:   N
              /
    default: A
             |\
    feat1:   | D--E
              \
    branchA:   B--C--F
    

    我们运行hg merge feat1 并得到:

    branchB:   N
              /
    default: A
             |\
    feat1:   | D------E
              \        \
    branchA:   B--C--F--M
    

    其中M 的第一个父级是F,第二个父级是E,就像以前一样。但现在我们可以hg update branchB; hg merge feat1NE 的合并基是 commit A; Mercurial 将AN 进行比较以了解我们做了什么,将AE 进行比较以了解他们做了什么。 Mercurial 构建新的合并提交 O 并提交它:

    branchB:   N----------O
              /          /
    default: A          /
             |\        /
    feat1:   | D------E
              \        \
    branchA:   B--C--F--M
    

    除了使用不同的命令和分支标签本身移动这一事实之外,Git 中的过程是相同的。


    3您可以使用-r 选项和面向图形的修订说明符找到提交。从数字上讲,您想要满足ancestor(tip-of-branchA) &amp; ancestor(tip-of-branchB) 的最后一次提交。更新到该提交,然后创建 feat1 提交。

    在 Git 中,要查找哈希,只需运行 git merge-base branchA branchB。找到哈希后,将新的分支名称指向该哈希 ID,检查该分支,然后开始提交。

    【讨论】:

      猜你喜欢
      • 2014-10-15
      • 1970-01-01
      • 2011-07-08
      • 2018-03-08
      • 2012-02-07
      • 1970-01-01
      • 2019-12-04
      • 2011-05-31
      • 2011-10-30
      相关资源
      最近更新 更多