目录

git mege 知识总结

[TOC]

git merge和git merge –no-ff区别

在很多介绍GItFlow工作流的文章里面,都会推荐在合并分支的时候加上--no-ff参数, 而我们在合并的时候,有时git也会提示 使用了 fast-forward, 这里我将介绍一下merge的三种状态及 git mergegit merge --no-ff 的区别

Git merge的时候,有几种合并方式可以选择

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
--ff
When the merge resolves as a fast-forward, only update the branch pointer, without creating a merge commit. This is the default behavior.

--no-ff
Create a merge commit even when the merge resolves as a fast-forward. This is the default behaviour when merging an annotated (and possibly signed) tag.

--squash
--no-squash
Produce the working tree and index state as if a real merge happened (except for the merge information), but do not actually make a commit, move the HEAD, or record $GIT_DIR/MERGE_HEAD (to cause the next git commit command to create a merge commit). This allows you to create a single commit on top of the current branch whose effect is the same as merging another branch (or more in case of an octopus).

With --no-squash perform the merge and commit the result. This option can be used to override --squash.

而我们平常什么都不加的时候,则使用默认的 –ff , 即 fast-forward 方式

看过官方注释后,我们用一张图来简单描画一下相应的行为

http://img.cana.space/picStore/20211207211516.png

fast-forward

Git 合并两个分支时,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,叫做“快进”(fast-forward)不过这种情况如果删除分支,则会丢失merge分支信息(不知道的还以为是在master上往前推进的)

假设合并前的分支是这样,这个一个非常常见的场景

http://img.cana.space/picStore/20211221101045.png

这是一个很常见的用例,功能开发分支是iss53,在开发新功能,master分支是线上分支,出现了问题,开辟了hotfix分支进行修复,修复完成,进行合并,需要把hotfix合并回master

1
2
3
4
5
6
$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

步骤如下:

  1. 切换回master分支。

  2. 将hotfix分支合并会master分支。 然后看到了Fast-forward 的字样,这个词组的意思就是快进,播放电影的时候,可以注意一下,快进按钮上面就是这个词组。 那么实际变成了什么样呢?

    http://img.cana.space/picStore/20211221101142.png

  3. 仅仅是master指针指向了这个提交C4。这样是一种比较快的合并方式,轻量级,简单。 这个时候,我们往往会删掉hotfix分支,因为它的历史作用已经结束,这个时候,我们的iss53这个功能又向前开发,进行了一次提交,到了C5,那么变成了这样:

    http://img.cana.space/picStore/20211221101214.png

    可以看到这个C4丢失了分支信息,不知道的还以为是在master上直接改的。

    然后,我们要把iss53 这个分支合并回master,就变成了这样:

    http://img.cana.space/picStore/20211221101323.png

    这个时候生成了一个新的commit号,这种提交就不是fast-forward(这个时候也无法生成fast-forward提交,因为要将两个版本的内容进行合并,只有在没有需要合并内容的时候,会有这个fast-forward 方式的提交)。 如果我们对第一次合并,使用了–no-ff参数,那么也会产生这样的结果,生成一个新的提交,实际上等于是对C4 进行一次复制,创建一个新的commit,这就是–no-ff的作用。

–squash

把一些不必要commit进行压缩,比如说,你的feature在开发的时候写的commit很乱,那么我们合并的时候不希望把这些历史commit带过来,于是使用–squash进行合并,此时文件已经同合并后一样了,但不移动HEAD,不提交。需要进行一次额外的commit来“总结”一下,然后完成最终的合并。

–no-ff

关闭fast-forward模式,在提交的时候,会创建一个merge的commit信息,然后合并的和master分支 merge的不同行为,向后看,其实最终都会将代码合并到master分支,而区别仅仅只是分支上的简洁清晰的问题,然后,向前看,也就是我们使用reset 的时候,就会发现,不同的行为就带来了不同的影响

http://img.cana.space/picStore/20211207211614.png

上图是使用 merge --no-ff的时候的效果,此时git reset HEAD^ --hard 的时候,整个分支会回退到 dev2-commit-2

http://img.cana.space/picStore/20211207211715.png

上图是使用 fast-forward 模式的时候,即 git merge ,这时候 git reset HEAD^ --hard,整个分支会回退到 dev1-commit-3

通常我们把 master 作为主分支,上面存放的都是比较稳定的代码,提交频率也很低,而 develop 是用来开发特性的,上面会存在许多零碎的提交,快进式合并会把 develop 的提交历史混入到 master 中,搅乱 master 的提交历史。所以如果你根本不在意提交历史,也不爱管 master 干不干净,那么 –no-ff 其实没什么用。不过,如果某一次 master 出现了问题,你需要回退到上个版本的时候,比如上例,你就会发现退一个版本到了 commint-3,而不是想要的 commit-2,因为 feature 的历史合并进了 master 里。这也就是很多人都会推荐 –no-ff 的原因了吧。

