【轉】Git詳解之五 分佈式Git

分佈式 Git

爲了便於項目中的全部開發者分享代碼,咱們準備好了一臺服務器存放遠程 Git 倉庫。通過前面幾章的學習,咱們已經學會了一些基本的本地工做流程中所需用到的命令。接下來,咱們要學習下如何利用 Git 來組織和完成分佈式工做流程。php

特別是,看成爲項目貢獻者時,咱們該怎麼作才能方便維護者採納更新;或者做爲項目維護者時,又該怎樣有效管理大量貢獻者的提交。git

 

 

5.1  分佈式工做流程

同傳統的集中式版本控制系統(CVCS)不一樣,開發者之間的協做方式因着 Git 的分佈式特性而變得更爲靈活多樣。在集中式系統上,每一個開發者就像是鏈接在集線器上的節點,彼此的工做方式大致相像。而在 Git 網絡中,每一個開發者同時扮演着節點和集線器的角色,這就是說,每個開發者均可以將本身的代碼貢獻到另一個開發者的倉庫中,或者創建本身的公共倉庫,讓 其餘開發者基於本身的工做開始,爲本身的倉庫貢獻代碼。因而,Git 的分佈式協做即可以衍生出種種不一樣的工做流程,我會在接下來的章節介紹幾種常見的應用方式,並分別討論各自的優缺點。你能夠選擇其中的一種,或者結合起 來,應用到你本身的項目中。github

集中式工做流

一般,集中式工做流程使用的都是單點協做模型。一個存放代碼倉庫的中心服務器,能夠接受全部開發者提交的代碼。全部的開發者都是普通的節點,做爲中心集線器的消費者,平時的工做就是和中心倉庫同步數據(見圖 5-1)。shell

Git詳解之五 分佈式Git
圖 5-1. 集中式工做流

若是兩個開發者從中心倉庫克隆代碼下來,同時做了一些修訂,那麼只有第一個開發者能夠順利地把數據推送到共享服務器。第二個開發者在提交他的修訂之 前,必須先下載合併服務器上的數據,解決衝突以後才能推送數據到共享服務器上。在 Git 中這麼用也決無問題,這就比如是在用 Subversion(或其餘 CVCS)同樣,能夠很好地工做。vim

若是你的團隊不是很大,或者你們都已經習慣了使用集中式工做流程,徹底能夠採用這種簡單的模式。只須要配置好一臺中心服務器,並給每一個人推送數據的 權限,就能夠開展工做了。但若是提交代碼時有衝突, Git 根本就不會讓用戶覆蓋他人代碼,它直接駁回第二我的的提交操做。這就等於告訴提交者,你所做的修訂沒法經過快近(fast-forward)來合併,你必 須先拉取最新數據下來,手工解決衝突合併後,才能繼續推送新的提交。絕大多數人都熟悉和了解這種模式的工做方式,因此使用也很是普遍。緩存

集成管理員工做流

因爲 Git 容許使用多個遠程倉庫,開發者即可以創建本身的公共倉庫,往裏面寫數據並共享給他人,而同時又能夠從別人的倉庫中提取他們的更新過來。這種情形一般都會有 個表明着官方發佈的項目倉庫(blessed repository),開發者們由此倉庫克隆出一個本身的公共倉庫(developer public),而後將本身的提交推送上去,請求官方倉庫的維護者拉取更新合併到主項目。維護者在本身的本地也有個克隆倉庫(integration manager),他能夠將你的公共倉庫做爲遠程倉庫添加進來,通過測試無誤後合併到主幹分支,而後再推送到官方倉庫。工做流程看起來就像圖 5-2 所示:ruby

  1. 項目維護者能夠推送數據到公共倉庫 blessed repository。 2. 貢獻者克隆此倉庫,修訂或編寫新代碼。
  2. 貢獻者推送數據到本身的公共倉庫 developer public。 4. 貢獻者給維護者發送郵件,請求拉取本身的最新修訂。
  3. 維護者在本身本地的 integration manger 倉庫中,將貢獻者的倉庫加爲遠程倉庫,合併更新並作測試。
  4. 維護者將合併後的更新推送到主倉庫 blessed repository。
Git詳解之五 分佈式Git
圖 5-2. 集成管理員工做流

在 GitHub 網站上使用得最多的就是這種工做流。人們能夠複製(fork 亦即克隆)某個項目到本身的列表中,成爲本身的公共倉庫。隨後將本身的更新提交到這個倉庫,全部人均可以看到你的每次更新。這麼作最主要的優勢在於,你可 以按照本身的節奏繼續工做,而沒必要等待維護者處理你提交的更新;而維護者也能夠按照本身的節奏,任什麼時候候均可以過來處理接納你的貢獻。服務器

司令官與副官工做流

這實際上是上一種工做流的變體。通常超大型的項目纔會用到這樣的工做方式,像是擁有數百協做開發者的 Linux 內核項目就是如此。各個集成管理員分別負責集成項目中的特定部分,因此稱爲副官(lieutenant)。而全部這些集成管理員頭上還有一位負責統籌的總 集成管理員,稱爲司令官(dictator)。司令官維護的倉庫用於提供全部協做者拉取最新集成的項目代碼。整個流程看起來如圖 5-3 所示:網絡

  1. 通常的開發者在本身的特性分支上工做,並不按期地根據主幹分支(dictator 上的 master)衍合。
  2. 副官(lieutenant)將普通開發者的特性分支合併到本身的 master 分支中。
  3. 司令官(dictator)將全部副官的 master 分支併入本身的 master 分支。
  4. 司令官(dictator)將集成後的 master 分支推送到共享倉庫 blessed repository 中,以便全部其餘開發者以此爲基礎進行衍合。
Git詳解之五 分佈式Git

圖 5-3. 司令官與副官工做流

這種工做流程並不經常使用,只有當項目極爲龐雜,或者須要多級別管理時,纔會體現出優點。利用這種方式,項目總負責人(即司令官)能夠把大量分散的集成工做委託給不一樣的小組負責人分別處理,最後再統籌起來,如此各人的職責清晰明確,也不易出錯(譯註:此乃分而治之)。app

以上介紹的是常見的分佈式系統能夠應用的工做流程,固然不止於 Git。在實際的開發工做中,你可能會遇到各類爲了知足特定需求而有所變化的工做方式。我想如今你應該已經清楚,接下來本身須要用哪一種方式開展工做了。下 節我還會再舉些例子,看看各式工做流中的每一個角色具體應該如何操做。

 

5.2  爲項目做貢獻

接下來,咱們來學習一下做爲項目貢獻者,會有哪些常見的工做模式。

不過要說清楚整個協做過程真的很難,Git 如此靈活,人們的協做方式即可以各式各樣,沒有固定不變的範式可循,而每一個項目的具體狀況又多少會有些不一樣,好比說參與者的規模,所選擇的工做流程,每一個人的提交權限,以及 Git 之外貢獻等等,都會影響到具體操做的細節。

