面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)

有梦想,有干货,微信搜索 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。
【面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)】Hi!大家好,我是小智 !用 Git 做版本控管应该是大部分工程师每天都会碰到的工作流程之一,但我在使用上不外乎就是 pushpullmergecheckoutlog 等几个指令,更深入一点就一问三不知了 ,在面试时被问到了这个问题:「你知道 Git 的 merge 和 rebase 有什麽不同吗?」
听完后我直接困惑,对我来说 rebase 就是用来整理 commit 的工具,居然还可以和 merge 做比较?
git-rebase
先来说说平常我会用 rebase 这个指令来干嘛,假如我新增了一个单元测试,然后 commit,这时候 log 就会多一条 commit 的纪录:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

但是在 commit 完才发现,我少写了另一个测试案例,因此在补上之后,我又 commit 了一次:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

这时记录中会多出另外一条 commit,不过对我来说,这两个 commit 在做的其实是同一件事,于是我在 push 到 remote 之前,就会想要先整理一下 commit,把这两条记录合并起来。
要把这两条记录合并起来有两个方法,第一个是 reset 到添加第一个测试案例之前,然后直接做一次 commit。第二个方法就是用 rebase 来处理!
首先让我们看看目前的 log:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

我的目的是把 9dc67ff87af945 整理成一个,所以要调整的 commit 是从 init, 也就是 commit id为 7eb57cb 之后的所有 commit,搭配上 rebase 指令的话就是:
git rebase -i 7eb57cb

输入完后就会跳到 vim 的编辑画面:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

画面上会看到 7eb57cb 后的所有 commit(目前就只有 9dc67ff87af945 ),接着把 9dc67ffpick 改成 squash,表示把它与前一个 commit 做合并。先点一下 i 后开始用 vim 编辑内容:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

编辑完后,可以点 esc 再输入 :wq 做保存,如果只是好奇进来玩看看,不想保存的话就输入 :q!。结束上面的流程后,再查看一次 log,会发现两条 commit 变成一笔了。保存完会跳到 commit message 的画面,这边可以让你输入合并后的 commit message,但我就不改了,一样直接保存:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

结束上方的流程后,再查看一次 log,会发现两笔 commit 变成一笔了:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

先 nice,上述的操作为 rebase 的 interactive mode,在 git rebase 后输入的 -i 其实就是 interactive 的缩写。
git-merge
大家应该对 merge 指令都非常熟悉,因为在做新功能的时候,通常都会拉一个分支出去,完成后再 merge 回 master 或 develop 等主要分支。操作流程如下:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

在 merge 的时候会有两种情况,第一种是 fast-forward,会把被合并分支的 HEAD 的 reference 移到要合併分支内最新的 commit 上,上方操作的 merge 结果就是 fast-forward,master 的 HEAD 被移到 string-library 的最新 commit,画成图的话就是这样子:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

但是如果在执行 merge 的时候产生冲突,那分支的合并行为就会和 fast-forward 有点不同了。举例来说,我分别在 master 和 string-library 的同一个文件添加内容,那当我执行 merge 的时候就会要求先修复冲突:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

修复完后,再执行 commit 完成合并,而这一次合并时,会再多一个 commit 是有关 merge 了 string-library 分支的纪录:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

这个情况画成图就会像这样子:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

git-rebase 与 git-merge 的差异
看完上方对 rebasemerge 的介绍后,你也许会想说:
「咦?那这两个不是完全不同的东西吗?」
对的,原本我也是这麽认为,一直到我去看了 git-rebase 的文档,才发现原来我一直误会它了。在 git book 的 rebase 篇章,第一段就说明了,在 Git 里有两种方法可以用来整合两个分支,而这两个在上方都有提到,分别为 mergerebase
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

