想象一下有例如以下情形:代码库中存在两个分支,而且每一个分支都进行了改动。最后你想要将当中的一个分支合并到其它的分支中。个人博客网址 http://swinghu.github.com/
那么要问合并的处理过程是怎么样的呢?Git
是对每一个分支,根据分支的历史数据依照序列化操作,还是它仅仅是合并每一个分支里文件的最后版本号?这是一个问题,我想对git
的merge
操作有必要进行分析一下。
回顾一下。我们知道Git
的版本号库内部结构是以有向无环图(directed
acyclic graph
)组织起来的:每一次commit
都会生成一个版本号树的快照(snapshot
),而且该快照保存了一个指向其父节点(该分支的近期上一次的提交快照)的引用(通常当前提交仅仅有一个父节点,可是初试提交快照没有父节点,而一次合并(merge
)操作有2个或多个父节点)。
就像这样。每次提交都递归的建立某些节点集指向父节点的引用。
有时候,当我们考虑提交的父节点提交树和当前节提交节点树做差异比較时(diff
),将一次提交想象成一次修补补丁(patch
)是有助于我们理解git
的工作机理。依照这样的方式,我们能够这样觉得,提交树就是集成应用了全部父节点的补丁修补。一颗在两个分支上做merge操作的树,因此就能够觉得是两个分支应用了其各自全部的父节点修补补丁程序,然后做一次联合操作Union。
可是那不是git merge
的真正运行方式。原因是。首先。假设以那样工作的话。运行会很的慢!,而且在运行过程中它要再一次又一次处理全部的之前合并时造成的冲突。如此,git
merge
真正是怎样操作的呢?
我喜欢用数学的思维方式思考:给定两个提交 commit
)操作
这个操作
你能够觉得运行路径为将
其实diff
和patch
操作并没有字面上依照上面的的操作行事,相反而是使用了:最长公共子序列算法来实现。
- 三个序列的共同拥有部分,或者
-
在
x 中出现。可是在y 和w 中不存在的部分,或者 -
在
y 中出现,可是在x 和w 中没有出现的部分
同一时候我们要删除那些序列,要么:
-
出如今
y 和w 可是在x 中没有出现,或者 -
出如今
x 和w 中可是在y 中没有出现。
举个栗子,下面是
x: w: y: ↦ merged:
milk milk milk milk
juice juice
flour flour flour flour
sausage sausagegit
eggs eggs eggs eggs
butter butter
在
当git
向你显示合并冲突的时候,默认情况下,你将会看到x和的冲突块:
然而,冲突块会变得更easy解决,当你可以看到合并基准w的时候。
我建议打开开关:
~/.gitconfig
通过设置merge.conflictstyle
为diff3
,则
git config --global merge.conflictstyle diff3
如今你能够看到解决方案为:
I had two eggs and three sausages for breakfast.
(注意,这个操作会对称性的(关于w和结果进行交换,因此你真正须要的是查看w)这里有另外两种其它的案例须要考虑,可能行为:
-
出如今
x 和y 中,可是在w 中没有出现 -
出如今
w 中。可是没有在x 和y 中出现
某些三向合并算法常常将这样的行标记为冲突行。
然而Git
,将会优雅的输出或者直接删除该行,依次,假定该行没有改变。
这样的效果叫做意外清理合并。偶尔某些情形在实际应用中非常实用,尤其是用户把版本号搞砸了,各自合并同一个补丁的两个不同的版本号。
可是我觉得掩盖这样的错误不是一种好的行事方式,我希望这样的行为能够并关闭。尽量避免由于他所能带来的这样的长处而使用它吧。
假设你细致。非常有观察力,你可能已经发现我在上述说明中存在的一个漏洞了:因为commit
提交 commit
,他们近期的共同祖先可能不是唯一的!
一般。他们最有可能的情形是,近期的共同祖先是 git
merge
操作将会递归的运行:它首先构造合并 base
)。
这就是为什么Git
的默认合并策略并称为递归的。
假定两个分支例如以下图所看到的。master
分支的历史快照(snapshot
);feature
分子的历史快照。命令
git merge feature
首先查找“master
”(当前分支)和“feature
”的共同祖先。它或多或少的等价于下面命令:
git merge-base master feature
在我们的举的样例里,他们的共同祖先是B。
假设在git
将会创建一次“merge
commit
” merge commit
会有两到多个父亲。 新的图将会是以下这个样子。每一次
git
commit
提交都会生成一棵树,一到多个“父亲节点”。作者的名字,email
,日期和提交者的姓名。email
,日期。
merge
提交和普通的提交的唯一差别就是祖先的数量。
在第二幅图中,merge commit
提交被以
git commit -a
将会创建合并提交。这条命令没什么特殊的语法。Git
已经知道了用户已经在进行合并了(已经在尝试合并)。