首當其衝的是參與者規模。項目中有多少開發者是常常提交代碼的?常常又是多久呢?大多數兩至三人的小團隊,一天大約只有幾回提交,若是不是什麼熱門 項目的話就更少了。可要是在大公司裏,或者大項目中,參與者能夠多到上千,天天都會有十幾個上百個補丁提交上來。這種差別帶來的影響是顯著的,越是多的人 參與進來,就越難保證每次合併正確無誤。你正在工做的代碼,可能會由於合併進來其餘人的更新而變得過期,甚至受創沒法運行。而已經提交上去的更新,也可能 在等着審覈合併的過程當中變得過期。那麼,咱們該怎樣作才能確保代碼是最新的,提交的補丁也是可用的呢?

接下來即是項目所採用的工做流。是集中式的,每一個開發者都具備等同的寫權限?項目是否有專人負責檢查全部補丁?是否是全部補丁都作過同行複閱(peer-review)再經過審覈的?你是否參與審覈過程?若是使用副官系統,那你是否是限定於只能向此副官提交?

還有你的提交權限。有或沒有向主項目提交更新的權限,結果徹底不一樣,直接決定最終採用怎樣的工做流。若是不能直接提交更新,那該如何貢獻本身的代碼呢?是否是該有個什麼策略?你每次貢獻代碼會有多少許?提交頻率呢?

全部以上這些問題都會或多或少影響到最終採用的工做流。接下來,我會在一系列由簡入繁的具體用例中,逐一闡述。此後在實踐時,應該能夠借鑑這裏的例子,略做調整,以知足實際須要構建本身的工做流。

提交指南

開始分析特定用例以前,先來了解下如何撰寫提交說明。一份好的提交指南能夠幫助協做者更輕鬆更有效地配合。Git 項目自己就提供了一份文檔(Git 項目源代碼目錄中Documentation/SubmittingPatches),列數了大量提示,從如何編撰提交說明到提交補丁,不一而足。

首先,請不要在更新中提交多餘的白字符(whitespace)。Git 有種檢查此類問題的方法,在提交以前,先運行 git diff --check,會把可能的多餘白字符修正列出來。下面的示例,我已經把終端中顯示爲紅色的白字符用X 替換掉:

$ git diff --check
lib/simplegit.rb:5: trailing whitespace.
+    @git_dir = File.expand_path(git_dir)XX
lib/simplegit.rb:7: trailing whitespace.
+ XXXXXXXXXXX
lib/simplegit.rb:26: trailing whitespace.
+    def command(git_cmd)XXXX

這樣在提交以前你就能夠看到這類問題,及時解決以避免困擾其餘開發者。

接下來,請將每次提交限定於完成一次邏輯功能。而且可能的話,適當地分解爲屢次小更新,以便每次小型提交都更易於理解。請不要在週末窮追猛打一次性 解決五個問題,而最後拖到週一再提交。就算是這樣也請儘量利用暫存區域,將以前的改動分解爲每次修復一個問題,再分別提交和加註說明。若是針對兩個問題 改動的是同一個文件,能夠試試看git add --patch 的方式將部份內容置入暫存區域(咱們會在第六章再詳細介紹)。不管是五次小提交仍是混雜在一塊兒的大提交,最終分支末端的項目快照應該仍是同樣的,但分解開 來以後,更便於其餘開發者複閱。這麼作也方便本身未來取消某個特定問題的修復。咱們將在第六章介紹一些重寫提交歷史,同暫存區域交互的技巧和工具,以便最 終獲得一個乾淨有意義,且易於理解的提交歷史。

最後須要謹記的是提交說明的撰寫。寫得好可讓你們協做起來更輕鬆。通常來講,提交說明最好限制在一行之內,50 個字符如下,簡明扼要地描述更新內容,空開一行後,再展開詳細註解。Git 項目自己須要開發者撰寫詳盡註解,包括本次修訂的因由,以及先後不一樣實現之間的比較,咱們也該借鑑這種作法。另外,提交說明應該用祈使如今式語態,好比, 不要說成 「I added tests for」 或 「Adding tests for」 而應該用 「Add tests for」。下面是來自 tpope.net 的 Tim Pope 原創的提交說明格式模版,供參考:

本次更新的簡要描述(50 個字符之內)

若是必要,此處展開詳盡闡述。段落寬度限定在 72 個字符之內。
某些狀況下,第一行的簡要描述將用做郵件標題,其他部分做爲郵件正文。
其間的空行是必要的,以區分二者(固然沒有正文另當別論)。
若是並在一塊兒,rebase 這樣的工具就可能會迷惑。

另起空行後,再進一步補充其餘說明。

 - 可使用這樣的條目列舉式。

 - 通常以單個空格緊跟短劃線或者星號做爲每項條目的起始符。每一個條目間用一空行隔開。
   不過這裏按本身項目的約定,能夠略做變化。

