使用Git中的Merge與Rebase與開源項目同步代碼

基於開源項目的開發有兩種主要工作模式。模式1是在從開源項目中拉出一個分支,在這個分支中開發新feature,完成後合併到upstream中。適用於本身是開源項目的developer。模式2是從開源項目中拉出分支後獨立發展,但定期從upstream拉更新(如重要版本升級時)。無論是哪種,都會面臨本地分支與upstream同步代碼的問題。爲此,git主要提供了兩種方式:一種是merge, 一種是rebase。下面通過例子簡單過一下它們的基本流程。

假設開源項目的git地址爲git://xxx.org/xxx/project。早先從它上面拉出代碼,作爲git repository放在在本地的代碼管理服務器上(比如gerrit服務器)。而在本地的開發機上有本地服務器上該項目git的clone。這樣,在本地開發機上執行git remote -v結果類似於:
local   ssh://[email protected]:xxxxx/xxx/project (fetch)
local   ssh://[email protected]:xxxxx/xxx/project (push)

首先將開源項目git加入remote源列表,名稱暫定爲upstream。
$ git remote add upstream git://xxx.org/xxx/project
再將新加源的信息同步下來:
$ git fetch
然後git remote -v結果就變成:
upstream       git://xxx.org/xxx/project (fetch)    
upstream       git://xxx.org/xxx/project (push)                    
local   ssh://[email protected]:xxxxx/xxx/project (fetch)
local   ssh://[email protected]:xxxxx/xxx/project (push)
現在當前git已經與兩個源關聯上了。

接下來從upstream源新建分支,稱爲upstream,它與開源項目git中的代碼一致:
$ git checkout remotes/upstream/master -b upstream
假設其中git log爲:
Commit 2 from branch upstream  2015/1/3
Commit 1 from branch upstream  2015/1/1

再從local源拉出分支,稱爲topic,它包含本地改動(比如新加feature):
$ git checkout remotes/local/master -b topic
當然,如果將來要提交到到gerrit服務器上,這兒用repo start topic .。假設其中log爲:
Commit 1 from branch topic 2015/1/2

Merge方式是把branch A在branch B上沒有的commit合併成一個commit,然後打在branch B上。如果要讓topic分支同步upstream中的改動,執行下面命令:
$ git checkout topic 
$ git merge upstream
如果想讓upstream同步topic改動,把上面命令中分支名的位置換下就行。如果有衝突的話會讓你解決(通過git diff查看衝突),解決完了以後git add + git commit(或git commit -a)。完成後在topic分支的git log裏看到的log有類似下面的結果:
Merge branch 'upstream' into topic
Commit 2 from branch upstream 2015/1/3
Commit 1 from branch topic 2015/1/2
Commit 1 from branch upstream 2015/1/1
兩個branch中的commit以時間順序排列,最頂上這個commit代表了這次merge的bundle commit。把它git reset掉的話所有原upstream上同步的改動都會消失。

Rebase方式是把branch A在branch B上沒有的commit挨個在branch B上再重新打一遍。通過git rebase的文檔,可以看到如果是git rebase upstream topic的話相當於git checkout topic + git rebase upstream。即把topic分支上的commit以upstream的HEAD爲base重新打一遍,重複的commit會自動忽略。因此,如果是前面提到的工作模式1的話就用git rebase topic upstream,工作模式2的話就用git rebase upstream topic。一般爲了避免whitespace帶來的錯誤,會加上--ignore-whitespace參數:
$ git rebase upstream topic --ignore-whitespace
更細粒度的rebase控制請參見-i參數。

在rebase過程中可能出現衝突,出現衝突時先用git diff看衝突情況。比如是a.c的話,解決衝突後執行:
$ git add a.c
然後執行下面命令繼續歡快地rebase:
$ git rebase --continue

完成後topic分支的git log裏是類似於這樣的:
Commit 1 from branch topic 2015/1/2
Commit 2 from branch upstream 2015/1/3
Commit 1 from branch upstream 2015/1/1
可以看到,這裏不是按時間順序的。topic分支的commit在上面,而upstream的commit在下面。如果是git rebase topic upstream的話順序就是相反的。最後,如果還需要讓upstream分支也同步topic分支的代碼。可以到upstream分支做一次fast-forward merge:
$ git checkout upstream
$ git merge topic
妥妥的。

同步完了之後,該git push就git push,該repo upload就repo upload,將同步的代碼更新到本地的代碼管理服務器上。

總結一下,從工作方式來說,merge與rebase最大的差別就是前者是把所有要打的commit作爲一整個commit打到目標分支上,後者是一個個重新打(但注意打上去後<SHA1>不同了,也就是說,雖然內容相同,但此commit已經不是原來那個了)。從結果來看,merge後log中commit是按時間順序的,最近的一個commit記錄了這次merge操作。rebase後log中不是按時間順序的,而是一個分支的commit在另一個分支的commit之後。總得來說,rebase比merge更靈活,更強大。但它的缺點是會改寫歷史。因此,不要對public的分支做rebase,否則你會冒着被同事砍死的風險(因爲他可能基於改寫前的歷史做了改動)。基本原則是,當從遠端同步代碼到本地時,用rebase。當本地完成feature開發,同步回遠端時用merge。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章