为什么在 git 樱桃挑选冲突中进行额外的更改



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

(请注意,我把你的问题变成了一个命令序列,我已经附加了这个序列;我的提交哈希当然与你的不同。

樱桃采摘是一种特殊的合并形式

您在此处看到有些病态行为的原因是git cherry-pick实际上是在执行合并操作。 最奇怪的部分是选择的合并基础。

正常合并

对于普通合并,您签出某个提交(通过签出某个分支来签出该分支的提示提交)并运行git mergeother。 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"作为形容词。 我们通常只是称它为合并,它使用">合并"一词作为名词。

但是,为了获得这个合并作为名词,Git 必须运行合并机制,以组合两组差异。 这是合并的过程,使用">合并"一词作为动词。

樱桃合并

为了做一个挑选,Git 运行合并机制——合并作为一个动词,正如我喜欢说的那样——但挑选了一个特殊的合并基础。 樱桃拣选的合并基只是被挑选的提交的父级

在您的情况下,您正在挑选提交E。 所以 Git 正在合并(动词),将提交D作为合并基础,将提交A作为左侧/本地L提交,并将 commitE作为右侧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

嗯...在我看来,这是一个边缘情况,生活在"挑选在源分支中以目标分支中不存在的行进行的插入"和"所有都被视为一个'笨蛋'的更改的进展,因为每个都与前一个相邻"。

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

如果将文件初始化为

a
b
c
d
e
f
g
h
i

然后创建类似

x -- A <--(master)

C -- E -- G <--(branch)

(其中 commit 标记为大写A文件中的相应字母等),这种情况的行为将更像您的预期 - 因为 git 的所有内容看起来都非常清楚,当您说挑选Emaster时,它只是做了显而易见的事情。 (它不仅不会将不相关的更改粘贴到冲突标记中,而且没有冲突标记;它只是工作。

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

基本上记住两件事:

1)如果您在源分支上插入新行,并且无法在目标分支中明确确定相应的插入点,则会遇到冲突,并且可能会包含来自源分支的"上下文">

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

相关内容

  • 没有找到相关文章

最新更新