关于Git分支管理的思考?merge还是rebase?

620 阅读5分钟

关注可以查看更多粉丝专享blog~

引发思考

最近这段时间入职新公司,处在熟悉阶段,所以有些时间没有写博客了。其实每次进入一个新的环境的时候肯定会有很多开发工具和开发方式和自己之前使用的方式不一样。没有对错,只有是否合适。最近遇到了一个Git分支管理的问题。由于都是使用的多人协同开发已经成熟的敏捷流程,主干分支是一样的,一共四个,包含开发、测试、预发布和发布分支。不一样的点在于对于master分支的commit记录,一个是采用合并(merge),一个是采用变基(rebase),其实最终的代码是一样的。但是这一点让我产生了浓厚的兴趣,到底是该rebase,还是应该merge,为此专门取读了一遍《精通Git》。

什么是merge(合并)?

基本操作,简单介绍一下merge。 精通 Git 如图是我们在开发过程中遇到的最基本情况,我们基于C2节点的master checkout出了experiment分支。此时有同时提交了自己的代码C3,则master分支超前experiment 一个commit,然后我们在experiment上开发了C4,此时我们想提交C4到master分支,我们可以使用merge操作。

// 当前状态
$ git l
* 19661bd (HEAD -> experiment) C4 - Davids, 4 seconds ago
| * fa0f0a2 (master) C3 - Davids, 21 seconds ago
|/  
* acdf94c C2 - Davids, 2 minutes ago
* 2b55d4e C1 - Davids, 2 minutes ago
// 切换到master分支,合并experiment代码
$ git checkout master
$ git merge experiment

执行merge命令回产生一条新的commit,自动进入vim界面,默认描述如下,可以修改commit信息

Merge branch 'experiment'
  
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
// merge后状态,产生了新的Merge branch 'experiment'的commit,我们命名为C5
$ git l
*   177d4c3 (HEAD -> master) Merge branch 'experiment' - Davids, 4 minutes ago
|\  
| * 19661bd (experiment) C4 - Davids, 9 minutes ago
* | fa0f0a2 C3 - Davids, 9 minutes ago
|/  
* acdf94c C2 - Davids, 10 minutes ago
* 2b55d4e C1 - Davids, 10 minutes ago

$ git log --oneline
177d4c3 (HEAD -> master) Merge branch 'experiment'
19661bd (experiment) C4
fa0f0a2 C3
acdf94c C2
2b55d4e C1

在这里插入图片描述

什么是rebase(变基)?

是不是rebase用多了就会变基?要不我就merge,挺好的!下面简单介绍一下什么是rebase。当前状态与merge前状态一致。(如图,为了保证阅读的连贯性,把上面的图拉下来,免得翻一遍。) 精通 Git

你可以提取在 C4 中引入的补丁和修改,然后在 C3 的基础上再应用一次。在 Git 中,这种 操作就叫做 变基。你可以使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新 播放”一样。

// 当前状态
$ git l
* e458f64 (HEAD -> experiment) C4 - Davids, 4 seconds ago
| * 77115ac (master) C3 - Davids, 21 seconds ago
|/  
* 360ea4b C2 - Davids, 2 minutes ago
* bfc4923 C1 - Davids, 2 minutes ago
// 开始rebase
// 首先切到基底分支更新代码,目的是保证本地基底分支master上面有我们想要的代码,如果不想要最新的,或者就想要当前本地master分支的内容可以不用操作
$ git checkout master

$ git pull origin master

// 切回当前分支
$ git checkout experiment

// 开始rebase
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: C4

$ git status
On branch experiment
nothing to commit, working tree clean

$ git log --oneline
8fe28d4 (HEAD -> experiment, master) C4
77115ac C3
360ea4b C2
bfc4923 C1

// 当前experiment状态(如图所示)
$ git l
* 8fe28d4 (HEAD -> experiment, master) C4 - Davids, 17 minutes ago
* 77115ac C3 - Davids, 17 minutes ago
* 360ea4b C2 - Davids, 19 minutes ago
* bfc4923 C1 - Davids, 19 minutes ago

它的原理是首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master)的最近共同祖 先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目 标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。

rebase之后

$ git checkout master
Switched to branch 'master'

$ git merge experiment
Updating 77115ac..8fe28d4
Fast-forward
 test1.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 test1.txt

$ git status
On branch master
nothing to commit, working tree clean

$ git log --oneline
8fe28d4 (HEAD -> master, experiment) C4
77115ac C3
360ea4b C2
bfc4923 C1

$ git l
* 8fe28d4 (HEAD -> master, experiment) C4 - Davids, 19 minutes ago
* 77115ac C3 - Davids, 19 minutes ago
* 360ea4b C2 - Davids, 21 minutes ago
* bfc4923 C1 - Davids, 21 minutes ago

在这里插入图片描述 此时,C4' 指向的快照就和上面使用 merge 命令的例子中 C5 指向的快照一模一样了。这两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁。你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的,但它们看上去就像是先后串行的一样,提交历史是一条直线没有分叉。

一般我们这样做的目的是为了确保在向远程分支推送时能保持提交历史的整洁——例如向某个别人维护的项目贡献代码时。在这种情况下,你首先在自己的分支里进行开发,当开发完成时你需要先将你的代码变基origin/master 上,然后再向主项目提交修改。这样的话,该项目的维护者就不再需要进行整合工作,只需要快进合并便可。

请注意,无论是通过变基,还是通过三方合并,整合的最终结果所指向的快照始终是一样的,只不过提交历史不同罢了。变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。

总结

至此,已经简单介绍了merge和rebase,到底哪种方式更好。在回答这个问题之前,让我们退后一步,想讨论一下提交历史到底意味着什么。

有一种观点认为,仓库的提交历史即是 记录实际发生过什么。它是针对历史的文档,本身就有价值,不能乱改。从这个角度看来,改变提交历史是一种亵渎,你使用_谎言_掩盖了实际发生过的事情。如果由合并产生的 提交历史是一团糟怎么办?既然事实就是如此,那么这些痕迹就应该被保留下来,让后人能够查阅。

另一种观点则正好相反,他们认为提交历史是 项目过程中发生的故事。没人会出版一本书的第一批草稿,软件维护手册也是需要反复修订才能方便使用。持这一观点的人会使用 rebase 及 filter-branch 等工具来编写故事,怎么方便后来的读者就怎么写。

现在,让我们回到之前的问题上来,到底合并还是变基好?希望你能明白,并没有一个简单的答案。Git 是一个 非常强大的工具,它允许你对提交历史做许多事情,但每个团队、每个项目对此的需求并不相同。既然你已经分 别学习了两者的用法,相信你能够根据实际情况作出明智的选择。

总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史,从不对已推送至别处的提交执行变 基操作,这样,你才能享受到两种方式带来的便利。