通过 cherry-pick 添加行到不同位置导致冲突
Add line to differnent position cause conflict by cherry-pick
我的分支结构是:
0x1---->0x2---->0x3
/\ /\
| |
master dev
共同祖先是 0x1。
我挑选了一个要掌握的功能。
场景 1:
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
没有冲突。
场景二:
但是我修改了添加的位置。
0x3 third commit
0x2 second commit
0x1 first commit
a.txt content:
1
2
3
我申请0x3为主。会有冲突。
场景一和场景二有什么区别?我很困惑!
您的 cherry-pick 发生合并冲突,因为相同的合并会发生合并冲突。这是因为 cherry-pick 是 合并操作。只是特意选择了cherry-pick的merge base,最终commit是普通的单亲commit,而不是双亲merge commit
我认为,当用 git merge
完成时,冲突本身更容易解释。假设我们有以下一系列提交:
I--J <-- branch1
/
...--G--H
\
K--L <-- branch2
我们通过选择一个分支名称来选择一些提交,例如提交J
,作为当前提交,例如branch1
,成为当前分支。 Git 会将提交 J
中的快照提取到我们的工作树中:
git checkout branch1
我想通过将特殊名称 HEAD
附加到所选分支名称来表示这种情况,如下所示:
I--J <-- branch1
/
...--G--H
\
K--L <-- branch2
如果我们现在运行 git merge branch2
,Git将激活它的合并机制,从而执行合并过程。此合并机制需要 三个 提交:
- 其中一个提交是我们当前的提交
J
.
- 其中一个提交是我们用
git merge
命名的提交:在这种情况下,branch2
选择提交 L
。
- 第三个——或者,在某种意义上,第一个,因为Git必须首先使用它——提交是合并进程定位的那个。
merge
命令找到 最佳共享提交: 一个在两个分支上的提交,并且在某种意义上“最接近”两个分支提示提交。在这里,该提交显然是提交 H
.
要执行实际的合并工作,Git现在:
- 将基础 (
H
) 中的快照与当前提交中的快照进行比较 J
;和
- 将基础中的快照与其他提交中的快照进行比较
L
。
这会产生两个“差异”:两个用于更改出现在合并基础提交中的文件的方法。第一个秘诀说,如果您对 H
中的文件执行各种操作(例如添加 and/or 删除特定文件的特定行),您将获得 J
中的文件。第二个秘诀说,如果你做任何其他事情,你会得到 L
.
中的文件
合并引擎现在合并了两个差异。这种合并会产生 合并冲突。
何时 Git 发现合并冲突的问题有点奇怪。有一个案例就很清楚了。假设文件的基本版本在提交 H
中读取,例如:
The quick brown fox
jumps over
the lazy dog.
假设 J
和 L
版本为:
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
然后在 a.txt
的顶部插入 3
或将其添加到 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
选择提交 C
,而您当前的分支是 master
,您当前的提交是 A
。
cherry-pick 命令调用 Git 的合并引擎,但是这次,合并基础 被强制成为您要提交的提交的父级采摘樱桃。所以这里的合并基础是commit B
。 Git 现在将:
- diff
B
vs A
,以获得“我们的”改变;和
- 差异
B
与 C
,以获得“他们的”更改。
B
与 A
的区别在于您 删除了 a.txt
末尾的 2
行.以下(使用偷偷摸摸的 git rev-parse
找到正确的提交;参见 the gitrevisions documentation 中 :/<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
和 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
这表示要在读取 1
的行之前添加 3
,这是第 1 行变成第 2 行。所以 Git 应该 delete 行读取 2
,这是合并基础版本中的第 2 行。 Git 可以安全地执行此操作,因为它知道在哪里执行此操作,并且行 1
本身未被触及:只有从 2 到文件末尾的行受到影响。 Git 也应该 插入 行 3
之前 行 1
。这影响从文件的开头(“第 0 行”)到第 1 行。受影响的行范围不重叠,并且读取 1
的行位于它们之间,使它们也不会“接触”彼此。
现在让我们看看确实发生合并冲突的情况:
$ 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 挑选提交 C
(主题 0x3
),其父项是B
。从 B
到 A
的差异仍然是“删除行读数 2”(在第 2 行)。这次让我们看看从 B
到 C
的差异:
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 现在应该结合删除行 2
和添加行 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
我个人觉得比较神秘。在像这样的樱桃选择的情况下,从 B
到 A
的差异“向后”这一事实意味着我们删除了第 2 行。这形成了 ours
冲突的一部分。从 merge
风格冲突来看一点也不明显;在 diff3
样式中,我可以查看它并看到合并基础版本有一行内容为 2
,我已将其删除。他们在添加行 3
.
时 保留了第 2 行
我的分支结构是:
0x1---->0x2---->0x3
/\ /\
| |
master dev
共同祖先是 0x1。
我挑选了一个要掌握的功能。
场景 1:
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
没有冲突。
场景二: 但是我修改了添加的位置。
0x3 third commit
0x2 second commit
0x1 first commit
a.txt content:
1
2
3
我申请0x3为主。会有冲突。
场景一和场景二有什么区别?我很困惑!
您的 cherry-pick 发生合并冲突,因为相同的合并会发生合并冲突。这是因为 cherry-pick 是 合并操作。只是特意选择了cherry-pick的merge base,最终commit是普通的单亲commit,而不是双亲merge commit
我认为,当用 git merge
完成时,冲突本身更容易解释。假设我们有以下一系列提交:
I--J <-- branch1
/
...--G--H
\
K--L <-- branch2
我们通过选择一个分支名称来选择一些提交,例如提交J
,作为当前提交,例如branch1
,成为当前分支。 Git 会将提交 J
中的快照提取到我们的工作树中:
git checkout branch1
我想通过将特殊名称 HEAD
附加到所选分支名称来表示这种情况,如下所示:
I--J <-- branch1
/
...--G--H
\
K--L <-- branch2
如果我们现在运行 git merge branch2
,Git将激活它的合并机制,从而执行合并过程。此合并机制需要 三个 提交:
- 其中一个提交是我们当前的提交
J
. - 其中一个提交是我们用
git merge
命名的提交:在这种情况下,branch2
选择提交L
。 - 第三个——或者,在某种意义上,第一个,因为Git必须首先使用它——提交是合并进程定位的那个。
merge
命令找到 最佳共享提交: 一个在两个分支上的提交,并且在某种意义上“最接近”两个分支提示提交。在这里,该提交显然是提交H
.
要执行实际的合并工作,Git现在:
- 将基础 (
H
) 中的快照与当前提交中的快照进行比较J
;和 - 将基础中的快照与其他提交中的快照进行比较
L
。
这会产生两个“差异”:两个用于更改出现在合并基础提交中的文件的方法。第一个秘诀说,如果您对 H
中的文件执行各种操作(例如添加 and/or 删除特定文件的特定行),您将获得 J
中的文件。第二个秘诀说,如果你做任何其他事情,你会得到 L
.
合并引擎现在合并了两个差异。这种合并会产生 合并冲突。
何时 Git 发现合并冲突的问题有点奇怪。有一个案例就很清楚了。假设文件的基本版本在提交 H
中读取,例如:
The quick brown fox
jumps over
the lazy dog.
假设 J
和 L
版本为:
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
然后在 a.txt
的顶部插入 3
或将其添加到 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
选择提交 C
,而您当前的分支是 master
,您当前的提交是 A
。
cherry-pick 命令调用 Git 的合并引擎,但是这次,合并基础 被强制成为您要提交的提交的父级采摘樱桃。所以这里的合并基础是commit B
。 Git 现在将:
- diff
B
vsA
,以获得“我们的”改变;和 - 差异
B
与C
,以获得“他们的”更改。
B
与 A
的区别在于您 删除了 a.txt
末尾的 2
行.以下(使用偷偷摸摸的 git rev-parse
找到正确的提交;参见 the gitrevisions documentation 中 :/<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
和 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
这表示要在读取 1
的行之前添加 3
,这是第 1 行变成第 2 行。所以 Git 应该 delete 行读取 2
,这是合并基础版本中的第 2 行。 Git 可以安全地执行此操作,因为它知道在哪里执行此操作,并且行 1
本身未被触及:只有从 2 到文件末尾的行受到影响。 Git 也应该 插入 行 3
之前 行 1
。这影响从文件的开头(“第 0 行”)到第 1 行。受影响的行范围不重叠,并且读取 1
的行位于它们之间,使它们也不会“接触”彼此。
现在让我们看看确实发生合并冲突的情况:
$ 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 挑选提交 C
(主题 0x3
),其父项是B
。从 B
到 A
的差异仍然是“删除行读数 2”(在第 2 行)。这次让我们看看从 B
到 C
的差异:
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 现在应该结合删除行 2
和添加行 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
我个人觉得比较神秘。在像这样的樱桃选择的情况下,从 B
到 A
的差异“向后”这一事实意味着我们删除了第 2 行。这形成了 ours
冲突的一部分。从 merge
风格冲突来看一点也不明显;在 diff3
样式中,我可以查看它并看到合并基础版本有一行内容为 2
,我已将其删除。他们在添加行 3
.