若是你的提交說明都用這樣的格式來書寫,好多事情就能夠變得十分簡單。Git 項目自己就是這樣要求的,我強烈建議你到 Git 項目倉庫下運行 git log --no-merges 看看,全部提交歷史的說明是怎樣撰寫的。(譯註:若是如今尚未克隆 git 項目源代碼,是時候git clone git://git.kernel.org/pub/scm/git/git.git 了。)

爲簡單起見,在接下來的例子(及本書隨後的全部演示)中,我都不會用這種格式,而使用 -m 選項提交 git commit。不過請仍是按照我以前講的作,別學我這裏偷懶的方式。

私有的小型團隊

咱們從最簡單的狀況開始,一個私有項目,與你一塊兒協做的還有另一到兩位開發者。這裏說私有,是指源代碼不公開,其餘人沒法訪問項目倉庫。而你和其餘開發者則都具備推送數據到倉庫的權限。

這種狀況下,大家能夠用 Subversion 或其餘集中式版本控制系統相似的工做流來協做。你仍然能夠獲得 Git 帶來的其餘好處:離線提交,快速分支與合併等等,但工做流程仍是差很少的。主要區別在於,合併操做發生在客戶端而非服務器上。讓咱們來看看,兩個開發者一 起使用同一個共享倉庫,會發生些什麼。第一我的,John,克隆了倉庫,做了些更新,在本地提交。(下面的例子中省略了常規提示,用... 代替以節約版面。)

# John's Machine
$ git clone john@githost:simplegit.git
Initialized empty Git repository in /home/john/simplegit/.git/
...
$ cd simplegit/
$ vim lib/simplegit.rb 
$ git commit -am 'removed invalid default value'
[master 738ee87] removed invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

第二個開發者,Jessica,同樣這麼作:克隆倉庫,提交更新:

# Jessica's Machine
$ git clone jessica@githost:simplegit.git
Initialized empty Git repository in /home/jessica/simplegit/.git/
...
$ cd simplegit/
$ vim TODO 
$ git commit -am 'add reset task'
[master fbff5bc] add reset task
 1 files changed, 1 insertions(+), 0 deletions(-)

如今,Jessica 將她的工做推送到服務器上:

# Jessica's Machine
$ git push origin master
...
To jessica@githost:simplegit.git
   1edee6b..fbff5bc  master -> master

John 也嘗試推送本身的工做上去:

# John's Machine
$ git push origin master
To john@githost:simplegit.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'

John 的推送操做被駁回,由於 Jessica 已經推送了新的數據上去。請注意,特別是你用慣了 Subversion 的話,這裏其實修改的是兩個文件,而不是同一個文件的同一個地方。Subversion 會在服務器端自動合併提交上來的更新,而 Git 則必須先在本地合併後才能推送。因而,John 不得不先把 Jessica 的更新拉下來:

$ git fetch origin
...
From john@githost:simplegit
 + 049d078...fbff5bc master     -> origin/master

此刻,John 的本地倉庫如圖 5-4 所示:

Git詳解之五 分佈式Git
圖 5-4. John 的倉庫歷史

雖然 John 下載了 Jessica 推送到服務器的最近更新(fbff5),但目前只是 origin/master 指針指向它,而當前的本地分支master 仍然指向本身的更新(738ee),因此須要先把她的提交合並過來,才能繼續推送數據:

$ git merge origin/master
Merge made by recursive.
 TODO |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

還好,合併過程很是順利,沒有衝突,如今 John 的提交歷史如圖 5-5 所示:

Git詳解之五 分佈式Git
圖 5-5. 合併 origin/master 後 John 的倉庫歷史

如今,John 應該再測試一下代碼是否仍然正常工做,而後將合併結果(72bbc)推送到服務器上:

$ git push origin master
...
To john@githost:simplegit.git
   fbff5bc..72bbc59  master -> master

最終,John 的提交歷史變爲圖 5-6 所示:

Git詳解之五 分佈式Git
圖 5-6. 推送後 John 的倉庫歷史

而在這段時間,Jessica 已經開始在另外一個特性分支工做了。她建立了 issue54 並提交了三次更新。她尚未下載 John 提交的合併結果,因此提交歷史如圖 5-7 所示:

Git詳解之五 分佈式Git
圖 5-7. Jessica 的提交歷史

Jessica 想要先和服務器上的數據同步,因此先下載數據:

# Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
   fbff5bc..72bbc59  master     -> origin/master

因而 Jessica 的本地倉庫歷史多出了 John 的兩次提交(738ee 和 72bbc),如圖 5-8 所示:

Git詳解之五 分佈式Git
圖 5-8. 獲取 John 的更新以後 Jessica 的提交歷史

此時,Jessica 在特性分支上的工做已經完成,但她想在推送數據以前,先確認下要並進來的數據到底是什麼,因而運行git log 查看:

$ git log --no-merges origin/master ^issue54
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith 
Date:   Fri May 29 16:01:27 2009 -0700

    removed invalid default value

如今,Jessica 能夠將特性分支上的工做併到 master 分支,而後再併入 John 的工做(origin/master)到本身的master分支,最後再推送回服務器。固然,得先切回主分支才能集成全部數據:

$ git checkout master
Switched to branch "master"
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.

要合併 origin/master 或 issue54 分支,誰先誰後都沒有關係,由於它們都在上游(upstream)(譯註:想像分叉的更新像是匯流成河的源頭,因此上游 upstream 是指最新的提交),因此無所謂前後順序,最終合併後的內容快照都是同樣的,而僅是提交歷史看起來會有些前後差異。Jessica 選擇先合併issue54

$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
 README           |    1 +
 lib/simplegit.rb |    6 +++++-
 2 files changed, 6 insertions(+), 1 deletions(-)

正如所見,沒有衝突發生,僅是一次簡單快進。如今 Jessica 開始合併 John 的工做(origin/master):

$ git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by recursive.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

全部的合併都很是乾淨。如今 Jessica 的提交歷史如圖 5-9 所示:

Git詳解之五 分佈式Git
圖 5-9. 合併 John 的更新後 Jessica 的提交歷史

如今 Jessica 已經能夠在本身的 master 分支中訪問 origin/master 的最新改動了,因此她應該能夠成功推送最後的合併結果到服務器上(假設 John 此時沒再推送新數據上來):

$ git push origin master
...
To jessica@githost:simplegit.git
   72bbc59..8059c15  master -> master

至此,每一個開發者都提交了若干次,且成功合併了對方的工做成果,最新的提交歷史如圖 5-10 所示:

Git詳解之五 分佈式Git
圖 5-10. Jessica 推送數據後的提交歷史

以上就是最簡單的協做方式之一:先在本身的特性分支中工做一段時間,完成後合併到本身的 master 分支;而後下載合併 origin/master 上的更新(若是有的話),再推回遠程服務器。通常的協做流程如圖 5-11 所示:

Git詳解之五 分佈式Git
圖 5-11. 多用戶共享倉庫協做方式的通常工做流程時序

私有團隊間協做

如今咱們來看更大一點規模的私有團隊協做。若是有幾個小組分頭負責若干特性的開發和集成,那他們之間的協做過程是怎樣的。

假設 John 和 Jessica 一塊兒負責開發某項特性 A,而同時 Jessica 和 Josie 一塊兒負責開發另外一項功能 B。公司使用典型的集成管理員式工做流,每一個組都有一名管理員負責集成本組代碼,及更新項目主倉庫的master 分支。全部開發都在表明小組的分支上進行。

讓咱們跟隨 Jessica 的視角看看她的工做流程。她參與開發兩項特性,同時和不一樣小組的開發者一塊兒協做。克隆生成本地倉庫後,她打算先着手開發特性 A。因而建立了新的featureA 分支,繼而編寫代碼:

# Jessica's Machine
$ git checkout -b featureA
Switched to a new branch "featureA"
$ vim lib/simplegit.rb
$ git commit -am 'add limit to log function'
[featureA 3300904] add limit to log function
 1 files changed, 1 insertions(+), 1 deletions(-)

此刻,她須要分享目前的進展給 John,因而她將本身的 featureA 分支提交到服務器。因爲 Jessica 沒有權限推送數據到主倉庫的master 分支(只有集成管理員有此權限),因此只能將此分支推上去同 John 共享協做:

$ git push origin featureA
...
To jessica@githost:simplegit.git
 * [new branch]      featureA -> featureA

Jessica 發郵件給 John 讓他上來看看 featureA 分支上的進展。在等待他的反饋以前,Jessica 決定繼續工做,和 Josie 一塊兒開發featureB 上的特性 B。固然,先建立此分支,分叉點以服務器上的 master 爲起點:

# Jessica's Machine
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch "featureB"

隨後,Jessica 在 featureB 上提交了若干更新:

$ vim lib/simplegit.rb
$ git commit -am 'made the ls-tree function recursive'
[featureB e5b0fdc] made the ls-tree function recursive
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'add ls-files'
[featureB 8512791] add ls-files
 1 files changed, 5 insertions(+), 0 deletions(-)

如今 Jessica 的更新歷史如圖 5-12 所示:

Git詳解之五 分佈式Git
圖 5-12. Jessica 的更新歷史

Jessica 正準備推送本身的進展上去,卻收到 Josie 的來信,說是她已經將本身的工做推到服務器上的 featureBee 分支了。這樣,Jessica 就必須先將 Josie 的代碼合併到本身本地分支中,才能再一塊兒推送回服務器。她用git fetch 下載 Josie 的最新代碼:

$ git fetch origin
...
From jessica@githost:simplegit
 * [new branch]      featureBee -> origin/featureBee

而後 Jessica 使用 git merge 將此分支合併到本身分支中:

$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by recursive.
 lib/simplegit.rb |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

合併很順利,但另外有個小問題:她要推送本身的 featureB 分支到服務器上的 featureBee 分支上去。固然,她可使用冒號(:)格式指定目標分支:

$ git push origin featureB:featureBee
...
To jessica@githost:simplegit.git
   fba9af8..cd685d1  featureB -> featureBee

咱們稱此爲_refspec_。更多有關於 Git refspec 的討論和使用方式會在第九章做詳細闡述。

接下來,John 發郵件給 Jessica 告訴她,他看了以後做了些修改,已經推回服務器 featureA 分支,請她過目下。因而 Jessica 運行git fetch 下載最新數據:

$ git fetch origin
...
From jessica@githost:simplegit
   3300904..aad881d  featureA   -> origin/featureA

接下來即可以用 git log 查看更新了些什麼:

$ git log origin/featureA ^featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith 
Date:   Fri May 29 19:57:33 2009 -0700

    changed log output to 30 from 25

最後,她將 John 的工做合併到本身的 featureA 分支中:

$ git checkout featureA
Switched to branch "featureA"
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forward
 lib/simplegit.rb |   10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)

