例如,在一个分支中,有3个提交:A <- B <- C
。如果我直接选择B
(测试A),Git会说:
The previous cherry-pick is now empty, possibly due to conflict resolution.
If you wish to commit it anyway, use:
git commit --allow-empty
我可以理解,因为B
已经在这个分支中了,所以不可能再挑剔它。
然后,我通过在批量提交中恢复了B
和C
git revert -n B^..C
git commit -a -m "xxx"
这将是一个新的大提交D
,它恢复B
和C
,分支应该类似于A <- B <- C <- D
。
然后由于某种原因,我需要重做B
和C
。我试过了:
git cherry-pick B^..C
我看到两个新的提交B'
和C'
被附加到分支:A <- B <- C <- D <- B' <- C'
。
我的第一个问题是,Git如何智能地知道它应该创建B'
和C'
?我以为Git会发现B
和C
已经在分支历史中了,所以它可能会跳过它们,就像我在测试A中直接选择"B"一样。
然后,在那之后,由于分支已经是A <- B <- C <- D <- B' <- C'
,我再次运行这个命令:
git cherry-pick B^..C
我本以为Git会意识到这是一个无操作的操作。但这一次Git抱怨矛盾。我的第二个问题是,为什么Git这次无法识别并跳过此操作?
cherry-pick是一个合并,从cherry-tick的父项到cherry-rick的diff,以及从cherry pick的父级到签出提示的diff。就是这样,Git不需要知道更多。它不在乎"其中";任何提交都是,它关心合并这两组diff。
revert是从您的revert到其父项的diff与从您的evert到您的签出提示的diff的合并。就是这样,Git不需要知道更多。
这里:试试这个:
git init test; cd $_
printf %s\n 1 2 3 4 5 >file; git add .; git commit -m1
sed -si 2s,$,x, file; git commit -am2
sed -si 4s,$,x, file; git commit -am3
运行git diff :/1 :/2
和git diff :/1 :/3
。这些是当你在这里说git cherry-pick :/2
时的diffs git运行。第一个diff更改第2行,第二个commit更改第2和第4行;第4行的变化不与第一diff中的任何变化邻接,并且第2行的变化在两者中是相同的。没有什么可做的了,所有:/1
-:/2
的更改也都在:/1
-:/3
中。
现在,在你开始下面的内容之前,让我说一下:这比仅仅看到更难用散文来解释。执行上面的示例序列,然后查看输出。通过看它比阅读任何描述都更容易看到发生了什么。每个人都会经历一段时间,这太新了,也许一点方向会有所帮助,这就是下面段落的目的,但同样:散文本身比差异更难理解。运行diffs,试着理解你在看什么,如果你需要一点帮助,我保证会在下面的文本中出现一个非常小的凸起。当它突然聚焦时,看看你是否至少在精神上拍打你的前额,并思考";哇,为什么这么难看到&";,就像,嗯,几乎每个人。
Git的合并规则非常简单:对重叠或邻接行进行相同的更改是可以接受的。对行的更改,其中一个diff中没有更改更改的行,或者对与更改的行相邻的行,另一个diffs中没有更改,都可以原样接受。对任何重叠或相邻行的不同更改,好吧,有很多历史要看,没有人找到一个规则来预测每次的结果,所以git声明更改冲突,将两组结果转储到文件中,并让您决定结果应该是什么
那么,如果您现在更改第3行,会发生什么呢?
sed -si 3s,$,x, file; git commit -amx
运行git diff :/1 :/2
和git diff :/1 :/x
,您会看到,相对于樱桃采摘的父代,:/2
更改了第2行,而您的尖端更改了第2,3行和第4行。2和3邻接,这在历史上太接近了,自动化天才无法正确处理,所以是的,你可以这样做:git cherry-pick :/2
现在将宣布冲突,向您展示对第2行的更改以及第3行和第4行的两个不同版本(:/2都没有更改,您的提示也都更改了,在这里的上下文中,很明显,第3行与第4行更改是可以的,但再次强调:没有人能想出可靠识别此类上下文的自动规则)。
您可以对此设置进行更改,以测试还原的工作方式。还隐藏pops和merges,以及git checkout -m
,它会对索引进行快速的特别合并。
您的git cherry-pick B^..C
是从两个提交(B
和C
)中挑选出来的。它一个接一个地完成它们,正如上面所描述的那样。由于您恢复了B
和C
,然后再次樱桃采摘它们,因此这与应用B
和C
,然后樱桃采摘B
(目的是樱桃采摘C
)具有完全相同的效果。我得出的结论是,B
和C
接触到重叠或邻接的线,所以git diff B^ B
将在git diff B^ C'
中显示重叠或邻接变化的变化,这是Git不会为您选择的,因为无论这里看起来如何,在其他情况下,没有人能写出识别规则,相同的选择都是错误的。所以git说这两组变化是冲突的,你需要解决它。
这扩展了@jthill的答案。
考虑一下历史上的常规合并:
a--b--c--d--e--f--g--h
r--s--t
Git通过只查看这些提交的内容来执行合并:
c--h <-- theirs
t <-- ours
^
|
base
什么都没有。注意,在概念层面上,哪一边被表示为"边"是完全无关的;我们的";并且是";他们的";;它们完全可以互换。(唯一有区别的是当发生冲突时,Git必须决定如何为用户将双方标记为"他们的"one_answers"我们的"。)
在您的历史
A--B--C
第一个git cherry-pick B
后面的合并操作着眼于以下提交:
A--B
C
这里,之所以选择A
,是因为它是B
的父级,也就是B^
。显然,从A
到C
的变化也包含从A
到B
的变化,合并机制产生无变化合并结果,并产生cherry-pick is now empty
消息。
然后,您通过恢复B
和C
:来创建此历史
A--B--C--R
然后,下一个git cherry-pick B
查看了这些提交:
A--B
R
这一次,从A
到R
的更改不再包含从A
到B
的更改,因为它们已被还原。因此,合并不再产生空结果。
一个小迂回:当您在历史中执行git revert B
时,合并机制会查看以下提交:
B--A
C
注意,与git cherry-pick B
相比,只有B
和B
的父级,也就是A
被交换。
(我描述的是一次提交冲销,因为我不确定多次提交冲销是如何工作的。)
让我们后退大约十英尺,在脑海中更清楚地了解Git是什么。
Git提交是所有文件的快照。它基本上代表了你的整个项目。这与差异无关。这是一个出色的体系结构,因为它速度极快,而且有效无误。任何提交都可以完全恢复项目的状态,kaboom,只需检查即可;没有必要";思考";。
然而,Git可以在两次提交之间进行差异,这就是它实现我们可以称之为";合并逻辑";。每次合并都包括同时应用两个diff。[好吧,它可能不止两个,但假装不是。]合并、樱桃采摘、重新基础、恢复都是这个意义上的合并——它们都使用";合并逻辑";以形成表示应用两个diff的结果的提交。诀窍是要知道在构造这两个diff时比较的是谁。
-
当你要求一个真正的
git merge
时,比如说两个分支,Git会计算出这些分支最后分叉的位置。这被称为合并基础。分为:分支1的并生基部和顶端,分支2的并生基部和顶端。这两个diff都应用于合并库,结果用于与两个父级(分支提示)形成提交。然后,第一个分支名称向上滑动一个,指向新的提交。 -
当您请求
cherry-pick
时,合并基是所选择的提交的父级。比较是:合并基础和头,以及合并基础和选择的提交。这两个diff都应用于合并基,结果用于与一个父级(头)形成提交。然后,头分支名称向上滑动一个,指向新的提交。 -
revert
也使用合并逻辑。正如jthill所解释的,这只是形成一个向后的diffs的问题。合并基是您试图撤消的提交。比较是:合并基部及其父级(在该方向),以及合并基部和头部。这些diff应用于合并基,并用于形成父级为头的提交。然后,头分支名称向上滑动一个,指向新的提交。如果这向你表明,回归基本上是一种向后的樱桃选择,那么你是绝对正确的。
最酷的是,一旦你知道了这一点,你就可以预测当你发出其中一个命令时会发生什么,因为你可以通过说git diff
来提取相同的差异。Git的合并逻辑本质上是开放的。接下来只需要了解Git在操作过程中停止的情况,因为如果没有进一步的明确指示,它将无法继续。这被称为(不幸的)冲突,主要有两种方式:
-
同一文件中的同一行在两个diff中以两种不同的方式更改。Git关于同一条线的概念比你想象的要宽泛得多;这让初学者感到惊讶。
-
同一个文件,qua文件,以两种不兼容的方式处理:例如,一个diff删除了它,但另一个diff保留并编辑了它
我应该再加一个事实来解释很多行为,包括你所问的部分内容。这可能看起来很明显,但值得明确说明:在diff中,"什么都没有";不是一件事我的意思是这个。假设一个diff改变了一行,而另一个diff对该行没有任何作用。那么,实现这两种差异的方法是:改变路线。无所事事不是一件事:;扭打;反对改变。
这一点值得一提,尤其是因为初学者通常不掌握。前几天有一个问题,用户抱怨在第二个分支删除文件的合并中,即使第一个分支保留了文件,文件也确实被删除了;不要删除文件";作为一件事,实际上也是一件首要的事。但事实并非如此。默认情况下,这两个diff的权重相等,因此一个分支什么都没做,一个分支删除了文件,什么都不做不是一件事,因此结果是删除了文件。