git auto-merge原理
1、merge 常见误区
1、git merge 是用时间先后决定merge结果的,后面会覆盖前面的?
答 :git 是分布式的文件版本控制系统,在分布式环境中时间是不可靠的,git是靠三路合并算法进行合并的。
2、git merge 只要两行不相同就一定会报冲突,叫人工解决?
答:git 尽管两行内容不一样,git 会进行取舍,当git无法进行取舍的时候才会进行人工解决冲突
2、merge对比单位:行
git 合并文件是以行为单位进行一行一行进行合并的,但是有些时候并不是两行内容不一样git就会报冲突,因为smart git 会帮我们自动帮我们进行取舍,分析出那个结果才是我们所期望的,如果smart git 都无法进行取舍时候才会报冲突,这个时候才需要我们进行人工干预。
3、git二路合并和三路合并处理冲突原理
4、git auto-merge的不同模式
1、Fast-Forward Merge模式
当前工作分支到合并目标分支之间的提交历史是线性路径时,可以进行快进合并。在这种情况下,不需要真实的合并两个分支,Git只需要把当前分支的顶端指针移动到目标分支的顶端就可以了(也就是快进的意思)。在这种情况下快进合并成功的将提交历史合并至一处,毕竟目标分支中的提交此时都包含在当前分支的提交历史中了。
判断依据:
在找2个修改集合X,Y 公共祖先的时候,会发现公共祖先就是他们中的一个,
要求:
要求合并的两个分支(或提交)必须是祖孙/父子关系
场景:dev是从master的c2 commit修改拉出来的,并做了dev分支上的c3,c4 commit
将dev合入master的时候,master没有变化,此时可以直接将master指向dev就好了
分析:
由于master分支从c2开始与dev分叉以后就再也没有新的提交了,所以Git只是简单地把master的head指针向前移动到c4,合并就完成了。这就是所谓的Fast-Forward Merge。因为不涉及内容变更的比较,所以这种合并方式效率很高。Fast-Forward Merge要求参与合并的两个分支上的提交必须是“一脉相承”的父子或祖孙关系。不过它有个缺点,作为被合并的dev分支,它的提交历史在合并以后会和master分支的提交历史重合。
如果我们想在合并后保留来自被合并分支的提交历史,并显式标注出合并发生的位置,那就需要在执行合并时加上参数–no-ff。当然,这样也表示我们在合并时将不使用Fast-Forward Merge:
git merge --no-ff -m c5 dev
2、Three-Way Merge模式
如果在合并时使用–no-ff参数,Git就会采用Three-Way Merge(三方合并)对两个分支进行合并。
场景:dev从master的c2 commit拉出来的,并做了c3,c4 commit,此时master也做了c5 commit,要将dev合并进来
这个时候,Git会自动采用Three-Way Merge方式进行合并。首先,它会在两个分支上分别找到head指针(又被称为branch tip)所对应的提交:c4和c5。然后,找到距离它们俩最近的“共同祖先”:c2(也就是前面所说的“原件”,又被称为common ancestor),然后进行Three-Way Merge。
3、Squash Merge模式
Squash Merge:
指Git在做两个分支间的合并时,会把被合并分支(通常被称为topic分支)上的所有变更“压缩(squash)”成一个提交,追加到当前分支的后面,作为“合并提交”(merge commit)。从参与合并的文件变更上来说,Squash Merge和普通Merge并没有任何区别,效果完全一样。唯一的区别体现在提交历史上:正如我们前面提到的,对于普通的Merge而言,在当前分支上的合并提交通常会有两个parent;而Squash Merge却只有一个。
使用场景:
如果在被合并分支上,完整的提交历史里包含了很多中间提交(intermediate commit),比如:改正一个小小的拼写错误可能也会成为一个独立的提交,而我们并不希望在合并时把这些细节都反应在当前分支的提交历史里。这时,我们就可以选择Squash Merge。
4、Ours模式
Ours策略:
无论有没有冲突发生,都会毫不犹豫的丢弃来自被合并分支的修改,完整保留当前分支上的修改。所以,对于Ours策略而言,实质上根本就没有做任何真正意义上的合并,或者说做的是假合并(fake merge)。不过,从提交历史上看,Git依然会创建一个新的合并提交(merge commit),并让它的parent分别指向参与合并的两个分支上的提交记录。
exmaple:
git merge -s ours -m feature1 feature2
没有Theirs策略!
事实上,Git在以前的版本里是有Theirs策略的,但后来它被去掉了。其实道理也很简单,因为它太危险了。正如Ours策略会毫不犹豫地丢弃被合并分支上的修改,Theirs策略也会毫不犹豫的丢弃当前分支上的修改。这相当于自己之前在当前分支上所做的工作全部丢掉了!
5、Octopus模式
Octopus策略:
假如我们要合并的分支超过两个,那该怎么办呢?这个时候,我们依然可以使用Recursive策略,对分支进行两两合并。但是,这样做每合并一次就会产生一个新的合并提交(merge commit)。过多的合并提交出现在提交历史里,会成为一种“杂音”,对提交历史造成不必要的“污染”,让它变得更加复杂,更难看懂。
这个时候,Octopus策略就派上用场了。Git在对两个以上的分支进行合并时,会自动选择Octopus策略。它的主要特点在于,只会生成一个合并提交,从而最大限度地减少了因为合并对提交历史造成的“污染”。因为按照这种合并策略得到的提交历史形似章鱼,所以名字还是起的很形象的
example:
git merge -m feature1 feature2 feature3
如果要是采用Recursive策略,git要在master分支上逐个合并feature1和feature2,其中,c4是在合并feature1时产生的,c5是在合并feature2时产生:
6、Recursive模式
Recursive模式:
Git在对两个分支进行合并时所采用的默认策略,它只适用于两个分支之间的合并。因此,对于超过两个分支的合并,需要反复地进行两两合并,才能最终完成所有分支的合并(这也是Recursive名字的由来)。本质上,Recursive就是一种Three-Way Merge。它的特点在于,如果Git在寻找共同祖先时,在参与合并的两个分支上找到了不只一个满足条件的共同祖先,它会先对共同祖先进行合并,建立临时快照。然后,把临时产生的“虚拟祖先”作为合并依据,再对分支进行合并。
如下图所示:
在对两个分支上的提交A和B进行合并时,我们发现了它们有两个共同祖先,分别是:ancestor0和ancestor1。这个时候,Recursive策略会对ancestor0和ancestor1进行合并,临时创建一个虚拟祖先:ancestor2。用ancestor2与A,B一起进行Three-Way Merge。
为啥叫递归模式:
如果在找ancestor0与ancestor1的公共祖先的时候,发现这两个分支的公共祖先也不止一个,又要找他们公共祖先的祖先,依次递归,直到找到公共祖先
Recursive模式中的Ours和Theirs参数:
在处理合并时,和其他某些Merge策略一样,Recursive策略通常会尽量自动完成合并。如果在合并过程中发现冲突,Git会在被合并的文件里插入冲突标记(merge conflict markers),并标记当前文件存在冲突,然后交由人工来处理。
不过,我们也可以通过指定参数告诉Git,当发生冲突时自动选择或丢弃其中一个分支上的修改。比如,假设我们要把分支B合并到分支A。如果指定参数-Xours,则表明丢弃分支B上的修改,保留当前分支A上的内容;指定参数-Xtheirs则刚好相反。
这里要注意的是,这两个参数只在发生冲突时起作用。而正常情况下,即没有发生冲突时,Git还是会帮我们自动完成合并的
Recursive模式中的Ours参数与上述Ours模式的差别:
如果在使用Recursive策略时指定-Xours参数,那么当发生冲突时,Git会选择丢弃来自被合并分支的修改,而保留被当前分支上的原有修改。这种情况只在有冲突时才会发生,如果没有冲突,Git还是会帮我们自动完成合并的。
与之不同的是,Ours策略无论有没有冲突发生,都会毫不犹豫的丢弃来自被合并分支的修改,完整保留当前分支上的修改。所以,对于Ours策略而言,实质上根本就没有做任何真正意义上的合并,或者说做的是假合并(fake merge)。不过,从提交历史上看,Git依然会创建一个新的合并提交(merge commit),并让它的parent分别指向参与合并的两个分支上的提交记录。