git-rebase和git-merge是干嘛的?差異是什么?

git-rebase和git-merge是干嘛的?git-rebase 與 git-merge 的差異是什么?下面本篇文章給大家介紹一下git-rebase 與 git-merge 的區(qū)別,希望對(duì)大家有所幫助!

git-rebase和git-merge是干嘛的?差異是什么?

用 Git 做版本控管應(yīng)該是大部分工程師每天都會(huì)碰到的工作流程之一,但我在使用上不外乎就是 push、pull、merge、checkout 或 log 等幾個(gè)指令,更深入一點(diǎn)就一問三不知了,在面試時(shí)被問到了這個(gè)問題:「你知道 Git 的 merge 和 rebase 有什麼不同嗎?」

聽完后我直接困惑,對(duì)我來說 rebase 就是用來整理 commit 的工具,居然還可以和 merge 做比較?【推薦學(xué)習(xí):《Git教程》】

git-rebase

先來說說平常我會(huì)用 rebase 這個(gè)指令來干嘛,假如我新增了一個(gè)單元測(cè)試,然后 commit,這時(shí)候 log 就會(huì)多一條 commit 的紀(jì)錄:

git-rebase和git-merge是干嘛的?差異是什么?

但是在 commit 完才發(fā)現(xiàn),我少寫了另一個(gè)測(cè)試案例,因此在補(bǔ)上之后,我又 commit 了一次:

git-rebase和git-merge是干嘛的?差異是什么?

這時(shí)記錄中會(huì)多出另外一條 commit,不過對(duì)我來說,這兩個(gè) commit 在做的其實(shí)是同一件事,于是我在 push 到 remote 之前,就會(huì)想要先整理一下 commit,把這兩條記錄合并起來。

要把這兩條記錄合并起來有兩個(gè)方法,第一個(gè)是 reset 到添加第一個(gè)測(cè)試案例之前,然后直接做一次 commit。第二個(gè)方法就是用 rebase 來處理!

首先讓我們看看目前的 log:

git-rebase和git-merge是干嘛的?差異是什么?

我的目的是把 9dc67ff 和 87af945 整理成一個(gè),所以要調(diào)整的 commit ?是從 init, 也就是 ?commit id為 7eb57cb 之后的所有 commit,搭配上 rebase 指令的話就是:

git rebase -i 7eb57cb

輸入完后就會(huì)跳到 vim 的編輯畫面:

git-rebase和git-merge是干嘛的?差異是什么?

畫面上會(huì)看到 7eb57cb 后的所有 commit(目前就只有 ?9dc67ff 和 87af945 ),接著把 9dc67ff 的 pick 改成 squash,表示把它與前一個(gè) commit 做合并。先點(diǎn)一下 i 后開始用 vim 編輯內(nèi)容:

git-rebase和git-merge是干嘛的?差異是什么?

編輯完后,可以點(diǎn) esc 再輸入 :wq 做保存,如果只是好奇進(jìn)來玩看看,不想保存的話就輸入 :q!。結(jié)束上面的流程后,再查看一次 log,會(huì)發(fā)現(xiàn)兩條 commit 變成一筆了。保存完會(huì)跳到 commit message 的畫面,這邊可以讓你輸入合并后的 commit message,但我就不改了,一樣直接保存:

git-rebase和git-merge是干嘛的?差異是什么?

結(jié)束上方的流程后,再查看一次 log,會(huì)發(fā)現(xiàn)兩筆 commit 變成一筆了:

git-rebase和git-merge是干嘛的?差異是什么?

先 nice,上述的操作為 rebase 的 interactive mode,在 git rebase 后輸入的 -i 其實(shí)就是 interactive 的縮寫。

git-merge

大家應(yīng)該對(duì) merge 指令都非常熟悉,因?yàn)樵谧鲂鹿δ艿臅r(shí)候,通常都會(huì)拉一個(gè)分支出去,完成后再 merge 回 master 或 develop 等主要分支。操作流程如下:

git-rebase和git-merge是干嘛的?差異是什么?

在 merge 的時(shí)候會(huì)有兩種情況,第一種是 ?fast-forward,會(huì)把被合并分支的 HEAD 的 reference 移到要合併分支內(nèi)最新的 commit 上,上方操作的 merge 結(jié)果就是 fast-forward,master 的 HEAD 被移到 string-library 的最新 commit,畫成圖的話就是這樣子:

git-rebase和git-merge是干嘛的?差異是什么?

但是如果在執(zhí)行 merge 的時(shí)候產(chǎn)生沖突,那分支的合并行為就會(huì)和 fast-forward 有點(diǎn)不同了。舉例來說,我分別在 master 和 string-library 的同一個(gè)文件添加內(nèi)容,那當(dāng)我執(zhí)行 merge 的時(shí)候就會(huì)要求先修復(fù)沖突:

git-rebase和git-merge是干嘛的?差異是什么?

修復(fù)完后,再執(zhí)行 commit 完成合并,而這一次合并時(shí),會(huì)再多一個(gè) commit 是有關(guān) merge 了 string-library 分支的紀(jì)錄:

git-rebase和git-merge是干嘛的?差異是什么?

這個(gè)情況畫成圖就會(huì)像這樣子:

git-rebase和git-merge是干嘛的?差異是什么?

git-rebase 與 git-merge 的差異

看完上方對(duì) rebase 和 merge 的介紹后,你也許會(huì)想說:

「咦?那這兩個(gè)不是完全不同的東西嗎?」

