首页 > 解决方案 > 通过cherry-pick将行添加到不同的位置导致冲突

问题描述

我的分支结构是:

0x1---->0x2---->0x3
/\              /\
|               |
master          dev

共同祖先是0x1。

我挑选了一个要掌握的功能。

场景一:

Master branch have a.txt file.
0x1 first commit.
a.txt content:
1

Then I create a branch dev, I add "2" to a.txt,
0x2 second commit
0x1 first commit

a.txt content:
1
2

Then I add "3" to a.txt
0x3 third commit
0x2 second commit
0x1 first commit

a.txt content:
3
1
2

I cherry-pick 0x3 to master:
master> git cherry-pick 0x3

它没有冲突。

场景2:但我修改了添加的位置。

0x3 third commit
0x2 second commit
0x1 first commit

a.txt content:
1
2
3

我将 0x3 应用于 master。会有冲突。

场景1和场景2有什么区别?我很困惑!

标签: gitversion-controlgit-cherry-pick

解决方案


您的樱桃选择会发生合并冲突,因为相同的合并会发生合并冲突。那是因为樱桃挑选一种合并操作。只是cherry-pick的merge base是特意选择的,最终commit是普通的单亲commit,而不是双亲merge commit。

我认为,冲突本身在完成后更容易解释git merge。假设我们有以下一系列提交:

          I--J   <-- branch1
         /
...--G--H
         \
          K--L   <-- branch2

我们通过选择一个分支名称来选择一些提交,例如 commit J,作为当前提交,例如,作为当前分支。Git 会将提交中的快照提取到我们的工作树中:branch1J

git checkout branch1

我喜欢通过将特殊名称附加HEAD到所选分支名称来表示这种情况,如下所示:

          I--J   <-- branch1
         /
...--G--H
         \
          K--L   <-- branch2

如果我们现在运行git merge branch2,Git 将激活它的合并机制,从而执行合并过程。这个合并机制需要三个提交:

  • 其中一个提交是我们当前的提交J
  • 其中一个提交是我们用 命名的git merge:在这种情况下,branch2选择 commit L
  • 第三个——或者,在某种意义上,第一个,因为 Git 必须首先使用它——commit 是合并过程所定位的。该merge命令找到最佳共享提交:在两个分支上的提交,并且在某种意义上“最接近”两个分支提示提交。在这里,该提交显然是 commit H

要执行实际的合并工作,Git 现在:

  • 将 base ( H) 中的快照与当前提交中的快照进行比较J;和
  • 将 base 中的快照与另一个 commit 中的快照进行比较L

这会产生两个“差异”:两个用于更改出现在合并基础提交中的文件的方法。第一个秘诀说,如果你HJ. 第二个秘诀说,如果你做任何其他不同的事情,你将把文件放在L.

合并引擎现在结合了两个差异。这种组合会产生合并冲突

Git何时发现合并冲突的问题有点奇怪。有一个案例很清楚。假设文件的基本版本在 commitH中读取,例如:

The quick brown fox
jumps over
the lazy dog.

假设JL版本为:

The quick brown fox
sometimes jumps over
the lazy dog.

和:

The quick brown fox
has often jumped over
the lazy dog.

两个差异都以不兼容的方式更改了第 2 行。Git 不知道要进行哪个更改,因此两者都不进行,或两者都进行,这取决于您如何看待它,然后声明合并冲突并让您清理混乱。

你的樱桃精选

[注意:这已被修改为在更新的问题中使用提交图。我们现在使用以下命令创建存储库:

mkdir tcherry && cd tcherry && git init
echo 1 > a.txt && git add a.txt && git commit -m 0x1
git checkout -b dev
echo 2 >> a.txt && git add a.txt && git commit -m 0x2

然后3在顶部插入a.txt或将其添加到末尾a.txt以生成两种场景的设置:

printf "3\n1\n2\n" > a.txt && git add a.txt && git commit -m 0x3
git checkout master
git cherry-pick dev

或者:

echo 3 >> a.txt && git add a.txt && git commit -m 0x3
git checkout master
git cherry-pick dev

第一个工作没有冲突;第二个给出了冲突。]

在您的情况下,您没有使用git merge,您正在使用git cherry-pick. 但挑选樱桃仍然是一种合并。代替:

          I--J   <-- branch1
         /