Jessica 稍作一番修整後同步到服務器:

$ git commit -am 'small tweak'
[featureA ed774b3] small tweak
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git push origin featureA
...
To jessica@githost:simplegit.git
   3300904..ed774b3  featureA -> featureA

如今的 Jessica 提交歷史如圖 5-13 所示:

Git詳解之五 分佈式Git
圖 5-13. 在特性分支中提交更新後的提交歷史

如今,Jessica,Josie 和 John 通知集成管理員服務器上的 featureA 及 featureBee 分支已經準備好,能夠併入主線了。在管理員完成集成工做後,主分支上便多出一個新的合併提交(5399e),用 fetch 命令更新到本地後,提交歷史如圖 5-14 所示:

Git詳解之五 分佈式Git
圖 5-14. 合併特性分支後的 Jessica 提交歷史

許多開發小組改用 Git 就是由於它容許多個小組間並行工做,而在稍後恰當時機再行合併。經過共享遠程分支的方式,無需干擾總體項目代碼即可以開展工做,所以使用 Git 的小型團隊間協做能夠變得很是靈活自由。以上工做流程的時序如圖 5-15 所示:

Git詳解之五 分佈式Git
圖 5-15. 團隊間協做工做流程基本時序

公開的小型項目

上面說的是私有項目協做,但要給公開項目做貢獻,狀況就有些不一樣了。由於你沒有直接更新主倉庫分支的權限,得尋求其它方式把工做成果交給項目維護 人。下面會介紹兩種方法,第一種使用 git 託管服務商提供的倉庫複製功能,通常稱做 fork,好比 repo.or.cz 和 GitHub 都支持這樣的操做,並且許多項目管理員都但願你們使用這樣的方式。另外一種方法是經過電子郵件寄送文件補丁。

但無論哪一種方式,起先咱們總須要克隆原始倉庫,然後建立特性分支開展工做。基本工做流程以下:

$ git clone (url)
$ cd project
$ git checkout -b featureA
$ (work)
$ git commit
$ (work)
$ git commit

你可能想到用 rebase -i 將全部更新先變做單個提交,又或者想從新安排提交之間的差別補丁,以方便項目維護者審閱 – 有關交互式衍合操做的細節見第六章。

