這是一張有關標題為 操作 Git 的變基與分支合併 的圖片

操作 Git 的變基與分支合併

學習如何在 VS Code 上進行變基與分支合併。

前言

在前篇文章中介紹了 Git 分支的建立、切換與刪除

本篇的重點會說明如何在 VS Code 進行變基(rebase)整理提交,以及分支合併(merge)的操作流程。

範例情境

⚠️ 在進行以下操作前,請先確認所有變更已經提交至儲存庫,並且沒有任何暫存的內容。

合併

當我們從遠端儲存庫複製到本地後,若想要開發新功能,我們會創建一個新分支並進行後續提交。

例如,假設當前的主分支為 main(或 master),我們想開發新的演算法功能。這個新功能會基於目前已提交的內容進行開發,因此我們可以創建一個新分支(例如:feature/alg_dev)。

演算法開發完成後,我們可以將 feature/alg_dev 分支合併到 main 分支上,並進行推送。

(也可以透過變基將 feature/alg_dev 的提交移動到 main 上後推送。或是切換到 main 分支上,進行 cherry-pick 後推送。)

變基

在當前分支中,我們可以選擇某個提交作為起點,並對後續所有提交進行修改

例如:我們目前正在某個提交(Node)上開發:Node → 提交 A → 提交 B (feature/alg_dev)

然後我們發現遠端有了新的提交:Node → 提交 1 → 提交 2 (main)

我們可以進行變基,方法為切換到 feature/alg_dev 分支,並以提交 2為起點進行變基,這樣就會變成:Node → 提交 1 → 提交 2 → 提交 A → 提交 B,完成功能的整合。

另一種情境為:Node → 提交 A1 → 提交 A2 → 不需要的測試 C → 提交 B1 → 提交 B2

我們可以透過互動式變基(interactive rebase),任意的排序提交順序、壓縮(squash)、刪除(drop)、修改(edit)或改寫(reword)提交。

並將其提交壓縮,整理為:Node → 提交 A → 提交 B

當前的分支狀態

每個人的分支狀態不一樣,在此接續先前文章 開始使用 Git 管理專案 所建立的測試 Git repo 進行操作。

也強烈建議先從簡單的儲存庫開始操作,大約半天的時間就可以非常熟悉整個流程。

當前的 Git Graph

修改 Git 預設編輯器

使用 Remote SSH 連線 Linux 上進行進行 Git 操作,所使用的編輯器可能是 vim 或是 nano,可以透過以下指令將 Git 的編輯器改為 VS Code:

1
git config --global core.editor "code --wait"

Windows 下預設應該是 VS Code,如果不是也可以執行上方的指令進行修改。

合併(merge)

合併分支是將一個分支的更改整合到另一個分支的過程。它會創建一個特殊的合併提交,記錄了兩個分支的差異。

其中需要注意的是:

  1. 合併衝突:不同分支修改了同一個文件的同一部分時,進行合併操作可能會產生衝突。解決衝突需要手動選擇要保留哪些更改。這需要對原始內容有一定的熟悉程度才知道該如何解決衝突。
  2. 歷史保留:合併會保留分支的歷史。如果每次開發一個新功能都是使用合併分支的行為,會造成歷史紀錄太多且複雜。會需要有人對歷史紀錄進行重整。

合併分支

合併分支非常簡單,目前 master 的 SHA 為 f2654174,我們切到 master 分支上,並對分支 feature 右鍵點選Merge into current branch

此時會跳出三個選項,分別為:

  1. Create a new commit even if fast-forward is possible

    快進(fast-forward)會發生在分支為直線的情況,勾選則表示合併分支會額外建立一個新的提交(f68077d6),不勾選的話,由於 feature 對 master 是直線,所以會直接把 master 的 HEAD 直接移動到 9dcb1afa。

    如果切換到 feature2 分支,並想要合併 feature 的分支,由於他們並非直線,所以這個選項有勾沒勾都會是同樣的結果,都會建立一個新的合併提交。

  2. Squash Commits(壓縮提交)

    會額外進行壓縮後在提交,所以 bf2a6b87 與 9dcb1afa 會被壓縮後,建立一筆新的提交。

  3. No Commit

    會把 feature 分支上的提交壓縮後,儲存在暫存區中,不會建立新的提交。

Git 合併操作

變基(rebase)

變基是將一個分支的更改重新應用到另一個分支上。這種方法可以創造一個更線性、乾淨的提交歷史紀錄。

其中需要注意的是:

  1. 操作前建議切換到暫時分支:變基前建議開新的暫時分支進行操作,有任何操作失誤,想復原都可以隨時中止並回復到原始狀態。
  2. ⚠️變更歷史記錄:變基會修改歷史記錄,在多人合作的項目中,因為變基後的分支推送到遠端儲存庫時,會導致與其他貢獻者的歷史與遠端不一致,從而產生衝突。
  3. 衝突處理:由於修改了歷史紀錄,擷取、提交或推送時會遇到衝突。
  4. 變基有兩種:一種是標準變基(standard rebase)與互動式變基(interactive rebase),對於初學者和進階使用者而言,互動式變基提供更多的控制選項和靈活性。除非你對當前分支的狀態和你所進行的操作有充分的了解,否則建議優先考慮使用互動式變基。

互動式變基

