【git使用技巧】git merge VS git rebase,你会选择谁?

git rebasegit merge都是Git用来合并分支代码的基本命令。

Git 工作流中,两者作用一致,但是原理上却不相同。

今天我们就来了解下,它们分别有什么作用,适用于什么场景。

写在前面

如果大家不能很好的理解 Git 的原理及机制,可以试试这个非常好用的 Git 在线练习工具 Git Online,可以很直观地看到你所使用的命令会产生什么效果。

git merge

使用 git merge 命令将 master 分支合并到 feature 分支中:

git checkout feature
git merge master

缩写为一行代码就是:

git merge feature master

由上图可知,git merge 会在 feature 分支中新增一个新的 merge commit,然后将两个分支的历史联系在一起。

使用 merge 是很好的方式,因为它是一种非破坏性的操作,对现有分支不会以任何方式被更改。

另一方面,这也意味着 feature 分支每次需要合并上游更改时,它都将产生一个额外的合并提交。

如果 master 提交非常活跃,这可能会严重污染你的 feature 分支历史记录。不过这个问题可以使用高级选项 git log 来缓解。

git rebase

使用git rebase 命令将 master 分支合并到 feature 分支中:

git checkout feature
git rebase master

缩写为一行代码就是:

git rebase feature master

rebase 会将整个 feature 分支移动到 master 分支的顶端,从而有效地整合了所有 master 分支上的提交。

但是,与 merge 提交方式不同,rebase 通过为原始分支中的每个提交创建全新的 commits 来重写项目历史记录,特点是仍然会在 feature 分支上形成线性提交

rebase 的主要好处是可以获得更清晰的项目历史:

  • 首先,它消除了 git merge 所需的不必要的合并提交;
  • 其次,正如你在上图中所看到的,rebase 会产生完美线性的项目历史记录,你可以在 feature 分支上没有任何分叉的情况下一直追寻到项目的初始提交。

git rebase原理再深入:

master 分支代码合并到 feature 上:

这边需要强调一个概念:reapply(重放),使用 rebase 并不是简单的用 ctrl-x/ctrl-v 进行剪切复制一样,rebase 会依次地将你所要操作的分支的所有提交应用到目标分支上。

合并过程如下图:

从上图可以看出,在对特征分支进行 rebase 之后,其等效于创建了新的提交。并且老的提交也没有被销毁,只是简单地不能再被访问或者使用。

实际上在执行rebase的时候,有两个隐含的注意点:

  • 在重放之前的提交的时候,Git会创建新的提交,也就是说即使你重放的提交与之前的一模一样Git也会将之当做新的独立的提交进行处理。

  • git rebase并不会删除老的提交,也就是说你在对某个分支执行了rebase操作之后,老的提交仍然会存放在.git文件夹的objects目录下。

如何选择git merge和git rebase?

通过上面的介绍,我们可以知道:

  • git merge优点是分支代码合并后不破坏原分支的代码提交记录,缺点就是会产生额外的提交记录并进行两条分支的合并。
  • git rebase优点是无须新增提交记录到目标分支,rebase后可以将对象分支的提交历史续上目标分支上,形成线性提交历史记录,进行review的时候更加直观

git merge 如果有多人进行开发并进行分支合并,会形成复杂的合并分支图,如下图:

既然 rebase 如此有用,那么可以使用 rebase 完全取代 merge 吗?

答案:不能!

git rebase的黄金原则:

不能在一个共享的分支上进行Git rebase操作。

总结:

  • 融合代码到公共分支时使用git merge,而不用git rebase

  • 融合代码到个人分支的时候使用git rebase,可以不污染分支的提交记录,形成简洁的线性提交历史记录。

rebase的黄金原则

那么为什么不能在一个共享的分支上进行 Git rebase 操作呢?

所谓共享的分支,即是指那些存在于远端并且允许团队中的其他人进行Pull操作的分支,比如我们Git工作的master分支就是最常见的公共分支。

