圖解 Git 基本命令 merge 和 rebase

Michael翔發表於2020-06-22

green-leaf-merge-git

Git 基本命令 merge 和 rebase,你真的瞭解嗎?

前言

Git 中的分支合併是一個常見的使用場景。

  • 倉庫的 bugfix 分支修復完 bug 之後,要回合到主幹分支,這時候兩個分支需要合併;
  • 遠端倉庫的分支 A 有其他小夥伴合入了程式碼,這時候,你需要和遠端倉庫的分支 A 進行合併;

以上只是列舉了分支合併的一些常見場景,關於 mergerebase 命令你足夠了解嗎?

HEAD 的理解

在介紹本文的主要內容之前,我們先理解一下 HEAD

HEAD 指向當前所在的分支,類似一個活動的指標,表示一個「引用」。例如當前在 develop 分支,HEAD 內容就是 ref: refs/heads/develop

HEAD 既可以指向「當前分支」的最新 commit,也可以指向歷史中的某一次 commit (「分離頭指標」的情況)。歸根結底,HEAD 指向的就是某個提交點。

當我們做分支切換時,HEAD 會跟著切換到對應分支。

fast-forward 與 --no-ff 的區別

假如有一個場景:有兩個分支,master 分支和 feature 分支。現在,feautre 分支需要合併回 master 分支。

fast-forward-初始狀態

fast-forward 合併方式是條件允許的情況,通過將 master 分支的 HEAD 位置移動到 feature 分支的最新提交點上,這樣就實現了快速合併。這種情況,是不會新生成 commit 的。

fast-forward

--no-ff 的方式進行合併,master 分支就會新生成一次提交記錄。

--no-ff

如果條件滿足時,merge 預設採用的 fast-forward 方式進行合併,除非你顯示的加上 --no-ff 選項;而條件不滿足時,merge 也是無法使用 fast-forward 合併成功的!

merge 操作

上面用圖解的方式介紹了 fast-forward--no-ff 的區別。下面,結合實際的程式碼倉進行合併操作,舉幾個栗子理解一下。

git merge 操作是區分上下文的。當前分支始終是目標分支,其他一個或多個分支始終合併到當前分支。這個注意點記住了,方便記憶!所以,當需要將某個分支合併到目標分支時,需要先切到目標分支上。

fast-forward 合併

剛剛一直在強調條件允許的時候,fast-forward 才能合併成功。條件指的是什麼呢?

其實指的是源分支和目標分支之間沒有分叉(單詞 diverge),這種情況才可以進行快速合併。如果是下圖中的場景,無法通過 HEAD 的快速移動實現分支的合併!

分叉

下面進行一個不分叉的場景的示例:

featuren 分支的初始狀態

現在需要將 feature 分支合入到 master 分支,預設使用 fast-forward 方式:

# 切到目標分支
git checkout master
git merge feature

命令列裡顯示了 Fast-forward 的提示:

合入效果

看一眼 master 分支合入的前後對比(注意 HEAD 的位置):

master 分支合入前

master 分支合入後

no-ff 合併

不分叉的場景是否可以強制採用 --no-ff 方式合併呢?可以!

# master 回到合入前的狀態
git reset --hard d2fa1ae
git merge feature --no-ff

no-ff

這次命令列沒有 Fast-forward 的提示了。

看一眼 master 分支圖:

no-ff

這個圖和上面講解 no-ff 命令時的示意圖一致,果然會有新 commit 生成。

分叉場景的合併

分叉

上面的圖展示了我們經常遇到的一個場景,特性分支建立之後,源分支也會有新的提交。這就是形成分叉了。

這時候如果我們進行合併呢?

git merge feautre

分支圖

可以看到,雖然預設會嘗試 fast-forward 的方式進行合併,但是因為分叉了,所以此時會採用 no-ff 的方式進行合併!有新的 commit 生成了!

fast-forward 方式對應的合併引數是 --ff

我們試試這個引數 --ff-only,顧名思義,就是強制只使用 ff 方式進行合併:

# 回到合併前
git reset --hard 3793081
git merge feature --ff-only

合併終止

經過測試,當分叉時,因為無法使用 ff 方式合併,即使你強制指定使用該方式合併也不行,會提示終止!

附上 Git 官方文件中的解釋,方便理解:

With --ff, when possible resolve the merge as a fast-forward (only update the branch pointer to match the merged branch; do not create a merge commit). When not possible (when the merged-in history is not a descendant of the current history), create a merge commit.

rebase 操作

rebase 命令是一個經常聽到,但是大多數人掌握又不太好的一個命令。rebase 合併往往又被稱為 「變基」,我稱為 「基化」?。「基」的理解很重要,理解了它,其實 rebase 命令你就掌握了。

