Git中的merge命令实现和工作方式

时间:2022-03-18 01:39:41

想象一下有例如以下情形:代码库中存在两个分支,而且每一个分支都进行了改动。最后你想要将当中的一个分支合并到其它的分支中。个人博客网址 http://swinghu.github.com/

那么要问合并的处理过程是怎么样的呢?Git是对每一个分支,根据分支的历史数据依照序列化操作,还是它仅仅是合并每一个分支里文件的最后版本号?这是一个问题,我想对gitmerge操作有必要进行分析一下。

回顾一下。我们知道Git的版本号库内部结构是以有向无环图(directed
acyclic graph
)组织起来的:每一次commit都会生成一个版本号树的快照(snapshot),而且该快照保存了一个指向其父节点(该分支的近期上一次的提交快照)的引用(通常当前提交仅仅有一个父节点,可是初试提交快照没有父节点,而一次合并(merge)操作有2个或多个父节点)。

就像这样。每次提交都递归的建立某些节点集指向父节点的引用。

有时候,当我们考虑提交的父节点提交树和当前节提交节点树做差异比較时(diff),将一次提交想象成一次修补补丁(patch)是有助于我们理解git 的工作机理。依照这样的方式,我们能够这样觉得,提交树就是集成应用了全部父节点的补丁修补。一颗在两个分支上做merge操作的树,因此就能够觉得是两个分支应用了其各自全部的父节点修补补丁程序,然后做一次联合操作Union。

可是那不是git merge 的真正运行方式。原因是。首先。假设以那样工作的话。运行会很的慢!,而且在运行过程中它要再一次又一次处理全部的之前合并时造成的冲突。如此,git
merge
 真正是怎样操作的呢?

我喜欢用数学的思维方式思考:给定两个提交 A和 B,合并提交(commit)操作 A∨B 就能够描写叙述为: [A∨B ]=[ A]+[ B]−[ C ] 这里的 C是A 和 B的合并共同拥有项(近期提交树祖先共同含有的部分)。我们必需要“减去” C。由于假设不这种话,我们就会有两个A∧B。

这个操作x+y−z 被叫做三向合并。

你能够觉得运行路径为将x−z 应用到x 上,或者将 x−z应用到y 上。

其实diffpatch操作并没有字面上依照上面的的操作行事,相反而是使用了:最长公共子序列算法来实现。x−w和序列x,序列w的差异就是我们知道的在求最长公共子序列时的赋值(中间可能要去除到两个序列的公共部分)。为了构造三向合并x+y−w,我们对x和w在求公共子序列的时候进行赋值。对y和w在求公共子序列的时候赋值。然后输出每一个要么:

  • 三个序列的共同拥有部分,或者
  • 在x 中出现。可是在y 和 w 中不存在的部分,或者
  • 在y 中出现,可是在x 和w 中没有出现的部分

同一时候我们要删除那些序列,要么:

  • 出如今y 和 w可是在x中没有出现,或者
  • 出如今x和w中可是在y中没有出现。

举个栗子,下面是x,y,z 运行merge操作后的结果:

  1. x: w: y: ↦ merged:
  2. milk milk milk milk
  3. juice juice
  4. flour flour flour flour
  5. sausage sausagegit
  6. eggs eggs eggs eggs
  7. butter butter

在x,y与w的行序可能只说明了一种在三向合并的输出行上的一种偏序关系。假设是这种话,因为相同的块w,在x,y 之间以不同的方式被编辑-因此我们说那就是一个合并冲突。将会输出该信息,让用户手动解决。

git 向你显示合并冲突的时候,默认情况下,你将会看到x和的冲突块:

Git中的merge命令实现和工作方式然而,冲突块会变得更easy解决,当你可以看到合并基准w的时候。

我建议打开开关:

  1. ~/.gitconfig

通过设置merge.conflictstyle 为diff3,则

  1. git config --global merge.conflictstyle diff3

Git中的merge命令实现和工作方式如今你能够看到解决方案为:

  1. I had two eggs and three sausages for breakfast.

(注意,这个操作会对称性的(关于w和结果进行交换,因此你真正须要的是查看w)这里有另外两种其它的案例须要考虑,可能行为:

  • 出如今x和y中,可是在w中没有出现
  • 出如今w中。可是没有在x 和y 中出现

某些三向合并算法常常将这样的行标记为冲突行。

然而Git,将会优雅的输出或者直接删除该行,依次,假定该行没有改变。

这样的效果叫做意外清理合并。偶尔某些情形在实际应用中非常实用,尤其是用户把版本号搞砸了,各自合并同一个补丁的两个不同的版本号。

可是我觉得掩盖这样的错误不是一种好的行事方式,我希望这样的行为能够并关闭。尽量避免由于他所能带来的这样的长处而使用它吧。

假设你细致。非常有观察力,你可能已经发现我在上述说明中存在的一个漏洞了:因为commit提交 A和B可能各自又包括commit,他们近期的共同祖先可能不是唯一的!

一般。他们最有可能的情形是,近期的共同祖先是 C1,C2,C3,C4,⋯Ck−1,Ck ,在这样的情况下,git
merge
 操作将会递归的运行:它首先构造合并 C=C1∨C2∨C3⋯Ck−1∨Ck 。并以此作为三向合并[ A ]+[ B ]−[ C ] 的基础(base)。

这就是为什么Git的默认合并策略并称为递归的。
假定两个分支例如以下图所看到的。A,B,C,D,E是master分支的历史快照(snapshot);A,B,X,Y,Z是feature分子的历史快照。Git中的merge命令实现和工作方式命令

  1. git merge feature

首先查找“master”(当前分支)和“feature”的共同祖先。它或多或少的等价于下面命令:

  1. git merge-base master feature

在我们的举的样例里,他们的共同祖先是B。

假设在C,D,E和X,Y,Z提交中没有冲突,git 将会创建一次“merge
commit 
” merge commit会有两到多个父亲。 新的图将会是以下这个样子。Git中的merge命令实现和工作方式每一次git
commit
 提交都会生成一棵树,一到多个“父亲节点”。作者的名字,email,日期和提交者的姓名。email,日期。

merge提交和普通的提交的唯一差别就是祖先的数量。

在第二幅图中,merge commit提交被以M标注出来了。
假设提交存在冲突,用户就会被要求解决冲突,并手动创建合并提交,在冲突解决后

  1. git commit -a

将会创建合并提交。这条命令没什么特殊的语法。Git 已经知道了用户已经在进行合并了(已经在尝试合并)。