在完成了特性分支開發,提交給項目維護者以前,先到原始項目的頁面上點擊「Fork」按鈕,建立一個本身可寫的公共倉庫(譯註:即下面的 url 部分,參照後續的例子,應該是git://githost/simplegit.git)。而後將此倉庫添加爲本地的第二個遠端倉庫,姑且稱爲 myfork

$ git remote add myfork (url)

你須要將本地更新推送到這個倉庫。要是將遠端 master 合併到本地再推回去,還不如把整個特性分支推上去來得乾脆直接。並且,倘若項目維護者未採納你的貢獻的話(無論是直接合並仍是 cherry pick),都不用回退(rewind)本身的 master 分支。但若維護者合併或 cherry-pick 了你的工做,最後總還能夠從他們的更新中同步這些代碼。好吧,如今先把 featureA 分支整個推上去:

$ git push myfork featureA

而後通知項目管理員,讓他來抓取你的代碼。一般咱們把這件事叫作 pull request。能夠直接用 GitHub 等網站提供的 「pull request」 按鈕自動發送請求通知;或手工把git request-pull 命令輸出結果電郵給項目管理員。

request-pull 命令接受兩個參數,第一個是本地特性分支開始前的原始分支,第二個是請求對方來抓取的 Git 倉庫 URL(譯註:即下面myfork 所指的,本身可寫的公共倉庫)。好比如今Jessica 準備要給 John 發一個 pull requst,她以前在本身的特性分支上提交了兩次更新,並把分支整個推到了服務器上,因此運行該命令會看到:

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
  John Smith (1):
        added a new function

are available in the git repository at:

  git://githost/simplegit.git featureA

Jessica Smith (2):
      add limit to log function
      change log output to 30 from 25

 lib/simplegit.rb |   10 +++++++++-
 1 files changed, 9 insertions(+), 1 deletions(-)

輸出的內容能夠直接發郵件給管理者,他們就會明白這是從哪次提交開始旁支出去的,該到哪裏去抓取新的代碼,以及新的代碼增長了哪些功能等等。

像這樣隨時保持本身的 master 分支和官方 origin/master 同步,並將本身的工做限制在特性分支上的作法,既方便又靈活,採納和丟棄都垂手可得。就算原始主幹發生變化,咱們也能從新衍合提供新的補丁。好比如今要開始第二項特性的開發,不要在原來已推送的特性分支上繼續,仍是按原始master 開始:

$ git checkout -b featureB origin/master
$ (work)
$ git commit
$ git push myfork featureB
$ (email maintainer)
$ git fetch origin

如今,A、B 兩個特性分支各不相擾,如同竹筒裏的兩顆豆子,隊列中的兩個補丁,你隨時均可以分別從頭寫過,或者衍合,或者修改,而不用擔憂特性代碼的交叉混雜。如圖 5-16 所示:

Git詳解之五 分佈式Git
圖 5-16. featureB 之後的提交歷史

假設項目管理員接納了許多別人提交的補丁後,準備要採納你提交的第一個分支,卻發現由於代碼基準不一致,合併工做沒法正確乾淨地完成。這就須要你再次衍合到最新的 origin/master,解決相關衝突,而後從新提交你的修改:

$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA

天然,這會重寫提交歷史,如圖 5-17 所示:

Git詳解之五 分佈式Git
圖 5-17. featureA 從新衍合後的提交歷史

注意,此時推送分支必須使用 -f 選項(譯註:表示 force,不做檢查強制重寫)替換遠程已有的 featureA 分支,由於新的 commit 並不是原來的後續更新。固然你也能夠直接推送到另外一個新的分支上去,好比稱做featureAv2

再考慮另外一種情形:管理員看過第二個分支後以爲思路新穎,但想請你改下具體實現。咱們只需以當前 origin/master 分支爲基準,開始一個新的特性分支featureBv2,而後把原來的 featureB 的更新拿過來,解決衝突,按要求從新實現部分代碼,而後將此特性分支推送上去:

$ git checkout -b featureBv2 origin/master
$ git merge --no-commit --squash featureB
$ (change implementation)
$ git commit
$ git push myfork featureBv2

這裏的 --squash 選項將目標分支上的全部更改全拿來應用到當前分支上,而 --no-commit 選項告訴 Git 此時無需自動生成和記錄(合併)提交。這樣,你就能夠在原來代碼基礎上,繼續工做,直到最後一塊兒提交。

好了,如今能夠請管理員抓取 featureBv2 上的最新代碼了,如圖 5-18 所示:

Git詳解之五 分佈式Git
圖 5-18. featureBv2 以後的提交歷史

公開的大型項目

許多大型項目都會立有一套本身的接受補丁流程,你應該注意下其中細節。但多數項目都容許經過開發者郵件列表接受補丁,如今咱們來看具體例子。

整個工做流程相似上面的情形:爲每一個補丁建立獨立的特性分支,而不一樣之處在於如何提交這些補丁。不須要建立本身可寫的公共倉庫,也不用將本身的更新推送到本身的服務器,你只需將每次提交的差別內容以電子郵件的方式依次發送到郵件列表中便可。

$ git checkout -b topicA
$ (work)
$ git commit
$ (work)
$ git commit

如此一番後,有了兩個提交要發到郵件列表。咱們能夠用 git format-patch 命令來生成 mbox 格式的文件而後做爲附件發送。每一個提交都會封裝爲一個.patch 後綴的 mbox 文件,但其中只包含一封郵件,郵件標題就是提交消息(譯註:額外有前綴,看例子),郵件內容包含補丁正文和 Git 版本號。這種方式的妙處在於接受補丁時仍可保留原來的提交消息,請看接下來的例子:

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch

format-patch 命令依次建立補丁文件,並輸出文件名。上面的 -M 選項容許 Git 檢查是否有對文件重命名的提交。咱們來看看補丁文件的內容:

$ cat 0001-add-limit-to-log-function.patch 
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith 
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function

Limit log functionality to the first 20

---
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGit
   end

   def log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")
   end

   def ls_tree(treeish = 'master')
-- 
1.6.2.rc1.20.g8c5b.dirty

若是有額外信息須要補充,但又不想放在提交消息中說明,能夠編輯這些補丁文件,在第一個 --- 行以前添加說明,但不要修改下面的補丁正文,好比例子中的Limit log functionality to the first 20 部分。這樣,其它開發者能閱讀,但在採納補丁時不會將此合併進來。

你能夠用郵件客戶端軟件發送這些補丁文件,也能夠直接在命令行發送。有些所謂智能的郵件客戶端軟件會自做主張幫你調整格式,因此粘貼補丁到郵件正文 時,有可能會丟失換行符和若干空格。Git 提供了一個經過 IMAP 發送補丁文件的工具。接下來我會演示如何經過 Gmail 的 IMAP 服務器發送。另外,在 Git 源代碼中有個Documentation/SubmittingPatches 文件,能夠仔細讀讀,看看其它郵件程序的相關導引。

首先在 ~/.gitconfig 文件中配置 imap 項。每一個選項均可用 git config 命令分別設置,固然直接編輯文件添加如下內容更便捷:

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = p4ssw0rd
  port = 993
  sslverify = false

若是你的 IMAP 服務器沒有啓用 SSL,就無需配置最後那兩行,而且 host 應該以 imap:// 開頭而再也不是有 s 的imaps://。保存配置文件後,就能用 git send-email 命令把補丁做爲郵件依次發送到指定的 IMAP 服務器上的文件夾中(譯註:這裏就是 Gmail 的[Gmail]/Drafts 文件夾。但若是你的語言設置不是英文,此處的文件夾 Drafts 字樣會變爲對應的語言。):

$ git send-email *.patch
0001-added-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith ] 
Emails will be sent from: Jessica Smith 
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y

接下來,Git 會根據每一個補丁依次輸出相似下面的日誌:

(mbox) Adding cc: Jessica Smith  from 
  \line 'From: Jessica Smith '
OK. Log says:
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith 
To: jessica@example.com
Subject: [PATCH 1/2] added limit to log function
Date: Sat, 30 May 2009 13:29:15 -0700
Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com>
X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty
In-Reply-To:  References:  Result: OK

最後,到 Gmail 上打開 Drafts 文件夾,編輯這些郵件,修改收件人地址爲郵件列表地址,另外給要抄送的人也加到 Cc 列表中,最後發送。

小結

本節主要介紹了常見 Git 項目協做的工做流程,還有一些幫助處理這些工做的命令和工具。接下來咱們要看看如何維護 Git 項目,併成爲一個合格的項目管理員,或是集成經理。

 

5.3  項目的管理

既然是相互協做,在貢獻代碼的同時,也免不了要維護管理本身的項目。像是怎麼處理別人用 format-patch 生成的補丁,或是集成遠端倉庫上某個分支上的變化等等。但不管是管理代碼倉庫,仍是幫忙審覈收到的補丁,都須要同貢獻者約定某種長期可持續的工做方式。

使用特性分支進行工做

若是想要集成新的代碼進來,最好侷限在特性分支上作。臨時的特性分支可讓你隨意嘗試,進退自如。好比碰上沒法正常工做的補丁,能夠先擱在那邊,直到有時間仔細覈查修復爲止。建立的分支能夠用相關的主題關鍵字命名,好比ruby_client 或者其它相似的描述性詞語,以幫助未來回憶。Git 項目自己還時常把分支名稱分置於不一樣命名空間下,好比 sc/ruby_client 就說明這是 sc 這我的貢獻的。如今從當前主幹分支爲基礎,新建臨時分支:

$ git branch sc/ruby_client master

另外,若是你但願當即轉到分支上去工做,能夠用 checkout -b

$ git checkout -b sc/ruby_client master

好了,如今已經準備穩當,能夠試着將別人貢獻的代碼合併進來了。以後評估一下有沒有問題,最後再決定是否是真的要併入主幹。

