【问题标题】:Change direction of git merge (after the merge is done)更改 git merge 的方向(合并完成后)
【发布时间】:2017-04-06 13:20:00
【问题描述】:

我在 git repo 中有一个分支(例如 Feature-X)。

我所做的是以下

git checkout master
git merge Feature-X

我解决了很多冲突。我还没有提交更改。

但是,事实证明我想要做的是反向合并(即将 master 合并到 Feature-x),因为分支可能仍然不稳定。

是否有任何命令可以挽救我在解决冲突时所做的工作,或者我是否需要再次解决,这次是在分支 Feature-X 中?

我正在考虑是否有办法获取当前补丁,但以“反向”顺序将其应用到分支 Feature-X 中。例如。当补丁说 X 行更改为 Y 时,这是相对于 Feature-X 要掌握的。如果我想做相反的事情,补丁应该说 Y 行改为 X 行,对吗?这只是我的想法。任何解决方案都可以。

【问题讨论】:

    标签: git git-merge git-patch


    【解决方案1】:

    TL;DR:看到结尾

    最后有一个“食谱”;向下滚动找到它(然后向上滚动一点找到描述和解释以及稍微安全一点的方法)。

    好消息和坏消息

    从某种意义上说,合并并不完全一个“方向”。 (当你看到一个合并时,你“看到”的方向是你想象的产物,加上合并提交的消息,再加上一个更重要的事情,这将是我们的“坏消息”——但最终它并不是致命的坏消息。 )

    考虑这个以提交* 为合并基础的一对分支的图表:

              o--o--...--o   <-- branch1
             /
    ...--o--*
             \
              o--o--...--o   <-- branch2
    

    合并(“合并为动词”)branch1branch2的过程是通过:

    • 比较,即git diff,合并基础提交*branch1的提示提交;
    • 将合并基数与branch2的尖端进行比较;
    • 然后,从基础开始,结合两组更改。

    因此实际的合并本身应该是这样的:

              o--o--...--o
             /            \
    ...--o--*              M
             \            /
              o--o--...--o
    

    (我将合并提交命名为M,因为我们不知道或真正关心的是什么疯狂的 Git 哈希 ID deadbeefbadc0ffee 或其他任何东西。)最终的合并提交 M 是合并(“作为名词合并”);它基于 merge-as-a-verb 组合过程存储最终的源树。

    这次合并是哪个“方向”?好吧,这至少部分取决于我们贴上的标签,不是吗? :-) 请注意,我小心地剥离了branch1branch2。让我们把它们放回去:

              o--o--...--o     <-- branch1?
             /            \
    ...--o--*              M   <-- branch1? branch2?
             \            /
              o--o--...--o     <-- branch2?
    

    这可能看起来令人困惑。它可能令人困惑。这是整个问题的很大一部分。 坏消息是,这不是全部的事情。有些东西我们无法在这些图纸中完全捕捉到。这对您来说可能无关紧要,如果有,那也没什么大不了的;我们将通过再进行一次合并提交来修复它。但是现在,让我们继续前进吧。

    进行合并提交

    一旦我们自己做出合并提交 M,我们必须选择——或者让 Git 选择——它在哪个分支上。在一个简单的、无冲突的合并中,我们总是让 Git 选择这个。 Git 所做的 很简单:无论我们在哪个分支上,当我们启动git merge 命令时,都是获取 合并提交的分支。所以:

    git checkout branch1
    git merge branch2
    

    表示最终的图片是:

              o--o--...--o
             /            \
    ...--o--*              M   <-- branch1
             \            /
              o--o--...--o     <-- branch2
    

    我们可以重新绘制为:

    ...--o--*--o--...--o--M   <-- branch1
             \           /
              o---...---o     <-- branch2
    

    这表明我们将 branch2 合并到 branch1 中,反之亦然。尽管如此,提交M 所附的源代码并不依赖于这个“合并方向”。

    侧边栏:合并冲突

    就最终结果而言,冲突合并就像非冲突合并一样。只是 Git 不能自己进行合并。它停止并让解决冲突,然后运行git commit 进行合并提交。最后的git commit 步骤使合并提交M

    新的合并提交继续当前分支,就像任何其他提交一样。这就是Mbranch1 上的结束方式。请注意,合并提交的 message 还说明了哪个分支名称被合并到哪个其他分支名称中 - 但是您有机会在提交时编辑它,因此您可以更改它以适应任何你可能心血来潮。 :-)

    (实际上,这与无冲突的情​​况没有什么不同:都运行git commit 来进行最终的合并提交,并且都给了你编辑消息的机会。)

    坏消息

    坏消息是这些图表忽略了一些可能对你很重要的东西。 Git 有一个 first parent 的概念:M 这样的合并提交有两个父级,所以其中一个是 first 父级,一个不是。1 first parent 是您在进行合并时如何判断您在哪个分支上的方式。

    因此,虽然这些图和作为动词的合并过程表明并不完全有“合并方向”,但这个第一父概念证明了存在。 如果您曾经使用过这种第一父母的概念,您会关心这一点,并希望把它做好。幸运的是,有一个解决方案。 (事实上​​,有多种解决方案,但我会先展示一些笨拙但简单的解决方案。)


    1显然,另一个是第二个父母:只有两个父母。但是,Git 允许将提交与三个或更多父级合并。您可以在必要时枚举所有父母;但 Git 认为 first 尤为重要,对各种命令都有--first-parent 标志,以便遵循“原始分支”。


    得到我们想要的合并

    让我们继续进行“错误”的合并提交:

    # git add ...    (if needed)
    git commit
    

    现在我们有了:

              o--o--...--o     <-- [old branch1 tip]
             /            \
    ...--o--*              M   <-- branch1
             \            /
              o--o--...--o     <-- branch2
    

    其中M第一个父母 是旧的branch1 提示。

    我们现在想要的是创建一个 新的 合并提交(具有不同的 ID),它与 M 相同,除了:

    • branch2 指向它
    • 它的第一个父节点是branch2 的尖端
    • 它的第二个父节点是branch1上一个提示

    诀窍是以某种方式保存提交M——这很简单,我们将使用一个临时名称,这样我们就不必复制M的哈希ID——然后重置branch1

    git branch temp          # or git tag temp
    git reset --hard HEAD~1  # move branch1 back, dropping M
    

    (请注意,到目前为止,这与brehonia's answer 相同)。我们现在有了这个图表,除了每个提交附加的 标签 之外,它几乎没有变化:

              o--o--...--o     <-- branch1
             /            \
    ...--o--*              M   <-- temp
             \            /
              o--o--...--o     <-- branch2
    

    现在查看branch2 并再次运行合并;它会像以前一样因冲突而失败:

    git checkout branch2
    git merge branch1        # use --no-commit if Git would commit the merge
    

    从某种意义上说,我们尚未进行的提交与合并提交 M 相同——但一旦我们完成,它的 first 父级将成为 @987654368 的当前提示@,其 second 父级将是 branch1 的当前提示。我们只需要获得正确的source 来完成我们将要进行的提交。

    现在我们使用名称temp 保存的合并结果。这假设您位于树的顶层,因此 . 命名所有内容:

    git rm -rf -- .          # remove EVERYTHING
    git checkout temp -- .   # get it all back from temp
    

    我们现在正在使用我们之前提交的合并结果。我们甚至不需要 git add 任何东西,因为这种形式的 git checkout 会更新索引,所以:

    git commit
    

    我们根据需要在分支branch2 上进行了新的合并M2

              o--o--...--o__   <-- branch1
             /            \ \
    ...--o--*              M \ <-- temp
             \            /   \
              o--o--...--o-----M2   <-- branch2
    

    现在我们可以删除临时分支或标签了:

    git branch -D temp   # or git tag -d temp
    

    我们都完成了。随着临时名称的消失,我们再也无法看到原来的合并 M

    偷偷摸摸的管道方法

    实际上有一个更短的方法来完成这一切,使用 Git 的“管道”命令,但它有点棘手。一旦我们拥有所有git add-ed 并运行git commit,我们就有了正确的树,我们只是有一个具有错误的第一和第二父ID 的提交。您可能还希望编辑提交 message,以便看起来您正在将消息中的 branch1 合并到 branch2 中。那么:

    # git add ...    (if / as needed, as above)
    # git commit     (make the merge)
    git branch -f branch2 $(git log --pretty=format:%B --no-walk HEAD |
        git commit-tree -p HEAD^2 -p HEAD^1 -F -)
    git reset --hard HEAD^1
    

    git commit-tree 步骤创建了一个新的合并提交,它是我们刚刚创建的那个的副本,但是交换了两个父级。然后我们使用git branch -f 使branch2 指向这个提交。 确保您在这一步得到了正确的名称 (branch2)。 HEAD^1HEAD^2 名称指的是 当前 的两个(第一和第二)父母em> commit,这当然是我们刚刚做的合并提交。具有正确的树和正确的提交消息文本;它只是有错误的第一个和第二个父哈希。

    一旦我们将新提交安全地添加到 branch2,我们只需将当前 (branch1) 分支重置回来以删除我们所做的合并提交。

    【讨论】:

    • 您介意将可视图添加到Getting the merge we want 部分的某些步骤中,以便我们了解发生了什么吗?会更容易跟随并在我们的脑海中形象化..就我个人而言,我有点迷路了。
    • @Cyber​​Mew:看看这些是否有帮助。当图表变得纠结时,绘制图表会变得很困难......
    • +1 很好地解释了为什么合并没有方向,但它们确实(在某种意义上)。非常有用的图表。
    • 合并方向很重要!查看“Merge A into B”(即合并期间检出 B)和“Merge B into A”(即合并期间检出 A)之间产生的合并提交。有完全不同的,它们让您了解开发流程是什么。当然,merge-commit 的内容是一样的,只是它读出的历史会不一样。
    • @Ryuu:重要的是第一父与第二父,当然,一旦 Git 提交,哪个分支 name Git 会更新。但是我们可以随时更改分支 name 指向的提交哈希,这意味着如果我们愿意,我们可以“向后”进行合并,然后将名称打乱。这仅留下第一个与第二个父级,这就是答案的全部内容:最后,我们可能希望使用git commit-tree 将错误的父级合并复制到不同的更正合并,同时保留树等.
    【解决方案2】:

    在 master 上提交合并,在该提交上创建一个新分支,并将 master 分支重置为上一个提交。

    git branch temp
    git reset --hard HEAD~1
    

    (这是假设您的工作副本中没有其他要保留的内容,您会在重置时丢失它!)

    你回到了你开始的地方(合并前),但你的冲突解决方案是安全的,如果你想要的话。

    现在再次尝试合并,以正确的方式(主 -> 功能)。如果你幸运的话,这种方式不会有那么多冲突,你可以完成它。

    如果这对您没有好处,您可以合并或挑选您的临时分支到功能中。它在图表上看起来不正确,但这就是价格!

    完成后记得删除多余的分支。

    【讨论】:

      【解决方案3】:

      你可以做几件事:

      1. 使用 git rebase -i 手动执行并手动反转提交的顺序。

      2. 编写一个循环遍历git log --reverse 的脚本,并按照您希望的顺序更新提交。

      3. 下次使用git rebase --onto 并决定您希望从哪里开始提交

      --onto &lt;newbase&gt;
      创建新提交的起点。如果未指定 --onto 选项,则起点为 .可以是任何有效的提交,而不仅仅是现有的分支名称。

      作为一种特殊情况,如果只有一个合并基,您可以使用“A...B”作为 A 和 B 的合并基的快捷方式。您最多可以省略 A 和 B 之一,在这种情况下,它默认为 HEAD。

      【讨论】:

        猜你喜欢
        • 2021-08-26
        • 2018-08-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-27
        • 2016-01-05
        • 1970-01-01
        相关资源
        最近更新 更多