對(duì)的,原本我也是這麼認(rèn)為,一直到我去看了 git-rebase 的文檔,才發(fā)現(xiàn)原來我一直誤會(huì)它了。在 git book 的 rebase 篇章,第一段就說明了,在 Git 里有兩種方法可以用來整合兩個(gè)分支,而這兩個(gè)在上方都有提到,分別為 merge 和 rebase:

git-rebase和git-merge是干嘛的?差異是什么?

從上方的 merge 例子已經(jīng)知道了,merge 在合并的時(shí)候會(huì)有 fast-forward,和沖突時(shí)用一個(gè) commit 記錄合并變更的兩種情形。而 rebase 的整合方式非常有趣,依照關(guān)于 rebase 的另一段說明,它可以「把某個(gè)分支中所有 commit 的過程,以另一個(gè)分支的 commit 為基礎(chǔ)重播一遍」:

git-rebase和git-merge是干嘛的?差異是什么?

這是什麼意思呢?首先讓我們回到上述的例子,并在 master 分支上用 reset,讓 master 的版本回到合并 string-library 之前:

git-rebase和git-merge是干嘛的?差異是什么?

現(xiàn)在我們要用 rebase 指令,將 string-library 所有的 commit 修改,以 master 的 commit 為基礎(chǔ)跑一次。使用 rebase 合并的第一步,要先切到想重播 commit 的分支:

git checkout string-library

然后再輸入 git rebase 指令,并于后方指定要在哪個(gè)分支上重播:

git rebase master

執(zhí)行結(jié)果:

git-rebase和git-merge是干嘛的?差異是什么?

在 rebase 重播 commit 的過程中,和 merge 相似的地方在于,如果有沖突的話還是需要解決,但在解決后,并不是使用 commit 指令進(jìn)行合并,而是要輸入 git rebase –continue,讓 rebase 可以繼續(xù)重播接下來的 commit:

git-rebase和git-merge是干嘛的?差異是什么?

重播完成時(shí),會(huì)顯示目前重播到哪個(gè) commit,以 string-library 來說就是最新的add string unit test D。這時(shí)候的分支關(guān)系,畫成圖就會(huì)變成:

git-rebase和git-merge是干嘛的?差異是什么?

上圖在經(jīng)過 rebase 之后,string-library 里 07e38fb 修改,會(huì)以 master 的 commit 為基底再重播一次。

需要注意的是,重播后的 commit id 會(huì)和原本的不一樣,這等于完全改寫了分支內(nèi)所有的 commit 歷史紀(jì)錄。

另外,執(zhí)行完 rebase 后,string-library 其實(shí)還沒有被合并回 master 分支上,因此還是要再切回 master 執(zhí)行 merge,以完成合併:

git-rebase和git-merge是干嘛的?差異是什么?

因?yàn)橐呀?jīng)先用 rebase 在重播時(shí)處理完 commit 的沖突了,所以現(xiàn)在 merge 就會(huì)直接走 fast-forward 合并,也不會(huì)另外多一個(gè) merge 的 commit 紀(jì)錄。

使用 git-rebase 合併的優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

  • 不會(huì)在合併時(shí)產(chǎn)生多馀的 commit。

  • 可以在重播的時(shí)候以 commit 為單位處理沖突。

  • 合併時(shí)會(huì)依分支的 commit 排列,能夠比較清楚的 review issue 或 feature 處理的過程。如果使用 merge,在合併后就會(huì)依照時(shí)間順序穿插排列兩個(gè)分支的 commit。

  • 在貢獻(xiàn)開源項(xiàng)目的時(shí)候,如果在 push 前先做 rebase,那作者就能夠直接以 fast-forward 的方式合并,不需要再另外解沖突。

缺點(diǎn)

最大的缺點(diǎn)就是上方提到的,使用 rebase 會(huì)修改 commit 的歷史紀(jì)錄,如果在自己的 local 整理 commit 或是分支那還好,但如果不小心去異動(dòng)到 remote 的分支,然后又更不小心用了 git push -f,那可能就會(huì)被同事討厭,或被投稿到純靠北工程師。

該用 git-rebase 或 git-merge?

在查了一些資料后,發(fā)現(xiàn) rebase 和 merge 都各有擁護(hù)者,我先闡述他們的想法,再主觀提一下自己的觀點(diǎn)。

git-merge 派

支持 git-merge 派的工程師們認(rèn)為,版本紀(jì)錄有價(jià)值的地方就在于項(xiàng)目的 commit,也就是這個(gè)項(xiàng)目的「歷史實(shí)際上發(fā)生過哪些事情」,如果你去修改了這些歷史紀(jì)錄那就很不好。因此即使不同分支的內(nèi)容在 merge 后都混在一起,但這些內(nèi)容仍然說明了這個(gè)項(xiàng)目的歷史。

git-rebase 派

支持 git-rebase 派的工程師則覺得,commit 是在說這個(gè)項(xiàng)目的「演進(jìn)過程」,發(fā)生了什麼事情才是重要的,即使修改了 commit 的歷史,但是發(fā)生的事情依然沒有改變,既然可以用更清楚簡(jiǎn)潔的紀(jì)錄供后人閱讀,那就應(yīng)該要這麼做。

個(gè)人主觀觀點(diǎn)

我個(gè)人還是會(huì)使用 git-rebase 來修改 commit,讓歷史紀(jì)錄更為簡(jiǎn)單好閱讀,但使用上僅限于 push 到 remote 之前,如果今天已經(jīng)把紀(jì)錄 push 到 remote,那即使多亂我也不會(huì)去修改它們,畢竟 remote 的紀(jì)錄就是大家共有的,不隨意修改,也是尊重團(tuán)隊(duì)內(nèi)的其他成員。

【相關(guān)視頻教程推薦:Git教程

以上就是

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊14 分享