採納來自郵件的補丁

若是收到一個經過電郵發來的補丁,你應該先把它應用到特性分支上進行評估。有兩種應用補丁的方法:git apply 或者git am

使用 apply 命令應用補丁

若是收到的補丁文件是用 git diff 或由其它 Unix 的 diff 命令生成,就該用 git apply 命令來應用補丁。假設補丁文件存在 /tmp/patch-ruby-client.patch,能夠這樣運行:

$ git apply /tmp/patch-ruby-client.patch

這會修改當前工做目錄下的文件,效果基本與運行 patch -p1 打補丁同樣,但它更爲嚴格,且不會出現混亂。若是是 git diff 格式描述的補丁,此命令還會相應地添加,刪除,重命名文件。固然,普通的patch 命令是不會這麼作的。另外請注意,git apply 是一個事務性操做的命令,也就是說,要麼全部補丁都打上去,要麼所有放棄。因此不會出現patch 命令那樣,一部分文件打上了補丁而另外一部分卻沒有,這樣一種不上不下的修訂狀態。因此總的來講,git apply 要比patch嚴謹許多。由於僅僅是更新當前的文件,因此此命令不會自動生成提交對象,你得手工緩存相應文件的更新狀態並執行提交命令。

在實際打補丁以前,能夠先用 git apply --check 查看補丁是否可以乾淨順利地應用到當前分支中:

$ git apply --check 0001-seeing-if-this-helps-the-gem.patch 
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply

若是沒有任何輸出,表示咱們能夠順利採納該補丁。若是有問題,除了報告錯誤信息以外,該命令還會返回一個非零的狀態,因此在 shell 腳本里可用於檢測狀態。

使用 am 命令應用補丁

若是貢獻者也用 Git,且擅於製做 format-patch 補丁,那你的合併工做將會很是輕鬆。由於這些補丁中除了文件內容差別外,還包含了做者信息和提交消息。因此請鼓勵貢獻者用format-patch 生成補丁。對於傳統的 diff 命令生成的補丁,則只能用 git apply 處理。

對於 format-patch 製做的新式補丁,應當使用 git am 命令。從技術上來講,git am 可以讀取 mbox 格式的文件。這是種簡單的純文本文件,能夠包含多封電郵,格式上用 From 加空格以及隨便什麼輔助信息所組成的行做爲分隔行,以區分每封郵件,就像這樣:

From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith 
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function

Limit log functionality to the first 20

這是 format-patch 命令輸出的開頭幾行,也是一個有效的 mbox 文件格式。若是有人用 git send-email 給你發了一個補丁,你能夠將此郵件下載到本地,而後運行git am 命令來應用這個補丁。若是你的郵件客戶端能將多封電郵導出爲 mbox 格式的文件,就能夠用 git am 一次性應用全部導出的補丁。

若是貢獻者將 format-patch 生成的補丁文件上傳到相似 Request Ticket 同樣的任務處理系統,那麼能夠先下載到本地,繼而使用git am 應用該補丁:

$ git am 0001-limit-log-function.patch 
Applying: add limit to log function

你會看到它被幹淨地應用到本地分支,並自動建立了新的提交對象。做者信息取自郵件頭 From 和 Date,提交消息則取自Subject 以及正文中補丁以前的內容。來看具體實例,採納以前展現的那個 mbox 電郵補丁後,最新的提交對象爲:

$ git log --pretty=fuller -1
commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0
Author:     Jessica Smith 
AuthorDate: Sun Apr 6 10:17:23 2008 -0700
Commit:     Scott Chacon 
CommitDate: Thu Apr 9 09:19:06 2009 -0700

   add limit to log function

   Limit log functionality to the first 20

Commit 部分顯示的是採納補丁的人,以及採納的時間。而 Author 部分則顯示的是原做者,以及建立補丁的時間。

有時,咱們也會遇到打不上補丁的狀況。這多半是由於主幹分支和補丁的基礎分支相差太遠,但也多是由於某些依賴補丁還未應用。這種狀況下,git am 會報錯並詢問該怎麼作:

$ git am 0001-seeing-if-this-helps-the-gem.patch 
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Patch failed at 0001.
When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".

Git 會在有衝突的文件里加入衝突解決標記,這同合併或衍合操做同樣。解決的辦法也同樣,先編輯文件消除衝突,而後暫存文件,最後運行 git am --resolved 提交修正結果:

$ (fix the file)
$ git add ticgit.gemspec 
$ git am --resolved
Applying: seeing if this helps the gem

若是想讓 Git 更智能地處理衝突,能夠用 -3 選項進行三方合併。若是當前分支未包含該補丁的基礎代碼或其祖先,那麼三方合併就會失敗,因此該選項默認爲關閉狀態。通常來講,若是該補丁是基於某個公開的提交製做而成的話,你老是能夠經過同步來獲取這個共同祖先,因此用三方合併選項能夠解決不少麻煩:

$ git am -3 0001-seeing-if-this-helps-the-gem.patch 
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.

像上面的例子,對於打過的補丁我又再打一遍,天然會產生衝突,但由於加上了 -3 選項,因此它很聰明地告訴我,無需更新,原有的補丁已經應用。

對於一次應用多個補丁時所用的 mbox 格式文件,能夠用 am 命令的交互模式選項 -i,這樣就會在打每一個補丁前停住,詢問該如何操做:

$ git am -3 -i mbox
Commit Body is:
--------------------------
seeing if this helps the gem
--------------------------
Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all

在多個補丁要打的狀況下,這是個很是好的辦法,一方面能夠預覽下補丁內容,同時也能夠有選擇性的接納或跳過某些補丁。

打完全部補丁後,若是測試下來新特性能夠正常工做,那就能夠安心地將當前特性分支合併到長期分支中去了。

檢出遠程分支

若是貢獻者有本身的 Git 倉庫,並將修改推送到此倉庫中,那麼當你拿到倉庫的訪問地址和對應分支的名稱後,就能夠加爲遠程分支,而後在本地進行合併。

好比,Jessica 發來一封郵件,說在她代碼庫中的 ruby-client 分支上已經實現了某個很是棒的新功能,但願咱們能幫忙測試一下。咱們能夠先把她的倉庫加爲遠程倉庫,而後抓取數據,完了再將她所說的分支檢出到本地來測試:

$ git remote add jessica git://github.com/jessica/myproject.git
$ git fetch jessica
$ git checkout -b rubyclient jessica/ruby-client

如果不久她又發來郵件,說還有個很棒的功能實如今另外一分支上,那咱們只需從新抓取下最新數據,而後檢出那個分支到本地就能夠了,無需重複設置遠程倉庫。

