为什么在git cherry-pick冲突中需要额外的更改?

12
git cherry-pick 命令在产生冲突时,为什么 Git 建议的变更不止来自于给定的提交?
示例:
-bash-4.2$ git init
Initialized empty Git repository in /home/pfusik/cp-so/.git/
-bash-4.2$ echo one >f
-bash-4.2$ git add f
-bash-4.2$ git commit -m "one"
[master (root-commit) d65bcac] one
 1 file changed, 1 insertion(+)
 create mode 100644 f
-bash-4.2$ git checkout -b foo
Switched to a new branch 'foo'
-bash-4.2$ echo two >>f
-bash-4.2$ git commit -a -m "two"
[foo 346ce5e] two
 1 file changed, 1 insertion(+)
-bash-4.2$ echo three >>f
-bash-4.2$ git commit -a -m "three"
[foo 4d4f9b0] three
 1 file changed, 1 insertion(+)
-bash-4.2$ echo four >>f
-bash-4.2$ git commit -a -m "four"
[foo ba0da6f] four
 1 file changed, 1 insertion(+)
-bash-4.2$ echo five >>f
-bash-4.2$ git commit -a -m "five"
[foo 0326e2e] five
 1 file changed, 1 insertion(+)
-bash-4.2$ git checkout master
Switched to branch 'master'
-bash-4.2$ git cherry-pick 0326e2e
error: could not apply 0326e2e... five
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'
-bash-4.2$ cat f
one
<<<<<<< HEAD
=======
two
three
four
five
>>>>>>> 0326e2e... five

我本来期望冲突标记之间只有"five"这一行。 我能否将Git切换到我的预期行为?

2个回答

14
在我们进一步讨论之前,让我们先画出提交图:
A   <-- master (HEAD)
 \
  B--C--D--E   <-- foo

为了方便您比较,以下是Git的绘制方式:
$ git log --all --decorate --oneline --graph
* 7c29363 (foo) five
* a35e919 four
* ee70402 three
* 9a179e6 two
* d443a2a (HEAD -> master) one

请注意,我已将您的问题转化为命令序列,并附加了它;我的提交哈希值当然与您的不同。

Cherry-pick是一种特殊的合并形式

在这里看到的有些病态行为的原因是git cherry-pick实际上执行了一个合并操作。其中最奇怪的部分是选择的合并基础。

正常的合并

对于正常的合并,您可以通过检出某个提交(通过检出某个分支来检出该分支的顶部提交)并运行git merge other来完成。Git会定位由other指定的提交,然后使用提交图来定位合并基础,这通常从图中很明显。例如,当图像如下所示时:

          o--o--L   <-- ours (HEAD)
         /
...--o--B
         \
          o--o--R   <-- theirs

合并基础就是提交B(代表base)。

为了进行合并,Git会从合并基础到我们本地的提交L(左侧)和他们的提交R(右侧,有时称为远程提交)分别进行两个git diff操作。即:

git diff --find-renames B L   # find what we did on our branch
git diff --find-renames B R   # find what they did on theirs

Git可以将这些变更合并在一起,将合并后的变更应用于B,生成一个新的合并提交,其第一个父提交是L,第二个父提交是R。最终合并提交称为合并提交,使用"merge"作为形容词。我们通常只称之为一次合并,使用"merge"作为名词。
但是要获得这种名词式的合并,Git必须运行合并机制,以合并两组差异。这是合并的过程,使用"merge"作为动词。

樱桃拣选式合并

要执行樱桃拣选(cherry-pick)操作,Git会运行合并机制——也就是我喜欢称之为合并作为动词的过程——但会挑选出一个特殊的合并基础。此时,樱桃拣选的合并基础就是所选提交的父提交
在您的情况下,您正在拣选提交E。因此,Git会将提交D作为合并基础,将提交A作为左侧/本地L提交,将提交E作为右侧R提交,运行合并(verb)机制。Git会生成两个内部等效的差异清单列表:
git diff --find-renames D A   # what we did
git diff --find-renames D E   # what they did

我们所做的是删除四行:分别是twothreefour。他们所做的是添加一行:five

使用merge.conflictStyle

如果我们将merge.conflictStyle设置为diff3,这一切就会变得更加清晰——或许有点儿清晰。现在,Git不仅会显示我们的和他们的部分,还会添加标记为|||||||合并基础版本

one
<<<<<<< HEAD
||||||| parent of 7c29363... five
two
three
four
=======
two
three
four
five
>>>>>>> 7c29363... five

我们现在看到Git声称我们从基础版本中删除了三行,而他们保留了这三行并添加了第四行。
当然,我们需要理解这里的合并基础是提交E的父提交,如果有的话,它比我们当前的提交A更加“先进”。实际上,我们并没有真正意义上的删除三行。事实上,我们一开始就没有这三行内容。我们只需要处理Git显示的东西,就好像我们删除了某些行一样。
附录:生成冲突的脚本
#! /bin/sh
set -e
mkdir t
cd t
git init
echo one >f
git add f
git commit -m "one"
git checkout -b foo
echo two >>f
git commit -a -m "two"
echo three >>f
git commit -a -m "three"
echo four >>f
git commit -a -m "four"
echo five >>f
git commit -a -m "five"
git checkout master
git cherry-pick foo

非常清楚。有关如何在Meld中处理这种情况的建议(我已经根据此答案配置了meld“$LOCAL”“$MERGED”“$REMOTE”--output“$MERGED”)?它已经在中间窗格(即 $MERGED)中显示出额外的更改。 - watery

1

嗯……在我看来,这似乎是一个边缘案例,处于“在目标分支中不存在的行上挑选源分支中插入的内容”和“所有更改都被认为是一个‘hunk’(块),因为每个更改都紧邻前面的更改”的交叉点。

这些问题使得git不确定你的意图,所以它以最广泛的方式询问“我应该做什么”,给你所有可能需要做出正确决策的代码。

如果你将文件初始化为

a
b
c
d
e
f
g
h
i

然后进行类似以下的更改

x -- A <--(master)
 \
  C -- E -- G <--(branch)

在提交标记为A的提交中,文件中相应的字母将会大写等等,这是一个行为更符合你期望的情况 - 因为对于Git来说一切看起来都非常清晰,当你告诉它将E移到master分支时,它就会做出显而易见的事情。 (它不仅不会将不相关的更改粘贴到冲突标记中,而且也没有冲突标记;它只是工作正常。)

现在要明确的是 - 我并不是说cherry-pick只会在像我这样明确的情况下做正确的事情。 但是,就像您的测试用例是cherry-pick的“最坏情况”场景一样,我的情况是理想情况。 在中间有许多情况,在实践中人们通常似乎可以从cherry-pick中获得他们想要的结果。 (如果听起来像是对cherry-pick命令的认可,请让我澄清:我认为它是套件中被过度推荐的命令,并且我从未真正使用过它。 但是,在其定义的范围内它确实有效。)

基本上记住两件事:

1)如果在源分支上插入了新行,并且在目标分支中无法明确确定相应的插入点,则会发生冲突,它可能包括来自源分支的“上下文”。

2)如果两个更改是相邻的 - 即使它们不重叠 - 它们仍然可以被视为冲突。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接