Git Step by Step (8):Git的merge和rebase
原文出處: 田小計劃的博客
前面一篇文章中提到了”git pull”等價于”git fetch”加上”git merge”,然后還提到了pull命令支持rebase模式,這篇文章就介紹一下merge和rebase之間有什么差別。
由于我們主要是想看看merge跟rebase之間的區別,這里就是用本地倉庫的分支進行演示了。
merge
其實在介紹分支的那篇文章中已經介紹過了一些分支merge的內容,這里就進行一些補充和總結。
下面我們基于本地一個倉庫開始介紹,當前倉庫的分支情況如下:
其實,merge命令總結下來會有三種情況發生:
-
merge命令不生效
當目標分支是當前分支的祖先commit節點,也就是說當前分支已經是最新的了,在這種情況下merge命令沒有任何效果。
在當前倉庫中,當我們把dev分支merge到master的時候,會得到”Already up-to-date.”
-
Fast-forward合并模式
當前分支是目標分支的祖先commit節點時,會發生Fast-forward的merge,看下圖
這時的對象模型就更新了,這里merge的操作只是把dev分支的HEAD引用進行更新,指向最新的commit對象
-
三方合并
請參照”Git Step by Step – (5) Git分支(branch)”中分支合并的內容。如果沒有沖突,Git會幫我們完成分支的合并,如果有沖突,就需要我們手動解決沖突了。
Fast-forward合并模式
在前面我們看到了Fast-forward合并模式,這種合并模式很簡單,只是HEAD引用的更新。但是合并后,我們從”git log”中將看不到分支的信息。
在Git中,我們可以選擇在merge的時候禁止Fast-forward。現在,我們通過”git reset –hard HEAD~1″撤銷前面的Fast-forward合并。
然后,我們在merge命令中加上”–no-ff”進行合并操作。
cherry-pick
這里我們將插入介紹一個非常有用的命令,雖然它跟merge沒有什么關系。
在實際應用中,我們可能會經常碰到這種情況,在分支A上提交了一個更新,但是后來發現我們同樣需要在分支B上應用這個更新。那么這時cherry-pick就可以幫助你解決問題。
我們要做的就是通過”git reflog”找到A上那個更新的SHA1哈希值,然后切換到B分支上使用”git cherry-pick”。
當我們手動合并過沖突,然后繼續執行”cherry-pick”時候,Git會給出友好的交互界面,如果我們不需要更新這個提交的message,我們可以直接”:wq”進行保存退出。到此,這個”cherry-pick”操作就成功了。
rebase
前面我們介紹了merge,現在來看看rebase。在merge的過程中,比較好的就是我們可以看到分支的歷史信息,但是,如果分支很多的時候,這個分支歷史可能就會變得很復雜了。如果我們使用rebase,那么提交的歷史會保持線性。
rebase的原理:先將當前分支的HEAD引用指向目標分支和當前分支的共同祖先commit節點,然后將當前分支上的commit一個個apply到目標分支上,apply完以后再將HEAD引用指向當前分支。是不是有點繞,下面我們看個實例。
下面就開始rebase的介紹,我們會基于master新建一個release-1.0的分支,并在該分支上提交一個更新。
這時,我們在release-1.0分支上執行”git rebase master”,就會得到下面的對象關系圖。
根據rebase的工作原理進行分析:
- 把當前分支的HEAD引用指向”00abc3f”
- 然后將當前分支上的commit一個個apply到目標分支,這里就是把”ed53897″更新apply到master上;注意,如果沒有沖突,這里將直接生成一個新的更新
- 最后更新當前分支的HEAD引用執行最新的提交”8791e3b”
這個就是rebase操作,可以看到通過rebase操作的話,我們的commit歷史會一直保持線性。在這種情況下,當我們切換到master分支,然后進行”git merge release-1.0″分支合并時,master將會直接是Fast-forward合并模式,commit歷史也會是線性的。
當然rebase操作也會產生沖突,當一個沖突發生的時候,我們可以skip過當前的patch(一定要當心,不要隨便使用,以免丟失更新);也可以手動的解決沖突,然后繼續rebase操作
rebase交互模式
其實,rebase還有別的很強大功能,比如rebase交互模式,通過交互模式我們可以改變commit的信息,刪除commit,合并commit以及更改commit的順序等等。
假如我們現在想要更新一個commit的信息,我們就可以使用rebase交互模式,找到commit的哈希值,然后進入交互模式。
根據rebase的操作提示,我們選擇edit操作,然后保存退出。
這時,Git將會提示我們,是進行更改,還是可以繼續操作。這里我們通過”git commit –amend”進入編輯模式。
在編輯模式中對commit進行更新,然后保存退出,繼續rebase操作。
關于rebase交互模式的其他命令,這里就不做介紹了,有興趣的同學可以google一下。
總結
這篇文章主要對merge和rebase進行了介紹。對于最終的結果而言,rebase和merge是沒有區別的,不會發生rebase和merge導致最終更新不一致的情況。
rebase和merge的差別主要是:
- rebase更清晰,因為commit歷史是線性的,但commit不一定按日期先后排,而是當前分支的commit總在后面(參考rebase原理)
- merge之后commit歷史變得比較復雜,并且一定程度上反映了各個分支的信息,而且 commit按日期排序的。
大家可以根據自己的項目需求進行選擇使用哪種方式拉取遠程的更新。