這種作法便於同別人保持長期的合做關係。但前提是要求貢獻者有本身的服務器,而咱們也須要爲每一個人建一個遠程分支。有些貢獻者提交代碼補丁並非很 頻繁,因此經過郵件接收補丁效率會更高。同時咱們本身也不會但願建上百來個分支,卻只從每一個分支取一兩個補丁。但如果用腳本程序來管理,或直接使用代碼倉 庫託管服務,就能夠簡化此過程。固然,選擇何種方式取決於你和貢獻者的喜愛。

使用遠程分支的另一個好處是可以獲得提交歷史。無論代碼合併是否是會有問題,至少咱們知道該分支的歷史分叉點,因此默認會從共同祖先開始自動進行三方合併,無需 -3 選項,也不用像打補丁那樣祈禱存在共同的基準點。

若是隻是臨時合做,只需用 git pull 命令抓取遠程倉庫上的數據,合併到本地臨時分支就能夠了。一次性的抓取動做天然不會把該倉庫地址加爲遠程倉庫。

$ git pull git://github.com/onetimeguy/project.git
From git://github.com/onetimeguy/project
 * branch            HEAD       -> FETCH_HEAD
Merge made by recursive.

決斷代碼取捨

如今特性分支上已合併好了貢獻者的代碼,是時候決斷取捨了。本節將回顧一些以前學過的命令,以看清將要合併到主幹的是哪些代碼,從而理解它們到底作了些什麼,是否真的要併入。

通常咱們會先看下,特性分支上都有哪些新增的提交。好比在 contrib 特性分支上打了兩個補丁,僅查看這兩個補丁的提交信息,能夠用--not 選項指定要屏蔽的分支 master,這樣就會剔除重複的提交歷史:

$ git log contrib --not master
commit 5b6235bd297351589efc4d73316f0a68d484f118
Author: Scott Chacon 
Date:   Fri Oct 24 09:53:59 2008 -0700

    seeing if this helps the gem

commit 7482e0d16d04bea79d0dba8988cc78df655f16a0
Author: Scott Chacon 
Date:   Mon Oct 22 19:38:36 2008 -0700

    updated the gemspec to hopefully work better

還能夠查看每次提交的具體修改。請牢記,在 git log 後加 -p 選項將展現每次提交的內容差別。

若是想看當前分支同其餘分支合併時的完整內容差別,有個小竅門:

$ git diff master

雖然能獲得差別內容,但請記住,結果有可能和咱們的預期不一樣。一旦主幹 master 在特性分支建立以後有所修改,那麼經過 diff 命令來比較的,是最新主幹上的提交快照。顯然,這不是咱們所要的。比方在 master 分支中某個文件裏添了一行,而後運行上面的命令,簡單的比較最新快照所獲得的結論只能是,特性分支中刪除了這一行。

這個很好理解:若是 master 是特性分支的直接祖先,不會產生任何問題;若是它們的提交歷史在不一樣的分叉上,那麼產生的內容差別,看起來就像是增長了特性分支上的新代碼,同時刪除了master 分支上的新代碼。

實際上咱們真正想要看的,是新加入到特性分支的代碼,也就是合併時會併入主幹的代碼。因此,準確地講,咱們應該比較特性分支和它同 master 分支的共同祖先之間的差別。

咱們能夠手工定位它們的共同祖先,而後與之比較:

$ git merge-base contrib master
36c7dba2c95e6bbb78dfa822519ecfec6e1ca649
$ git diff 36c7db

但這麼作很麻煩,因此 Git 提供了便捷的 ... 語法。對於 diff 命令,能夠把 ... 加在原始分支(擁有共同祖先)和當前分支之間:

$ git diff master...contrib

如今看到的,就是實際將要引入的新代碼。這是一個很是有用的命令,應該牢記。

代碼集成

一旦特性分支準備停當,接下來的問題就是如何集成到更靠近主線的分支中。此外還要考慮維護項目的整體步驟是什麼。雖然有不少選擇,不過咱們這裏只介紹其中一部分。

合併流程

通常最簡單的情形,是在 master 分支中維護穩定代碼,而後在特性分支上開發新功能,或是審覈測試別人貢獻的代碼,接着將它併入主幹,最後刪除這個特性分支,如此反覆。來看示例,假設當前代碼庫中有兩個分支,分別爲ruby_client和 php_client,如圖 5-19 所示。而後先把 ruby_client 合併進主幹,再合併php_client,最後的提交歷史如圖 5-20 所示。

Git詳解之五 分佈式Git
圖 5-19. 多個特性分支 Git詳解之五 分佈式Git
圖 5-20. 合併特性分支以後

這是最簡單的流程,因此在處理大一些的項目時可能會有問題。

對於大型項目,至少須要維護兩個長期分支 master 和 develop。新代碼(圖 5-21 中的 ruby_client)將首先併入develop 分支(圖 5-22 中的 C8),通過一個階段,確認develop 中的代碼已穩定到可發行時,再將 master 分支快進到穩定點(圖 5-23 中的 C8)。而平時這兩個分支都會被推送到公開的代碼庫。

Git詳解之五 分佈式Git
圖 5-21. 特性分支合併前 Git詳解之五 分佈式Git
圖 5-22. 特性分支合併後 Git詳解之五 分佈式Git
圖 5-23. 特性分支發佈後

這樣,在人們克隆倉庫時就有兩種選擇:既可檢出最新穩定版本,確保正常使用;也能檢出開發版本,試用最前沿的新特性。你也能夠擴展這個概念,先將全部新代碼合併到臨時特性分支,等到該分支穩定下來並經過測試後,再併入develop分支。而後,讓時間檢驗一切,若是這些代碼確實能夠正常工做至關長一段時間,那就有理由相信它已經足夠穩定,能夠放心併入主幹分支發佈。

大項目的合併流程

Git 項目自己有四個長期分支:用於發佈的 master 分支、用於合併基本穩定特性的 next 分支、用於合併仍需改進特性的pu 分支(pu 是 proposed updates 的縮寫),以及用於除錯維護的 maint 分支(maint 取自 maintenance)。維護者能夠按照以前介紹的方法,將貢獻者的代碼引入爲不一樣的特性分支(如圖 5-24 所示),而後測試評估,看哪些特性能穩定工做,哪些還需改進。穩定的特性能夠併入next 分支,而後再推送到公共倉庫,以供其餘人試用。

Git詳解之五 分佈式Git
圖 5-24. 管理複雜的並行貢獻

仍需改進的特性能夠先併入 pu 分支。直到它們徹底穩定後再併入 master。同時一併檢查下 next 分支,將足夠穩定的特性也併入 master。因此通常來講,master 始終是在快進,next 偶爾作下衍合,而pu 則是頻繁衍合,如圖 5-25 所示:

Git詳解之五 分佈式Git
圖 5-25. 將特性併入長期分支

併入 master 後的特性分支,已經無需保留分支索引,放心刪除好了。Git 項目還有一個 maint 分支,它是以最近一次發行版爲基礎分化而來的,用於維護除錯補丁。因此克隆 Git 項目倉庫後會獲得這四個分支,經過檢出不一樣分支能夠了解各自進展,或是試用前沿特性,或是貢獻代碼。而維護者則經過管理這些分支,逐步有序地併入第三方貢獻。

