0

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

我所做的是以下

git checkout master
git merge Feature-X

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

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

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

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

4

3 回答 3

7

TL;DR:看到最后

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

好消息和坏消息

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

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

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

合并(“合并为动词”)的branch1过程branch2通过以下方式实现:

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

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

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

(我将合并提交命名为M因为我们不知道或真正关心什么疯狂的 Git 哈希 IDdeadbeefbadc0ffee其他任何东西。)最终的合并提交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

新的合并提交在当前分支上进行,就像任何其他提交一样。所以就这样M结束了branch1。请注意,合并提交的消息还说明了哪个分支名称已合并到哪个其他分支名称中——但是您有机会在进行提交时编辑它,因此您可以更改它以适应您可能有的任何想法。:-)

(事实上​​,这与无冲突的情​​况没有什么不同:两者都运行git commit以进行最终的合并提交,并且都让您有机会编辑消息。)

坏消息

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

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


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


得到我们想要的合并

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

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

现在我们有:

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

whereM的第一个父母是旧的branch1小费。

我们现在想要的是进行一个的合并提交(具有不同的 ID),它与以下内容相同M

  • branch2指向它
  • 它的第一个父母是branch2
  • 它的第二个父节点branch1

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

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

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

          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——但一旦我们做出,它的第一个父项将是 的当前提示branch2,而它的第二个父项将是 的当前提示branch1。我们只需要获得正确的资源来完成我们将要进行的提交。

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

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

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

git commit

并且我们根据需要M2在 branch 上有了新的 merge branch2

          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并运行git commit了,我们就有了正确的树,我们只是有一个具有错误的第一个和第二个父 ID 的提交。您可能还希望编辑提交消息,以便看起来您正在将消息中的 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步骤创建了一个新的合并提交,它是我们刚刚创建的那个的副本,但是交换了两个父级。然后branch2,我们使用git branch -f. 确保branch2在此步骤中获得正确的名称 ( )。HEAD^1HEAD^2名称指的是当前提交的两个(第一个和第二个)父级 ,当然是我们刚刚进行的合并提交。具有正确的树和正确的提交消息文本;它只是有错误的第一个和第二个父哈希。

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

于 2017-04-06T20:14:34.713 回答
4

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

git branch temp
git reset --hard HEAD~1

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

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

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

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

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

于 2017-04-06T14:01:14.497 回答
2

你可以做几件事:

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

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

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

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

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

在此处输入图像描述

于 2017-04-06T13:25:02.610 回答