...--G--H
         \
          K--L   <-- branch2

你有:

  B--C   <-- dev
 /
A   <-- master (HEAD)

你跑git cherry-pick dev。请注意,dev选择 commit C,而您当前的分支是master并且您当前的提交是A.

cherry-pick 命令调用 Git 的合并引擎,但这一次,合并基础被迫成为您正在挑选的提交的父级。所以这里的合并基础是 commit B。Git 现在将:

  • diff Bvs A,以获得“我们的”变化;和
  • diff Bvs C, 以获得“他们的”变化。

B与to的区别A在于您删除了. 以下(使用一种偷偷摸摸的方式来查找正确的提交;请参阅gitrevisions 文档中的描述)显示了这一点:2a.txtgit rev-parse:/<text>

$ git diff :/0x2 :/0x1
diff --git a/a.txt b/a.txt
index 1191247..d00491f 100644
--- a/a.txt
+++ b/a.txt
@@ -1,2 +1 @@
 1
-2

A与to的区别C取决于您使用的是这两种情况中的哪一种。

这是非冲突情况的差异:

$ git diff :/0x2 :/0x3
diff --git a/a.txt b/a.txt
index 1191247..0571a2e 100644
--- a/a.txt
+++ b/a.txt
@@ -1,2 +1,3 @@
+3
 1
 2

这表示要3在 reading 行之前添加1,这是第 1 行并成为第 2 行。所以 Git 应该删除行 reading 2,这是合并基础版本中的第 2 行。Git 可以安全地执行此操作,因为它知道在哪里执行此操作,并且1不会触及行本身:只有从 2 到文件结尾的行受到影响。Git 也应该在 line reading之前插入line reading 。这影响从文件的开头(“第 0 行”)到第 1 行。受影响的行范围不重叠,并且行读数位于它们之间,使它们也不会“接触”彼此。3 11

现在让我们看看确实发生合并冲突的情况:

$ git reset --hard HEAD^          # discard the cherry-pick
HEAD is now at 2b4180d 0x1
$ git checkout -q dev
$ git reset --hard HEAD^          # discard commit 0x3
HEAD is now at 1160130 0x2
$ echo 3 >> a.txt && git add a.txt && git commit -m 0x3
[dev c546f74] 0x3
 1 file changed, 1 insertion(+)
$ cat a.txt
1
2
3
$ git checkout master
Switched to branch 'master'

同样,我们现在正在提交,A我们将指示 Git 挑选父级为. 从to的差异仍然是“删除读取 2 的行”(在第 2 行)。让我们看看这次的差异:C0x3BBABC

git diff :/0x2 :/0x3
diff --git a/a.txt b/a.txt
index 1191247..01e79c3 100644
--- a/a.txt
+++ b/a.txt
@@ -1,2 +1,3 @@
 1
 2
+3

Git 现在应该将删除 line reading2与添加line reading 结合起来3。第一个触及第 2 行(通过完全删除它)。第二个在第 2 行之后添加了一行。

Git当然可以结合这些,但是“这是一个冲突”算法不喜欢两个差异都影响第2行的事实。这些差异在两个更改之间没有一条线,将更改彼此分开。所以我们遇到了冲突:

$ git cherry-pick dev
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
error: could not apply c546f74... 0x3
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

检查a.txt留下的,我们看到:

$ cat a.txt
1
<<<<<<< HEAD
||||||| parent of c546f74... 0x3
2
=======
2
3
>>>>>>> c546f74... 0x3

请注意,我已merge.conflictStyle设置为diff3,所以我得到了这||||||| parent of ...条线,显示了冲突的内容。将其与默认样式进行比较:

$ git checkout -m --conflict merge a.txt
Recreated 1 merge conflict
$ cat a.txt
1
<<<<<<< ours
=======
2
3
>>>>>>> theirs

我个人觉得更神秘。在像这样的樱桃挑选的情况下,从BA“向后”的差异这一事实意味着我们删除了第 2 行。这构成ours了冲突的一部分。从merge风格冲突来看,这一点并不明显;在diff3样式中,我可以看一下,并看到合并基础版本有一行阅读2,我已将其删除。当他们添加他们的行时,他们保留了他们的第 2 行3


推荐阅读