衍合與挑揀(cherry-pick)的流程

一些維護者更喜歡衍合或者挑揀貢獻者的代碼,而不是簡單的合併,由於這樣可以保持線性的提交歷史。若是你完成了一個特性的開發,並決定將它引入到主幹代碼中,你能夠轉到那個特性分支而後執行衍合命令,好在你的主幹分支上(也多是develop分支之類的)從新提交這些修改。若是這些代碼工做得很好,你就能夠快進master分支,獲得一個線性的提交歷史。

另外一個引入代碼的方法是挑揀。挑揀相似於針對某次特定提交的衍合。它首先提取某次提交的補丁,而後試着應用在當前分支上。若是某個特性分支上有多個 commits,但你只想引入其中之一就可使用這種方法。也可能僅僅是由於你喜歡用挑揀,討厭衍合。假設你有一個相似圖 5-26 的工程。

Git詳解之五 分佈式Git
圖 5-26. 挑揀(cherry-pick)以前的歷史

若是你但願拉取e43a6到你的主幹分支,能夠這樣:

$ git cherry-pick e43a6fd3e94888d76779ad79fb568ed180e5fcdf
Finished one cherry-pick.
[master]: created a0a41a9: "More friendly message when locking the index fails."
 3 files changed, 17 insertions(+), 3 deletions(-)

這將會引入e43a6的代碼,可是會獲得不一樣的SHA-1值,由於應用日期不一樣。如今你的歷史看起來像圖 5-27.

Git詳解之五 分佈式Git
圖 5-27. 挑揀(cherry-pick)以後的歷史

如今,你能夠刪除這個特性分支並丟棄你不想引入的那些commit。

給發行版簽名

你能夠刪除上次發佈的版本並從新打標籤,也能夠像第二章所說的那樣創建一個新的標籤。若是你決定以維護者的身份給發行版簽名,應該這樣作:

$ git tag -s v1.5 -m 'my signed 1.5 tag'
You need a passphrase to unlock the secret key for
user: "Scott Chacon "
1024-bit DSA key, ID F721C45A, created 2009-02-09

完成簽名以後,如何分發PGP公鑰(public key)是個問題。(譯者注:分發公鑰是爲了驗證標籤)。還好,Git的設計者想到了解決辦法:能夠把key(既公鑰)做爲blob變量寫入Git庫,而後把它的內容直接寫在標籤裏。gpg --list-keys命令能夠顯示出你所擁有的key:

$ gpg --list-keys
/Users/schacon/.gnupg/pubring.gpg
---------------------------------
pub   1024D/F721C45A 2009-02-09 [expires: 2010-02-09]
uid                  Scott Chacon 
sub   2048g/45D02282 2009-02-09 [expires: 2010-02-09]

而後,導出key的內容並經由管道符傳遞給git hash-object,以後鑰匙會以blob類型寫入Git中,最後返回這個blob量的SHA-1值:

$ gpg -a --export F721C45A | git hash-object -w --stdin
659ef797d181633c87ec71ac3f9ba29fe5775b92

如今你的Git已經包含了這個key的內容了,能夠經過不一樣的SHA-1值指定不一樣的key來建立標籤。

$ git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92

在運行git push --tags命令以後,maintainer-pgp-pub標籤就會公佈給全部人。若是有人想要校驗標籤,他可使用以下命令導入你的key:

$ git show maintainer-pgp-pub | gpg --import

人們能夠用這個key校驗你簽名的全部標籤。另外,你也能夠在標籤信息裏寫入一個操做嚮導,用戶只須要運行git show查看標籤信息,而後按照你的嚮導就能完成校驗。

生成內部版本號

由於Git不會爲每次提交自動附加相似’v123’的遞增序列,因此若是你想要獲得一個便於理解的提交號能夠運行git describe命令。Git將會返回一個字符串,由三部分組成:最近一次標定的版本號,加上自那次標定以後的提交次數,再加上一段SHA-1值of the commit you’re describing:

$ git describe master
v1.6.2-rc1-20-g8c5b85c

這個字符串能夠做爲快照的名字,方便人們理解。若是你的Git是你本身下載源碼而後編譯安裝的,你會發現git --version命令的輸出和這個字符串差很少。若是在一個剛剛打完標籤的提交上運行describe命令,只會獲得此次標定的版本號,而沒有後面兩項信息。

git describe命令只適用於有標註的標籤(經過-a或者-s選項建立的標籤),因此發行版的標籤都應該是帶有標註的,以保證git describe可以正確的執行。你也能夠把這個字符串做爲checkout或者show命令的目標,由於他們最終都依賴於一個簡短的SHA-1值,固然若是這個SHA-1值失效他們也跟着失效。最近Linux內核爲了保證SHA-1值的惟一性,將位數由8位擴展到10位,這就致使擴展以前的git describe輸出徹底失效了。

準備發佈

如今能夠發佈一個新的版本了。首先要將代碼的壓縮包歸檔,方便那些可憐的尚未使用Git的人們。可使用git archive

$ git archive master --prefix='project/' | gzip > `git describe master`.tar.gz
$ ls *.tar.gz
v1.6.2-rc1-20-g8c5b85c.tar.gz

這個壓縮包解壓出來的是一個文件夾,裏面是你項目的最新代碼快照。你也能夠用相似的方法創建一個zip壓縮包,在git archive加上--format=zip選項:

$ git archive master --prefix='project/' --format=zip > `git describe master`.zip

如今你有了一個tar.gz壓縮包和一個zip壓縮包,能夠把他們上傳到你網站上或者用e-mail發給別人。

製做簡報

是時候通知郵件列表裏的朋友們來檢驗你的成果了。使用git shortlog命令能夠方便快捷的製做一份修改日誌(changelog),告訴你們上次發佈以後又增長了哪些特性和修復了哪些bug。實際上這個命令可以統計給定範圍內的全部提交;假如你上一次發佈的版本是v1.0.1,下面的命令將給出自從上次發佈以後的全部提交的簡介:

$ git shortlog --no-merges master --not v1.0.1
Chris Wanstrath (8):
      Add support for annotated tags to Grit::Tag
      Add packed-refs annotated tag support.
      Add Grit::Commit#to_patch
      Update version and History.txt
      Remove stray `puts`
      Make ls_tree ignore nils

Tom Preston-Werner (4):
      fix dates in history
      dynamic version method
      Version bump to 1.0.2
      Regenerated gemspec for version 1.0.2

這就是自從v1.0.1版本以來的全部提交的簡介,內容按照做者分組,以便你能快速的發e-mail給他們。

 

5.4  小結

你學會了如何使用Git爲項目作貢獻,也學會了如何使用Git維護你的項目。恭喜!你已經成爲一名高效的開發者。在下一篇你將學到更強大的工具來處理更加複雜的問題,以後你會變成一位Git大師。

相關文章
相關標籤/搜索