在進行互動式變基時,我們會選擇一個特定的提交作為起點,並基於這個起點對後續的提交進行一系列的調整和修改。以下是在互動式變基過程中可以對提交進行的操作:

  • pick:選擇此提交並保持原樣,不進行任何修改。
  • reword:僅修改此提交的訊息,不改動任何檔案內容。
  • edit:在此處暫停變基過程,允許對提交本身進行修改,比如更改檔案內容或者提交訊息。
  • squash:將此提交與前一個提交合併,同時合併兩個提交的訊息。
  • fixup:功能與squash相似,但會丟棄此提交的訊息,只保留前一個提交的訊息。(這個基本上不會使用到。)
  • drop:放棄此提交,將其從提交歷史中完全刪除。

選擇(Pick)

把 feature2 的修改,變基到 feature 上。

我們會先切換到 feature2 的分支(在此預防操作失誤,開了新的分支: merge_all),然後對著 feature 上的最後一筆提交進行變基做為起點,在此只進行 pick 的行為。

Rebase branch feature2 on branch feature

反之,把 feature 的修改變基到 feature2 上。

切換到 feature 分支,並對著 feature2 進行變基。

會把 feature 上的 [新增] 您好.txt[新增] 您好2.txt 提交,附加在 [New] 新增第三行內容 後。同理,如果變基過程中有不要的提交,就把 pick 改為 drop 即可。

Rebase branch feature on branch2 feature

壓縮提交(Squash)

把 feature 的修改合併到 feature2 上,如果想要壓縮多個提交,可以透過壓縮提交實現。在進行變基過程會要求輸入提交訊息。

使用變基時壓縮提交

修改提交訊息(reword)

把 feature 的修改合併到 feature2 上,並且修改特定提交的內容,但不想合併,可以選擇 reword。在進行變基過程會要求輸入提交內容。

使用變基時修改提交訊息

編輯提交內容(edit)

最後,是進行變基的編輯(edit),假設我們當初在提交時提交了一坨檔案,但是這個提交內的檔案應該要拆成獨立的提交,我們可以透過變基(rebase)→重置(reset)→提交(commit),把檔案拆為獨立的提交。當然選擇編輯提交內容也可以單純只編輯訊息,不更動檔案。或者進行其他操作,如挑選(cherry-pick)、新增檔案等等。

以範例來說,可以看到[新增] 一坨.txt這個提交有兩個檔案變更,我們的目的是將兩個檔案變更拆解為兩個獨立提交。

在該提交的上一筆進行變基,並在提交面板中選擇 edit 後,會進入分離指標狀態,此時的 HEAD 仍然指向[新增] 一坨.txt

接著,我們需要復原上一個提交(git reset HEAD~1)。這會將 HEAD 退回上一個提交,也就是 [New] 新增第三行內容,並且 [新增] 一坨.txt 原先內的檔案會變為暫存狀態(stage)。

將檔案全部取消暫存後(unstage all changes)後會變為變更狀態(modified)。這邊可以選擇性地重新將它們加入暫存區後。由於 VS Code 的限制,在變基狀態下沒辦法透過 UI 的方式進行提交,所以只能透過指令的方式新增提交:

1
git commit -m "標題" -m "內容行1" -m "內容行2"

檔案提交完畢後,再透過指令完成整個變基的過程:

1
git rebase --continue

使用變基拆解提交

推送(push)

進行變基後推送到遠端伺服器一定會發生衝突,因為本地端與遠端的父節點(Parent Node)不一致,如果操作的分支有其他人一同作業,推送的模式一定要選擇 Force With Lease 確保不會覆蓋到他人的提交。

使用 Force with release 進行推送

結論

通常,變基用於保持一個乾淨、直線的提交歷史,在我目前的專案中會盡量使用變基而不是合併分支。

然而,合併操作則可以保留分支間的歷史脈絡,這部分可能會在根據團隊內的習慣、權限、策略決定,當需要保持完整的開發歷程記錄時,合併分支是更合適的選擇。團隊內最終勢必會有一個人負責整理整個儲存庫(或沒有?),使其盡可能保持乾淨、整潔但又同時可追朔,往往會取決於當初提交的那個人有沒有好好寫提交訊息供後人進行追蹤與整合。

此外,無論是透過變基還是合併,衝突的發生都是有可能的,面對未知的儲存庫時會因為掌握度不夠,導致不知道該如何進行合併衝突。在此會建議更加熟悉程式碼後再進行操作。而要避免麻煩的失誤,在操作上述行為前會建議開啟新的分支進行操作。如果遇到任何問題,使用 git rebase --abort 可以隨時撤銷變基操作。確定變基成功或是沒有衝突,再將原本的分支刪除,並將暫存分支進行改名。

在面對像屎般、又臭又長的提交,或許直接全部壓縮也不無是一種好的方法,畢竟某些提交 5 年以上了,又沒有寫提交訊息的情況下,鬼才知道他當初為了什麼而提交。不過討論到最後又是整個公司制度的事情了。

最後,當需要多人協作時進行變基後,應使用 Force With Lease 進行推送,這是一個既安全且公認的做法。

參考文獻

  1. Git Branching - Rebasing
  2. Git Branching
  3. Git Tools - Rewriting History
  4. Git Basics - Undoing Things
  5. About Git rebase
  6. Using Git rebase on the command line
  7. Associating text editors with Git
主題 Stack 由 Jimmy 設計