Git + GitHub 版本控制教學 (3) - 合併 merge 與 rebase

我們在不同的分支上開發新功能、解決 bug ,任務完成後就需要把功能合併回主分支。合併分支的方式主要有兩種:merge 與 rebase 。merge 的特點是不改變過去 commit 的歷史,而 rebase 會在合併的當下重新改寫過去 commit 的紀錄。

Git + GitHub 版本控制教學 (3) - 合併 merge 與 rebase

我們在不同的分支上開發新功能、解決 bug ,任務完成後就需要把功能合併回主分支。合併分支的方式主要有兩種:merge 與 rebase 。merge 的特點是不改變過去 commit 的歷史,而 rebase 會在合併的當下重新改寫過去 commit 的紀錄。

git merge

git merge 顧名思義就是把兩個分支合併在一起。假設今天我們想把 feature 這個分支給合併到 main 這個主分支,那我們首先要先 checkout 到 main 這個分支,然後做:

git merge feature

在合併時,git 會產生一個新的 merge commit。(下指令時他會跳出一個編輯 commit message 的畫面,就跟我們平常做 git commit 一樣,然後 default 的 commit message 類似為 'Merge feature into main'。

git merge

Fast-Forward

有另外一種狀況是 fast-forward。假設今天一樣要把 feature 這個分支合進 main 分支裡,feature 裡包含了 main 的所有 commit,這種情況下 git 將不會產生而外的 merge commit,而是進行 快進 的動作,把master 的 HEAD 指向 feature 裡最新的 commit。

fast-forward

git rebase

git rebase 的目地也是為了合併兩個分支,核心概念是改寫 commit 歷史,把兩個分支前後相接。假設今天要把 main 這個分支 rebase 到 feature (注意這裡我們的例子跟上面 merge 的例子相反),我們會先 checkout 到 feature 的分支,然後做:

git rebase main

rebase 時,我們會重建 feature 這個分支,先以 main 這個分支爲基底,然後把原本 feature 裡新增而 main 沒有的新 commit 都加在後面。每一個新 commit 的 code changes 雖然都與原先的相同,但由於歷史被改寫,會導致 commit id 改變。

git rebase

改變歷史這個功能很強大,git 提供了互動模式讓我們可以用 git rebase -i 更顆粒度地調整我們的 commits,後續章節會有更深入的討論。

衝突 conflict

git 一項強大的功能就是比對兩個分支,可以找出差異在哪 (diff),並且判斷出是否兩個分支可以安全的合併。

當兩個分支同時改寫到某一段邏輯,git 沒辦法判斷到底該用哪一個版本時就會產生衝突。如果有衝突時,合併會失敗,git 會在衝突的位置上做標記,我們就必須要以人工的方式把衝突解決。

git rebase 在衝突發生時會更麻煩一點。如上面所說,他會以 rebase 進來的分支為基準然後一個個 commit 依序加上去。每一個 commit 進入的當下都有可能會產生衝突,那我們就得一個個解決。每解決一個就用 git rebase --continue 繼續下去。

git merge 與 git rebase 對比的優缺點

兩者同樣是做合併的動作,使用場景卻不盡相同。

git merge 最大的優點是很直覺,剛接觸 git 的人都可以很快上手 merge。再來是由於他不改寫歷史,所有一切的動作都會被保留。但也因為每個分支的每個 commit 都被保留,同時還會多很多 merge 時新增的 commit ,有狀況發生時常會導致我們不好追蹤到底是過去的那一個commit 發生問題。

而 git rebase 改寫歷史的能力,讓我們得以把我們的 commit 紀錄、順序等維持的很乾淨,也可以避免 git merge 會多一個 merge commit 的問題。但也因為改寫歷史這能力太強,如果不熟很容易把你的分支給弄壞。特別注意如果跟團隊一起合作(用 Github 等遠端 repo),code 或分支推上遠端後就儘量不要在用 rebase 了,改寫歷史會讓其他人合作或 review 的人很 confuse。

Git + GitHub 版本控制教學 (4) - 回復到之前的版本:reset、revert、checkout
既然我們在 git 裡面獲得了存檔(commit)的功能,就表示 git 也提供了讓我們可以回到過去的能力。使用 reset、revert、checkout 可以讓我們回到過去的版本,本篇文章會介紹這些指令的差異以及使用的時機。