面試官:主說 Git Rebase 和 Git Merge 差異

前端小智 發表於 2022-05-19
面試 Git

有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

Hi!大家好,我是小智 !用 Git 做版本控管應該是大部分工程師每天都會碰到的工作流程之一,但我在使用上不外乎就是 pushpullmergecheckoutlog 等幾個指令,更深入一點就一問三不知了 😂,在面試時被問到了這個問題:「你知道 Git 的 merge 和 rebase 有什麼不同嗎?」

聽完後我直接困惑,對我來說 rebase 就是用來整理 commit 的工具,居然還可以和 merge 做比較?

git-rebase

先來說說平常我會用 rebase 這個指令來幹嘛,假如我新增了一個單元測試,然後 commit,這時候 log 就會多一條 commit 的紀錄:

image.png

但是在 commit 完才發現,我少寫了另一個測試案例,因此在補上之後,我又 commit 了一次:

image.png

這時記錄中會多出另外一條 commit,不過對我來說,這兩個 commit 在做的其實是同一件事,於是我在 push 到 remote 之前,就會想要先整理一下 commit,把這兩條記錄合併起來。

要把這兩條記錄合併起來有兩個方法,第一個是 reset 到新增第一個測試案例之前,然後直接做一次 commit。第二個方法就是用 rebase 來處理!

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

image.png

我的目的是把 9dc67ff87af945 整理成一個,所以要調整的 commit 是從 init, 也就是 commit id為 7eb57cb 之後的所有 commit,搭配上 rebase 指令的話就是:

git rebase -i 7eb57cb

輸入完後就會跳到 vim 的編輯畫面:

image.png

畫面上會看到 7eb57cb 後的所有 commit(目前就只有 9dc67ff87af945 ),接著把 9dc67ffpick 改成 squash,表示把它與前一個 commit 做合併。先點一下 i 後開始用 vim 編輯內容:

image.png

編輯完後,可以點 esc 再輸入 :wq 做儲存,如果只是好奇進來玩看看,不想儲存的話就輸入 :q!。結束上面的流程後,再檢視一次 log,會發現兩條 commit 變成一筆了。儲存完會跳到 commit message 的畫面,這邊可以讓你輸入合併後的 commit message,但我就不改了,一樣直接儲存:

image.png

結束上方的流程後,再檢視一次 log,會發現兩筆 commit 變成一筆了:

image.png

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

git-merge

大家應該對 merge 指令都非常熟悉,因為在做新功能的時候,通常都會拉一個分支出去,完成後再 merge 回 master 或 develop 等主要分支。操作流程如下:

image.png

在 merge 的時候會有兩種情況,第一種是 fast-forward,會把被合併分支的 HEAD 的 reference 移到要合併分支內最新的 commit 上,上方操作的 merge 結果就是 fast-forward,master 的 HEAD 被移到 string-library 的最新 commit,畫成圖的話就是這樣子:

image.png

但是如果在執行 merge 的時候產生衝突,那分支的合併行為就會和 fast-forward 有點不同了。舉例來說,我分別在 master 和 string-library 的同一個檔案新增內容,那當我執行 merge 的時候就會要求先修復衝突:

image.png

修復完後,再執行 commit 完成合並,而這一次合併時,會再多一個 commit 是有關 merge 了 string-library 分支的紀錄:

image.png

這個情況畫成圖就會像這樣子:

image.png

git-rebase 與 git-merge 的差異

看完上方對 rebasemerge 的介紹後,你也許會想說:

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

對的,原本我也是這麼認為,一直到我去看了 git-rebase 的文件,才發現原來我一直誤會它了。在 git book 的 rebase 篇章,第一段就說明了,在 Git 裡有兩種方法可以用來整合兩個分支,而這兩個在上方都有提到,分別為 mergerebase

image.png

從上方的 merge 例子已經知道了,merge 在合併的時候會有 fast-forward,和衝突時用一個 commit 記錄合併變更的兩種情形。而 rebase 的整合方式非常有趣,依照關於 rebase 的另一段說明,它可以「把某個分支中所有 commit 的過程,以另一個分支的 commit 為基礎重播一遍」:

image.png

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

image.png

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

git checkout string-library

然後再輸入 git rebase 指令,並於後方指定要在哪個分支上重播:

git rebase master

執行結果:

image.png

在 rebase 重播 commit 的過程中,和 merge 相似的地方在於,如果有衝突的話還是需要解決,但在解決後,並不是使用 commit 指令進行合併,而是要輸入 git rebase --continue,讓 rebase 可以繼續重播接下來的 commit:

image.png

重播完成時,會顯示目前重播到哪個 commit,以 string-library 來說就是最新的add string unit test D。這時候的分支關係,畫成圖就會變成:

image.png

上圖在經過 rebase 之後,string-library 裡 07e38fb 修改,會以 master 的 commit 為基底再重播一次。

需要注意的是,重播後的 commit id 會和原本的不一樣,這等於完全改寫了分支內所有的 commit 歷史紀錄。

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

image.png

因為已經先用 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...