假设现在Bob和Anna在同一个项目组中工作,项目所属的仓库和分支大概是下图这样:

现在Bob为了图一时方便打破了原则,正巧这时Anna在特征分支上进行了新的提交,此时的结构图大概是这样的:

当Bob推送自己的分支到远端的时候,现在的分支情况如下:

然后呢,当Anna也进行推送的时候,她会得到如下的提醒,Git提醒Anna她本地的版本与远程分支并不一致,需要向远端服务器拉取代码进行同步:

在Anna提交之前,分支中的Commit序列是如下这样的:

A--B--C--D'   origin/feature // GitHub

A--B--D--E    feature        // Anna

在进行Pull操作之后,Git会进行自动地合并操作,结果大概是这样的:

这个第M个提交即代表着合并的提交,也就是Anna本地的分支与Github上的特征分支最终合并的点,现在Anna解决了所有的合并冲突并且可以Push她的代码,在Bob进行Pull之后,每个人的Git Commit结构为:

看到上面这个混乱的流线图,相信你对于Rebase和所谓的黄金准则也有了更形象深入的理解。

假设下还有一哥们Emma,第三个开发人员,在他进行了本地Commit并且Push到远端之后,仓库变为了:

这还只是仅有几个人,一个 feature 分支的项目因为误用 rebase 产生的后果。如果你团队中的每个人都对公共分支进行 rebase 操作,那么后果就是乱成一片。

另外,相信你也注意到,在远端的仓库中存有大量的重复的 Commit 信息,这会大大浪费我们的存储空间。

因此,不能在一个共享的分支上进行 Git rebase 操作,避免出现项目分支代码提交记录错乱和浪费存储空间的现象。

使用rebase合并多次提交记录

rebasemerge不同的作用还有一个就是合并分支多次提交记录。

在分支开发的过程中,我们常常会出现为了调试程序而多次提交代码记录,但是这些记录的共同目的都是为了解决某一个需求,所以,是否可以将这些记录合并起来为一个新的记录会更方便进行代码的review呢?

git rebase提供了合并 commit 的能力。

下面是一个合并的案例过程:

  • 尝试合并分支的最近 4 次提交纪录
git rebase -i HEAD~4
  • 这时候,会自动进入 vi 编辑模式:

进入编辑模式,第一列为操作指令,第二列为commit号,第三列为commit信息。

  • pick:保留该commit;
  • reword:保留该commit但是修改commit信息;
  • edit:保留该commit但是要修改commit内容;
  • squash:将该commit和前一个commit合并;
  • fixup:将该commit和前一个commit合并,并不保留该commit的commit信息;
  • exec:执行shell命令;
  • drop:删除该commit。

按照如上命令来修改你的提交记录:

p 799770a add article
s 72530e4 add article
s 53284b1 add article
s 9f6e388 add article

成功合并了四条记录为一条:

如果保存的时候,你碰到了这个错误:

error: cannot 'squash' without a previous commit

说明你在合并记录的时候顺序错误了,压缩顺序应该是从下往上,而不是从上往下,否则就会触发上面的错误。也就是以新记录为准。

例如上面的例子写成了这样就是出现错误:

s 799770a add article
s 72530e4 add article
s 53284b1 add article
p 9f6e388 add article

中途出现异常退出了 vi窗口,执行下面命令可以返回编辑窗口:

git rebase --edit-todo

继续编辑合并记录的操作,修改完保存一下:

git rebase --continue

最后

我们今天介绍了使用 git 删除分支的一些方法,今后的文章中,也会介绍其他的 git 使用技巧,觉得本文有用的小伙伴,可以帮忙点在“在看”,让更多的朋友看到咱们的文章。

然后,给“前端面试题宝典”的辅导服务打下广告,目前有简历指导模拟面试面试全流程辅导的增值服务,如果有感兴趣的伙伴,可以联系小助手(微信号:interview-fe)了解详情哦~