git - 通过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有什么区别?我很困惑!
解决方案
您的樱桃选择会发生合并冲突,因为相同的合并会发生合并冲突。那是因为樱桃挑选是一种合并操作。只是cherry-pick的merge base是特意选择的,最终commit是普通的单亲commit,而不是双亲merge commit。
我认为,冲突本身在完成后更容易解释git merge
。假设我们有以下一系列提交:
I--J <-- branch1
/
...--G--H
\
K--L <-- branch2
我们通过选择一个分支名称来选择一些提交,例如 commit J
,作为当前提交,例如,作为当前分支。Git 会将提交中的快照提取到我们的工作树中:branch1
J
git checkout branch1
我喜欢通过将特殊名称附加HEAD
到所选分支名称来表示这种情况,如下所示:
I--J <-- branch1
/
...--G--H
\
K--L <-- branch2
如果我们现在运行git merge branch2
,Git 将激活它的合并机制,从而执行合并过程。这个合并机制需要三个提交:
- 其中一个提交是我们当前的提交
J
。 - 其中一个提交是我们用 命名的
git merge
:在这种情况下,branch2
选择 commitL
。 - 第三个——或者,在某种意义上,第一个,因为 Git 必须首先使用它——commit 是合并过程所定位的。该
merge
命令找到最佳共享提交:在两个分支上的提交,并且在某种意义上“最接近”两个分支提示提交。在这里,该提交显然是 commitH
。
要执行实际的合并工作,Git 现在:
- 将 base (
H
) 中的快照与当前提交中的快照进行比较J
;和 - 将 base 中的快照与另一个 commit 中的快照进行比较
L
。
这会产生两个“差异”:两个用于更改出现在合并基础提交中的文件的方法。第一个秘诀说,如果你H
对J
. 第二个秘诀说,如果你做任何其他不同的事情,你将把文件放在L
.
合并引擎现在结合了两个差异。这种组合会产生合并冲突。
Git何时发现合并冲突的问题有点奇怪。有一个案例很清楚。假设文件的基本版本在 commitH
中读取,例如:
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
然后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
B
vsA
,以获得“我们的”变化;和 - diff
B
vsC
, 以获得“他们的”变化。
B
与to的区别A
在于您删除了. 以下(使用一种偷偷摸摸的方式来查找正确的提交;请参阅gitrevisions 文档中的描述)显示了这一点:2
a.txt
git 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
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 挑选父级为. 从to的差异仍然是“删除读取 2 的行”(在第 2 行)。让我们看看这次的差异:C
0x3
B
B
A
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 现在应该将删除 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
我个人觉得更神秘。在像这样的樱桃挑选的情况下,从B
到A
“向后”的差异这一事实意味着我们删除了第 2 行。这构成ours
了冲突的一部分。从merge
风格冲突来看,这一点并不明显;在diff3
样式中,我可以看一下,并看到合并基础版本有一行阅读2
,我已将其删除。当他们添加他们的行时,他们保留了他们的第 2 行3
。
推荐阅读
- c# - 文本框自动完成 - 单击建议的项目时防止自动提交(Keydown with Keycode.Enter)
- powershell - 使用 PowerShell 计算数组中的列数
- dart - 对于 Dart 初学者来说超级简单但很难的问题
- c# - 为什么添加两个 .OrderBy(或 .OrderByDescending)语句会以相反的顺序应用排序?
- sql-server - 使用 FreeTDS 连接到 Raspberry Pi 上的 Azure SQL 服务器
- python - 在 django restframework 视图中显示外键选项
- javascript - .after() 省略了一些子 div
- django - 是否可以仅使用 Django 从 REST API 获取数据并将其绘制为图形?
- linux-kernel - 来自 Linux 内核驱动程序的 PCI-ISA 桥接通信
- ios - 如何手动调用“tableView numberOfRowsInSection”函数