我的描述可能並不準確,只是為了能夠幫助你理解。這裡的「基」就是一個「基點」、「起點」的意思。「變基」就是改變當前分支的起點。注意,是當前分支! rebase 命令後面緊接著的就是「基分支」。

變基前:

分叉

git reabse master feature 變基後:

變基後

git rebase 命令通常稱為向前移植(forward porting)。

變基提交示例

我們接下來進行實際的測試,將程式碼庫狀態構造成分叉的狀態,狀態圖如下:

分叉初始狀態

以 master 分支為基,對 feautre 分支進行變基:

git checout feature
git rebase master

以上兩行命令,其實可以簡寫為:git rebase master feature

特性分支 feature 向前移植到了 master 分支。經常使用 git rebase 操作把本地開發分支移植到遠端的 origin/<branch> 追蹤分支上。也就是經常說的,「把你的補丁變基到 xxx 分支的頭」

變基後

可以發現,在 master 分支的最新節點(576cb7b)後面多了 2 個提交(生成了新的提交記錄,僅僅提交資訊保持一致),而這兩個提交內容就是來自變基前 feature 分支,feature 分支的提交歷史發生了改變。

觀察上圖還可以發現,變基後,改變的只是 feature 分支,基分支(master 分支)的 HEAD 指標依然在之前的 commit (576cb7b)處。這時候要將 feature 分支合入到 master 分支上,就滿足 fast-forward 的條件了,master 分支執行快速合併,將 HEAD 指標指向剛剛最新合入的提交點:

git checkout master
git merge feature

快速合併

看下圖 master 分支圖,觀察 HEAD 指標的位置:
分支圖

rebase 變基操作最適合的是本地分支和遠端對應跟蹤分支之間的合併。這樣理解可能會更清晰一點。比如,遠端倉庫裡有一個特性分支 feature,除了你開發之外,還有其他人往這個分支進行合入。當你每次準備提交到遠端之前,其實可以嘗試變基,這時候基分支就是遠端的追蹤分支。

下圖是倉庫的分支圖:

與遠端分支分叉

git fetch
git rebase origin/feature feature

變基後

觀察上圖,我們本地的提交以遠端分支的最新提交為「基」,將差異提交重新進行了提交!遠端分支的提交記錄依然是一條直線~如果分叉的情況,不採用這種「變基操作」,而直接採用 merge 的方式合併,就會有如下這種分支提交圖:

no-ff 合併

因為分叉了,採用 git pull 時也沒法 fast-forward 合併,只能採用 no-ff 方式合併,最後的提交歷史就會像上圖那樣。會產生一個合併提交。同時,分支圖也顯得稍微雜亂了一點,因為 feature 分支不是一條直線了。但是,其實也有好處,可以實際的看出來合併的提交歷史。該選擇哪個,往往取決於團隊的選擇策略。

rebase 總結

rebase 命令其實關鍵在於理解「基」,git rebase <基分支>,就是將當前基分支與當前分支的差異提交獲取到,然後在「基分支」最新提交點後面將差異提交逐個再次提交,最後將當前分支的 HEAD 指標指向最新的提交點。

「基分支」的 HEAD 位置是不變的。要想完成分支合併,完成變基之後,需要再進行分支間的合併等操作。

rebase 命令的用法也不止於此,計劃後期會專門寫一篇介紹她的文章。本文字來是計劃介紹 merge 命令的,但是合併的方式中,其實也經常涉及變基操作之後的合併,因此,乾脆就放一起比較好了,這樣易於理解記憶。

補充

  • git merge --abort 當合並的過程中,由於衝突難解決,你想放棄合併,回到未合併之前的狀態;
  • git log --graph --pretty=oneline --abbrev-commit 可以在命令列方便地檢視提交圖

一言

在 Git 這個專輯裡有一篇介紹 cherry-pick 的文章,有個小夥伴給瞭如下的留言,說明自己分享的內容獲得了肯定,欣慰啊!

留言

今天肝的這篇文章,介紹了 Git 中的 merge 和 rebase 的基本概念和用法,同時,又自己手動繪製了圖!俗話說,一圖勝千言,但寫完才發現,是真的耗時啊……不過,總結繪圖的過程,自己也加深了理解,有些概念也變得更加清晰了!希望,我的總結也能讓其他人讀懂~

之前我經常會開啟文章的「讚賞」,但發現收效甚微,很少有小夥伴會打賞。後來我就每次發文就關閉了這個選項。本文應該是 6 月份的「月末總結」了,就開啟一次「月末讚賞」吧!期待小夥伴的支援與鼓勵!

參考

我將本文的參考文章也都註明了,他們也都很有閱讀的價值。但由於微信外鏈的緣故,可以點選右下角的「閱讀原文」瀏覽!


生命不息,折騰不止!關注 「Coder 魔法院」,祝你 Niubilitiy !??

公眾號-二維碼-截圖

相關文章