git merge 和 git rebase 以及 git merge --ff, --no-ff, --ff-only, --squash 等选项的区别
git merge 和 git rebase 的区别
首先看到有下面两条分支,分支 topic 从 master 的 E commit 分出去。
A---B---C topic
/
D---E---F---G master
接下来让我们看看,git merge 以及 git rebase 的区别
git merge
git merge topic 将会在 E commit 点之后按时间重演(replay)各个改变(A,B,C),然后还会生成一个包含两个分支最后一个 commit 的名称以及用来描述合并的 log 信息(用户可改写)的 commit。
A---B---C topic
/ \
D---E---F---G---H master
我们可以看到,git merge 会保留历史的拓扑结构,可以保留分支是从哪个 commit 分出去,提交了哪些 commit,在什么地方合并回来的所有信息。可以更容易的追溯,当然缺点也比较明显,会使得 master 的历史记录看起来比较乱。
git rebase
假设我们正在 topic 分支之上,然后执行git rebase master。它会在 master 分支的基础之后重演(replay)一遍 topic 分支的变化。
A'--B'--C' topic
/
D---E---F---G master
然后再切换到 master,执行git merge topic。这样就可以以 rebase 的方式合并 topic 分支的代码。
我们可以看到,git rebase 会有一个比较干净的时间线,但是会破坏 master 分支历史的时间拓扑结构,导致历史在时间线上看会让人觉得奇怪。比如下面这个:
这是 rebase 之前的记录结构, D 是 B 之后,E 之前提交的。
D test
/
A---B---E master
然后在 test 分支上git rebase master,就会出现上面截图的历史结构。D 会在 master 的 E 之后重演,且保留 commit 时间,出现后一个记录的时间早于前一个记录的时间的情况,这样看起来就很奇怪。而且也不知道是什么时候分出去的分支,又做了什么修改,又是在什么时候合并回来。
**所以个人觉得,在项目中如果想保持一个清晰的历史,不想去频繁改写历史,还是应该使用 git merge 为主,减少使用 rebase。**我觉得可能需要使用 rebase 的情况是,当你的功能分支依赖于 master 分支后面合并的功能时,可以使用 rebase(但我也不太确定,有什么想法可以一起在评论里讨论一下~ 到底什么情况下使用 rebase 比较好)。
另外,为了能进一步够保证一个清晰的合并历史,也可以在合并的时候使用--no-ff选项,来避免 fast-forward 合并。接下来让我们了解一下什么是 fast-forward 合并以及其他相关的选项。
fast-forward merge 以及相关选项
什么是 ff(fast-forward) merge
当要合并进来的分支是当前分支的后代(when the merged-in history is a descendant of the current history),这种情况下就符合 fast-forward merge 的要求。
在这种情况下,如果选择了 fast-forward merge,只会更新当前分支的指示器(pointer)到要合并分支的最后一个 commit 上,并不会创建一个 merge commit。
git merge --ff
当带上了--ff,且合并满足上述的 fast-forward merge 条件时,就会优先采用 fast-forward 合并;如果不满足,那就是普通 merge,会创建一个 merge commit。
git merge --no-ff
在所有情况下都会创建一个 merge commit,就算满足 fast-forward merge 的条件也会创建。
git merge --ff-only
只在满足 fast-forward merge 条件的情况下合并,如果不满足,则拒绝合并且用一个非零的状态退出(exit with a non-zero status)。