对于导致冲突的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

我期望冲突标记之间只有“五个”线。
我可以将Git切换为预期的行为吗?

最佳答案

在进一步介绍之前,让我们绘制提交图:

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是 merge 的一种特殊形式

您在此处看到某种病理行为的原因是git cherry-pick实际上正在执行 merge 操作。与此有关的最奇怪的部分是所选的 merge 基础。

正常 merge

对于正常的 merge ,您需要 checkout 一些提交(通过 checkout 一些分支来 checkout 该分支的尖端提交)并运行git merge other。 Git定位other指定的提交,然后使用提交图来定位 merge 基础,这在图中通常很明显。例如,当图形如下所示:
          o--o--L   <-- ours (HEAD)
         /
...--o--B
         \
          o--o--R   <-- theirs

merge 基础只是提交B(用于基础)。

为了进行 merge ,Git然后创建两个git diff,一个从 merge 库到我们的本地提交L在左边,而他们的提交R在右边(有时称为远程提交)。那是:
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,以进行新的 merge 提交,其第一个父级为L,第二个父级为R。最后的 merge 提交是 merge 提交,它使用单词“merge”作为形容词。我们通常将其称为 merge ,它使用单词“merge”作为名词。

但是,为了获得这种“名词 merge ”,Git必须运行 merge 机制,以 merge 两组差异。这是 merge 的过程,使用单词“merge”作为动词。

樱桃选择 merge

为了进行挑选,Git运行了 merge 机制(如我喜欢说的那样,将 merge 作为动词进行 merge ),但选择了一个特殊的 merge 基础。樱桃选择的 merge 基础只是被樱桃选择的提交的父对象。

就您而言,您正在挑选commit E。因此,Git正在 merge (动词),并以D提交为 merge 基础,将A提交为左侧/本地L提交,并以E提交为右侧R提交。 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不仅向我们展示了我们的以及由<<<<<<<等包围的部分,还添加了 merge 基础版本,并标记了|||||||:
one
<<<<<<< HEAD
||||||| parent of 7c29363... five
two
three
four
=======
two
three
four
five
>>>>>>> 7c29363... five

现在,我们看到Git声称我们从基础上删除了三行,而他们保留了这三行并添加了第四行。

当然,我们需要了解,这里的 merge 基础是commit E的父级,也就是说,是否有任何东西比我们当前的commit 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

09-04 19:04
查看更多