如何让git merge的默认设置为–no-ff –no-commit

公司政策是对合并提交使用 - no-ff 。我个人喜欢调整合并日志消息,所以我使用 - no-commit 。另外我喜欢在我让提交进行之前进行实际编译和测试。

如何制作 - no-ff 和 - no-commit 所有分支的默认值?

在提出这些问题之后的几年中,我几乎总是对提交感到满意,所以默认情况下允许它提交更简单,只要我在修改或以其他方式解决问题之前进行推送就可以了。)

解决方案

把这个放在$ HOME / .gitconfig中:

1
2
3
  [merge] 
 ff = no 
 commit = no 

你可以使用git-config来做到这一点:**

1
2
git config --global merge.commit no
git config --global merge.ff no 

冲突判定

见git冲突判定文章

在自己的分支上可以使用rebase整理提交记录、cherry-pick辅助开发,但是在接受分支(必须使用显示合并)

1
Atlassian 非常喜欢使用显式合并。原因很简单:显式合并提供了很好的可追溯性和要合并的分支的上下文。绝对鼓励在共享功能分支进行审核之前进行本地历史记录清理,但这根本不会更改策略。它增强了它。
1
Atlassian strongly prefers using explicit merges. The reason is very simple: explicit merges provide great traceability and context on the features being merged. A local history clean-up rebase before sharing a feature branch for review is absolutely encouraged, but this does not change the policy at all. It augments it.

.img/image-20211208003619253.png

避免冲突

合并代码规范

版本控制规范

集成代码阶段

git merge 前先git rebase master

1
2
3
4
5
6
7
8
9
git checkout master
git pull
git checkout dev
git rebase master
# 整理一下自己的commit
git rebase -i HEAD~4/commitId
git checkout master
git merge dev
git push

如果公司要求master分支必须保持线性的log,并且每个comit都是一个独立的feature,那么你就需要用git rebase -i命令来整理commit log。

每个独立的feature就是一个commit

不要用git pull,用git fetch和git merge代替它。

master直接执行git pull即可,因为不会改本地master,其他分支如果协同开发建议使用git pull –rebase

git merge/git rebase 是和origin/对应的分支 merge/rebase

1
2
3
4
5
6
rebase好处
想要更好的提交树,使用rebase操作会更好一点。
这样可以线性的看到每一次提交,并且没有增加提交节点。
merge 操作遇到冲突的时候,当前merge不能继续进行下去。手动修改冲突内容后,add 修改,commit 就可以了。
而rebase 操作的话,会中断rebase,同时会提示去解决冲突。
解决冲突后,将修改add后执行git rebase –continue继续操作,或者git rebase –skip忽略冲突。

指导手册

1
2
3
4
5
6
git fetch
git rebase
// pull 冲突使用下面暂存
git stash解决完冲突后,使用git rebase整理commit
// 冲突解决,见上方冲突解决
git stash pop

最重要的是掌握原理,例如git工作空间、暂存区、本地仓库、远程仓库这些概念,以及代码的迁徙流程

冲突判定,合并类型,冲突解决(三路合并后的结果,使用idea三路合并,直接编辑合并后的文件,冲突解决分类,按照时间线枚举场景)

1
2
There are two main ways Git will merge: Fast Forward and Three way
默认先ff,遇到冲突则three way

线性不太适合快速迭代,有时候别人需要搭车改点东西或者在这个分支上干点别的事情,此时一个分支一个commit旧不适合了,这个分支就该有多个commit,如果能做到完全线性那也不需要no-ff了,可以做个平衡,rebase+no-ff

强制

  • 显示合并,使用no-ff,
    • 可以全局配置,好处是可以追踪commit是从哪一个feature带过来的
  • 不允许使用no-commit,保留原有merge信息

建议

  • feature分支使用rebase和rebase -i整理commit

  • 使用git fetch/git merge 替换 git pull,当然git fetch/git rebase替换git pull –rebase更好,

    .img/image-20211208005205318.png

git pull, git merge 合并类型都是ff,本地仓库改变只有git push和git fetch

git pull和git merge 区别

a. git pull这样就直接把你本地仓库中的代码进行更新但问题是可能会有冲突(conflicts),个人不推荐。 b. 先git fetch origin(把远程仓库中origin最新代码取回),再git merge origin/master(把本地代码和已取得的远程仓库最新代码合并),如果你的改动和远程仓库中最新代码有冲突,会提示,再去一个一个解决冲突,最后再从1开始。 c. 如果没有冲突,git push origin master,把你的改动推送到远程仓库中。

参考