为什么git cherry-pick冲突的额外变化?

时间:2018-03-19 15:17:23

标签: git git-cherry-pick

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

我只期待&#34;五&#34;冲突标记之间的界限。 我可以将Git切换到我预期的行为吗?

2 个答案:

答案 0 :(得分:3)

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

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(对于基础)。

要进行合并,Git会生成两个git diff,一个从合并库到左边的本地提交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,以进行新的合并提交,其第一个父级为L,第二个父级为R。最后的合并提交是合并提交,它使用单词“merge”作为形容词。我们经常称它为一个合并,它使用“merge”这个词作为名词。

要获得这个合并为名词,Git必须运行合并机制,以组合两组差异。这是合并的过程,使用“merge”作为动词。

樱桃选择合并

要做一个樱桃选择,Git运行合并机制 - 合并为动词,因为我喜欢把它 - 但是挑出一个特殊的合并基础。樱桃选择的合并基础只是提取的提交的

在您的情况下,您正在挑选提交E。所以Git将(动词)与commit D合并为合并库,将A提交为左/本地 L 提交,并将E提交为右-side 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不再向我们展示我们和他们的<<<<<<<等所包围的部分,而是添加了合并基础版本,标有|||||||

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

答案 1 :(得分:1)

嗯......在我看来,这是一个边缘案例,生活在&#34;樱桃采摘插入源于源分支中的一条线并不存在于目标分支&#34;,以及&#34;变化的进展,所有这些变化都被视为一个&#39; hunk&#39;因为每个都与#34;之前的那个相邻。

这些问题使得git不确定你的意图,所以它要求&#34;我应该做什么&#34;以最广泛的方式,为您提供做出正确决定所需的所有代码。

如果您将文件初始化为

a
b
c
d
e
f
g
h
i

然后创建像

这样的更改
x -- A <--(master)
 \
  C -- E -- G <--(branch)

(其中标记A的提交大写了文件中相应的字母等,这是一个表现得更像你期望的情况 - 因为git的所有内容看起来都非常清楚,它只是显而易见的当你说樱桃挑选Emaster时。 (它不仅不会将无关的变化粘附到冲突标记上,而且没有冲突标记;它只是起作用。)

现在要明确 - 我并不是说樱桃选择只会在我这样明确的案例中做正确的事情。但正如你的测试用例是最糟糕的情况&#34;对于樱桃采摘的场景,我的理想情况。介于两者之间的情况很多,在实践中,人们通常似乎从樱桃挑选中得到他们想要的结果。 (如果这听起来像是对cherry-pick命令的认可,请让我澄清一下:我认为它是套件中最过度推荐的命令,我从来没有真正使用它。但它确实有效,在它的内部定义边界。)

基本记住两件事:

1)如果在源分支上插入新行,并且无法在目标分支中明确确定相应的插入点,则会发生冲突,并且可能包含& #34;上下文&#34;来自源分支

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

相关问题