Git进阶--10、git-merge用法解析-用法大全
git merge
应该是开发者最常用的 git 指令之一,
默认情况下你直接使用 git merge
命令,没有附加任何选项命令的话,那么应该是交给 git 来判断使用哪种 merge 模式,实际上 git 默认执行的指令是 git merge -ff
指令(默认值)
1. Fast-forward
1.1. ff
1.1.1. 触发场景
当dev是master最新commit(HEAD)的孩子,默认触发此模式
1 |
|
1 |
|
1 |
|
1 |
|
git merge dev 会默认执行 fast-forward模式
Fast-forward
是指 Master 合并 Feature 时候发现 Master 当前节点一直和 Feature 的根节点相同,没有发生改变,那么 Master 快速移动头指针到 Feature 的位置,所以 Fast-forward 并不会发生真正的合并,只是通过移动指针造成合并的假象,这也体现 git 设计的巧妙之处。合并后的分支指针上图所示
通常功能分支(feature556) 合并 master 后会被删除,通过下图可以看到,通过 Fast-forward
模式产生的合并可以产生干净并且线性的历史记录:
1.1.2. 优缺点
- 优点:直接修改 HEAD 指针指向,不会创造一个新的 commit 节点,所以合并速度非常快,且产生干净并且线性的历史记录
- 缺点:删除分支或指针向前走会丢失分支信息 (log中体现不出原来的分支操作)
1.2. no-ff
1.2.1. 触发场景
dev非master最新commit(HEAD)的孩子
1 |
|
master分支的git log
1 |
|
dev分支的git log
1 |
|
1 |
|
master 已经没办法通过移动头指针来完成 Fast-forward
,所以在 master 合并 dev 的时候就不得不做出真正的合并,真正的合并会让 git 多做很多工作,具体合并的动作如下:
- 找出 master 和 dev 的公共祖先,节点 B,已经各自的最新提交M2, D3 三个节点的版本 (如果有冲突需要处理,我们这里的示例代码就需要手动合并,然后需要git add,git commit,因此又多出来一个commit历史,这里我备注的是“master merge dev”)
- 创建新的节点 D4,并且将三个版本的差异合并到 D4,并且创建 commit
- 将 master 和 HEAD 指针移动到 D4
此时生产了一个merge commit (D4’),这个merge commit不包含任何代码改动,而包含在dev分支上的几个commit列表(D1, D2和D3)。查看git的提交历史(git log)可以看到所有的这些提交历史记录。
1.2.2. 优缺点
- 优点:保留commit历史
- 缺点:产生一个新的merge commit
1.3. ff-only
只会按照 Fast-forward
模式进行合并,如果不符合条件(并非当前分支的直接后代),则会拒绝合并请求并且退出
1 |
|
1.4. 手动设置合并模式
先简单介绍一下 git merge
的三个合并参数模式:
- –ff 自动合并模式:当合并的分支为当前分支的后代的,那么会自动执行
--ff (Fast-forward)
模式,如果不匹配则执行--no-ff(non-Fast-forward)
合并模式 - –no-ff 非 Fast-forward 模式:在任何情况下都会创建新的 commit 进行多方合并(即使被合并的分支为自己的直接后代)
- –ff-only Fast-forward 模式:只会按照
Fast-forward
模式进行合并,如果不符合条件(并非当前分支的直接后代),则会拒绝合并请求并且退出
1 |
|
1.5. 适用场景
三种 merge 模式没有好坏和优劣之分,只有根据你团队的需求和实际情况选择合适的合并模式才是最优解,那么应该怎么选择呢? 我给出以下推荐:
- 如果你是小型团队,并且追求干净线性 git 历史记录,那么我推荐使用
git merge --ff-only
方式保持主线模式开发是一种不错的选择 - 如果你团队不大不小,并且也不追求线性的 git 历史记录,要体现相对真实的 merge 记录,那么默认的
git --ff
比较合适 - 如果你是大型团队,并且要严格监控每个功能分支的合并情况,那么使用
--no-ff
禁用Fast-forward
是一个不错的选择
2. squash merge
--squash
选项的含义是:本地文件内容与不使用该选项的合并结果相同,但是不提交、不移动HEAD
,因此需要一条额外的commit
命令。其效果相当于将another分支上的多个commit
合并成一个,放在当前分支上,原来的commit
历史则没有拿过来。
https://www.processon.com/view/6325a4f01efad46b0ab39c8d?fromnew=1
2.1. 适用场景
2.1.1. 简化提交历史
在dev分支上执行的是开发工作,有一些很小的提交,或者是纠正前面的错误的提交,对于这类提交对整个工程来说不需要单独显示出来一次提交,不然导致项目的提交历史过于复杂;所以基于这种原因,我们可以把dev上的所有提交都合并成一个提交;然后提交到主干。
判断是否使用
--squash
选项最根本的标准是,待合并分支上的历史是否有意义
在某些情况下,我们应该优先选择使用--squash
选项,如下:
联想发散一下,同一个分支上压缩commit,请见Git进阶--1、Git-后悔药-回退撤销-reset
2.1.2. github squash选项
2.2. 注意事项
2.2.1. 分支必须是直接孩子
1 |
|
需要commit一下
2.2.2. 合并提交归属问题
在这个例子中,我们把D1,D2和D3的改动合并成了一个D。
注意,squash merge并不会替你产生提交,它只是把所有的改动合并,然后放在本地文件,需要你再次手动执行git commit操作;此时又要注意了,因为你要你手动commit,也就是说这个commit是你产生的,不是有原来dev分支上的开发人员产生的,提交者身份发生了变化。也可以这么理解,就是你把dev分支上的所有代码改动一次性porting到master分支上而已。
Git进阶--11、git-rebase用法解析与merge区别由于squash merge会变更提交者作者信息,这是一个很大的问题,后期问题追溯不好处理(当然也可以由分支dev的所有者来执行squash merge操作,以解决部分问题),rebase merge可以保留提交的作者信息,同时可以合并commit历史,完美的解决了上面的问题。
3. 参考
https://static.kancloud.cn/apachecn/git-doc-zh/1945501
https://www.jianshu.com/p/ff1877c5864e
https://www.cnblogs.com/xiao2shiqi/p/14715119.html