从上方的 merge 例子已经知道了,merge 在合并的时候会有 fast-forward,和冲突时用一个 commit 记录合并变更的两种情形。而 rebase 的整合方式非常有趣,依照关于 rebase 的另一段说明,它可以「把某个分支中所有 commit 的过程,以另一个分支的 commit 为基础重播一遍」:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

这是什麽意思呢?首先让我们回到上述的例子,并在 master 分支上用 reset,让 master 的版本回到合并 string-library 之前:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

现在我们要用 rebase 指令,将 string-library 所有的 commit 修改,以 master 的 commit 为基础跑一次。使用 rebase 合并的第一步,要先切到想重播 commit 的分支:
git checkout string-library

然后再输入 git rebase 指令,并于后方指定要在哪个分支上重播:
git rebase master

执行结果:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

在 rebase 重播 commit 的过程中,和 merge 相似的地方在于,如果有冲突的话还是需要解决,但在解决后,并不是使用 commit 指令进行合并,而是要输入 git rebase --continue,让 rebase 可以继续重播接下来的 commit:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

重播完成时,会显示目前重播到哪个 commit,以 string-library 来说就是最新的add string unit test D。这时候的分支关系,画成图就会变成:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

上图在经过 rebase 之后,string-library 里 07e38fb 修改,会以 master 的 commit 为基底再重播一次。
需要注意的是,重播后的 commit id 会和原本的不一样,这等于完全改写了分支内所有的 commit 历史纪录。
另外,执行完 rebase 后,string-library 其实还没有被合并回 master 分支上,因此还是要再切回 master 执行 merge,以完成合併:
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

因为已经先用 rebase 在重播时处理完 commit 的冲突了,所以现在 merge 就会直接走 fast-forward 合并,也不会另外多一个 merge 的 commit 纪录。
使用 git-rebase 合併的优缺点
优点
  1. 不会在合併时产生多馀的 commit。
  2. 可以在重播的时候以 commit 为单位处理冲突。
  3. 合併时会依分支的 commit 排列,能够比较清楚的 review issue 或 feature 处理的过程。如果使用 merge,在合併后就会依照时间顺序穿插排列两个分支的 commit。
  4. 在贡献开源项目的时候,如果在 push 前先做 rebase,那作者就能够直接以 fast-forward 的方式合并,不需要再另外解冲突。
缺点
最大的缺点就是上方提到的,使用 rebase 会修改 commit 的历史纪录,如果在自己的 local 整理 commit 或是分支那还好,但如果不小心去异动到 remote 的分支,然后又更不小心用了 git push -f,那可能就会被同事讨厌,或被投稿到纯靠北工程师 。
该用 git-rebase 或 git-merge? 在查了一些资料后,发现 rebase 和 merge 都各有拥护者,我先阐述他们的想法,再主观提一下自己的观点。
git-merge 派 支持 git-merge 派的工程师们认为,版本纪录有价值的地方就在于项目的 commit,也就是这个项目的「历史实际上发生过哪些事情」,如果你去修改了这些历史纪录那就很不好。因此即使不同分支的内容在 merge 后都混在一起,但这些内容仍然说明了这个项目的历史。
git-rebase 派 支持 git-rebase 派的工程师则觉得,commit 是在说这个项目的「演进过程」,发生了什麽事情才是重要的,即使修改了 commit 的历史,但是发生的事情依然没有改变,既然可以用更清楚简洁的纪录供后人阅读,那就应该要这麽做。
个人主观观点 我个人还是会使用 git-rebase 来修改 commit,让历史纪录更为简单好阅读,但使用上仅限于 push 到 remote 之前,如果今天已经把纪录 push 到 remote,那即使多乱我也不会去修改它们,毕竟 remote 的纪录就是大家共有的,不随意修改,也是尊重团队内的其他成员。
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
作者:神Q超人 > 来源:medium
原文:https://medium.com/starbugs/g...
交流
有梦想,有干货,微信搜索 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。
面试官(主说|面试官:主说 Git Rebase 和 Git Merge 差异)
文章图片

    推荐阅读