我已经克隆了一个远程存储库,创建了一个新分支b
,并开始工作和进行提交。我也推过b
分支,但它是唯一正在处理它的分支。
过一会儿,我想在远程主服务器上重新建立本地分支,只是为了与一般系统可能发生的更改同步。请注意,我确定是唯一正在处理这些特定文件的人。
所以我做到了
git fetch --all
git rebase origin/master
然后Git通知我合并冲突。
现在,我可以轻松地手动解决冲突,但是我为此感到困扰:为什么会发生合并冲突?
如果我没记错的话,git rebase
的全部想法是在指定分支的顶端“重播”当前分支的所有提交。我是唯一处理这些特定文件或该特定分支的人。
那为什么会发生呢?我的方法有问题吗?
答案 0 :(得分:3)
我认为这有助于了解git rebase
确实是重复运行git cherry-pick
的自动化方法。但这仅在您还意识到git cherry-pick
是合并形式的情况下才有用。这就是合并冲突的来源。
在查看常规合并时,更容易理解这一点。让我们来绘制一个提交图,每次提交都使用单个大写字母,就像这样:
I--J <-- branch1 (HEAD)
/
...--G--H
\
K--L <-- branch2
如果我们运行git merge branch2
,Git会发现三个提交:
一个提交(最后是#2)始终是当前或HEAD
提交。由于HEAD
附加在名称branch1
上,因此当前提交是branch1
所标识的提交:J
。
最后的#3提交是提交 you 的名称。通过使用名称branch2
,您告诉Git读取该名称,并看到它指向提交L
。
#1提交是Git自己找到的。 Git通过找到两个分支上的 best 提交来做到这一点。 branch1
上的提交为...-G-H-I-J
。 branch2
上的提交为...-G-H-K-L
。因此,提交G
在两个分支上,但比在两个分支上的提交H
还远。提交I-J
仅在一个分支上,K-L
仅在另一分支上。这意味着提交H
是最佳共享提交。
Git现在可以执行合并。为此,Git运行两条 git diff
命令,实际上是:
git diff --find-renames hash-of-H hash-of-J
:这告诉Git在公共起点和您的提交之间发生了什么变化,即您所做的事情。
git diff --find-renames hash-of-H hash-of-L
:这告诉Git在同一起点和他们的提交之间发生了什么变化,即它们做了什么。
merge命令的工作现在是组合您的更改及其更改:
H
中没有人触摸的文件,请保留这些文件。H
中您已更改但未更改的文件H
,请使用您的版本。git cherry-pick
中的文件,请获取其版本。还有其他一些棘手的情况,例如,如果您重命名文件和/或他们重命名了文件,或者您删除了文件并对其进行了修改,将会发生什么,等等。但是大多数情况下,合并冲突是在您都更改了某个文件 且都更改了该文件的相同 lines 或进行了其他修改后发生的。如果您所做的更改及其更改没有“相互影响”,则Git会认为可以保留两个更改。否则,您将发生合并冲突。
在开始的几次中,这有点棘手,但是过了一会儿,感觉还是很自然的。如果爱丽丝将“红球”更改为“蓝球”,而鲍勃将“红球”更改为“红砖”,则Git不知道该怎么做,并让 you 选择正确的答案。
git diff
命令的作用是复制一次提交。也就是说,给定一些提交(代表所有文件的完整快照),我们想弄清楚该文件中发生了什么更改。
在Git中,将两个相邻的提交(一个接一个接一个的快照)转换为一组更改非常容易。我们要做的就是让Git在两个快照上运行--find-renames
。 Git将找出哪些文件是相同的,而对这些文件什么也不说。它将找出哪些文件不同,并产生一个配方(一组要添加和/或删除的行),该配方将先前提交的文件更改为以后提交的副本。如果我们要求使用...--G--H--I--J <-- main
\
K--L <-- feature
(自Git 2.9起默认启用),Git会找出与右侧的新文件相比,左侧丢失的文件代表文件重命名操作,
那么,想象一下,我们有这个:
H
如果我们要求从K
到K
的差异,我们将看到H
与file.py
相比发生了什么变化。例如,可能会说类似“在J
的第72行之后添加此行”之类的话。
但是,如果我们想应用这些更改来提交H
怎么办?我们可以闭上眼睛,希望“在第72行之后添加这行”是有意义的,但是如果是第72行现在是第75行,或者甚至更远,该怎么办?我们可以搜索上下文。但是也许我们可以做得更好。
不仅仅是盲目地应用此更改或检查上下文,如果我们首先抓住第二个差异,提交J
与提交git merge
会怎样?这将告诉我们他们发生了什么变化。如果他们在第72行的上方添加了三行,那么现在第72行就是肯定第75行。所以这告诉我们将更改放在何处。
但请稍等,git cherry-pick
就是这个“拿两个差异并将它们组合”的想法!实际上,这就是J
的工作方式:我们选择要复制的提交的 parent ,并假装它是合并基础。我们得到两个差异,一个从合并基础到我们要复制的提交-这是“它们的”更改-一个从合并基础到我们正在处理的提交,即提交git merge
,这些都是“我们的”更改。我们让Git使用运行git cherry-pick
时使用的相同代码将它们组合在一起。
如果一切顺利,git rebase
将为我们重新提交。 K' <-- HEAD
/
...--G--H--I--J <-- main
\
K--L <-- feature
命令以Git称为分离式HEAD 模式进行所有操作,因此图片如下所示:
K'
我们将新的提交K
称为原始提交L
的副本。现在是时候选择提交K
了,所以现在Git将diff L
与K
进行比较,以了解“他们”(实际上是我们)的变化,以及diff {{1} }与K'
进行比较,以查看“我们”(实际上是从上至下的所有操作,包括之前的“摘樱桃”操作)发生了什么变化。然后,Git将尝试结合这两组更改-K
-vs-K'
的“我们的”和K
-vs-L
的“他们的” —如果一切顺利,git cherry-pick
将重新提交L'
:
K'-L' <-- HEAD
/
...--G--H--I--J <-- main
\
K--L <-- feature
如果事情进展不顺利,在任一git cherry-pick
步骤中,Git都将停止并让我们解决冲突,其方式与git merge
完全相同
一旦所有提交都被复制,git rebase
有一个最后的技巧:将名称 feature
从旧位置移开并粘贴到HEAD
指向的位置现在,然后将HEAD
重新连接到分支名称。在这种情况下,将产生:
K'-L' <-- feature (HEAD)
/
...--G--H--I--J <-- main
\
K--L [abandoned]
如果现在查看带有git log
的提交,您将根本看不到原始的K-L
提交,而将看到新的K'-L'
提交。 L
之前的下一个提交现在为J
,并且功能分支已重新建立到主分支上。
任何合并冲突都会发生,因为“ you”和“ they”在基于合并奇数的合并过程中产生的差异触摸了同一文件的相同或相邻行。当然,“他们”的提交实际上是您正在进行基础定位的您的提交,而在您开始重新设置基准时,“您的”提交通常是其他人最初提交的。最终,“您的”提交是您和他们的提交的混合,这真是令人困惑。
(我想将merge.conflictStyle
设置为diff3
,以便在遇到这些合并冲突时获得更多信息。)
答案 1 :(得分:0)
问题是“重播”的定义。重新设置确实完成了合并的工作:它创建了一个 diff (在这种情况下,从分支b
与master
分开到分支的尖端{ {1}}),并尝试将其应用到b
的末尾。
因此,请调用分支提交origin/master
。
现在,我们知道split
不是origin/master
-因为如果是split
,则首先需要基于master
。因此,自master
起,有一些提交已添加到split
中。
好吧,可能存在与合并中完全相同的冲突。一个人从split
到master
的差异和一个人从split
到b
的差异可能包含无法自动同时完成的事情-例如,以两种不同的方式编辑同一文件的同一区域,或者在一个路径中编辑文件,而在另一路径中删除文件,依此类推。那是个冲突。
请注意,冲突并不意味着有任何不好的事情发生! “冲突”一词的选择非常差。这仅意味着git
不想通过尝试读懂自己的思想来损害事物;它会导致您手动完成合并,因为如果自动选择合并,它可能会执行您不想要的事情。