接下來,我們來學習一下作為項目貢獻者,會有哪些常見的工作模式。
不過要說清楚整個協(xié)作過程真的很難,Git 如此靈活,人們的協(xié)作方式便可以各式各樣,沒有固定不變的范式可循,而每個項目的具體情況又多少會有些不同,比如說參與者的規(guī)模,所選擇的工作流程,每個人的提交權限,以及 Git 以外貢獻等等,都會影響到具體操作的細節(jié)。
首當其沖的是參與者規(guī)模。項目中有多少開發(fā)者是經(jīng)常提交代碼的?經(jīng)常又是多久呢?大多數(shù)兩至三人的小團隊,一天大約只有幾次提交,如果不是什么熱門項目的話就更少了??梢窃诖蠊纠铮蛘叽箜椖恐?,參與者可以多到上千,每天都會有十幾個上百個補丁提交上來。這種差異帶來的影響是顯著的,越是多的人參與進來,就越難保證每次合并正確無誤。你正在工作的代碼,可能會因為合并進來其他人的更新而變得過時,甚至受創(chuàng)無法運行。而已經(jīng)提交上去的更新,也可能在等著審核合并的過程中變得過時。那么,我們該怎樣做才能確保代碼是最新的,提交的補丁也是可用的呢?
接下來便是項目所采用的工作流。是集中式的,每個開發(fā)者都具有等同的寫權限?項目是否有專人負責檢查所有補?。渴遣皇撬醒a丁都做過同行復閱(peer-review)再通過審核的?你是否參與審核過程?如果使用副官系統(tǒng),那你是不是限定于只能向此副官提交?
還有你的提交權限。有或沒有向主項目提交更新的權限,結果完全不同,直接決定最終采用怎樣的工作流。如果不能直接提交更新,那該如何貢獻自己的代碼呢?是不是該有個什么策略?你每次貢獻代碼會有多少量?提交頻率呢?
所有以上這些問題都會或多或少影響到最終采用的工作流。接下來,我會在一系列由簡入繁的具體用例中,逐一闡述。此后在實踐時,應該可以借鑒這里的例子,略作調整,以滿足實際需要構建自己的工作流。
開始分析特定用例之前,先來了解下如何撰寫提交說明。一份好的提交指南可以幫助協(xié)作者更輕松更有效地配合。Git 項目本身就提供了一份文檔(Git 項目源代碼目錄中 Documentation/SubmittingPatches),列數(shù)了大量提示,從如何編撰提交說明到提交補丁,不一而足。
首先,請不要在更新中提交多余的白字符(whitespace)
。Git 有種檢查此類問題的方法,在提交之前,先運行 git diff --check,會把可能的多余白字符修正列出來。下面的示例,我已經(jīng)把終端中顯示為紅色的白字符用 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
這樣在提交之前你就可以看到這類問題,及時解決以免困擾其他開發(fā)者。
接下來,請將每次提交限定于完成一次邏輯功能。并且可能的話,適當?shù)胤纸鉃槎啻涡「拢员忝看涡⌒吞峤欢几子诶斫?。請不要在周末窮追猛打一次性解決五個問題,而最后拖到周一再提交。就算是這樣也請盡可能利用暫存區(qū)域,將之前的改動分解為每次修復一個問題,再分別提交和加注說明。如果針對兩個問題改動的是同一個文件,可以試試看 git add --patch 的方式將部分內容置入暫存區(qū)域(我們會在第六章再詳細介紹)。無論是五次小提交還是混雜在一起的大提交,最終分支末端的項目快照應該還是一樣的,但分解開來之后,更便于其他開發(fā)者復閱。這么做也方便自己將來取消某個特定問題的修復。我們將在第六章介紹一些重寫提交歷史,同暫存區(qū)域交互的技巧和工具,以便最終得到一個干凈有意義,且易于理解的提交歷史。
最后需要謹記的是提交說明的撰寫。寫得好可以讓大家協(xié)作起來更輕松。一般來說,提交說明最好限制在一行以內,50 個字符以下,簡明扼要地描述更新內容,空開一行后,再展開詳細注解。Git 項目本身需要開發(fā)者撰寫詳盡注解,包括本次修訂的因由,以及前后不同實現(xiàn)之間的比較,我們也該借鑒這種做法。另外,提交說明應該用祈使現(xiàn)在式語態(tài),比如,不要說成“I added tests for”
或 “Adding tests for”
而應該用 “Add tests for”。 下面是來自 tpope.net
的 Tim Pope
原創(chuàng)的提交說明格式模版,供參考:
本次更新的簡要描述(50 個字符以內)
如果必要,此處展開詳盡闡述。段落寬度限定在 72 個字符以內。
某些情況下,第一行的簡要描述將用作郵件標題,其余部分作為郵件正文。
其間的空行是必要的,以區(qū)分兩者(當然沒有正文另當別論)。
如果并在一起,rebase 這樣的工具就可能會迷惑。
另起空行后,再進一步補充其他說明。
可以使用這樣的條目列舉式。
一般以單個空格緊跟短劃線或者星號作為每項條目的起始符。每個條目間用一空行隔開。
不過這里按自己項目的約定,可以略作變化。
如果你的提交說明都用這樣的格式來書寫,好多事情就可以變得十分簡單。Git 項目本身就是這樣要求的,我強烈建議你到 Git 項目倉庫下運行 git log --no-merges 看看,所有提交歷史的說明是怎樣撰寫的。(譯注:如果現(xiàn)在還沒有克隆 git 項目源代碼,是時候 git clone git://git.kernel.org/pub/scm/git/git.git 了。)
為簡單起見,在接下來的例子(及本書隨后的所有演示)中,我都不會用這種格式,而使用 -m 選項提交 git commit。不過請還是按照我之前講的做,別學我這里偷懶的方式。
我們從最簡單的情況開始,一個私有項目,與你一起協(xié)作的還有另外一到兩位開發(fā)者。這里說私有,是指源代碼不公開,其他人無法訪問項目倉庫。而你和其他開發(fā)者則都具有推送數(shù)據(jù)到倉庫的權限。
這種情況下,你們可以用 Subversion 或其他集中式版本控制系統(tǒng)類似的工作流來協(xié)作。你仍然可以得到 Git 帶來的其他好處:離線提交,快速分支與合并等等,但工作流程還是差不多的。主要區(qū)別在于,合并操作發(fā)生在客戶端而非服務器上。 讓我們來看看,兩個開發(fā)者一起使用同一個共享倉庫,會發(fā)生些什么。第一個人,John,克隆了倉庫,作了些更新,在本地提交。(下面的例子中省略了常規(guī)提示,用 ... 代替以節(jié)約版面。)
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(-)
第二個開發(fā)者,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(-)
現(xiàn)在,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 已經(jīng)推送了新的數(shù)據(jù)上去。請注意,特別是你用慣了 Subversion 的話,這里其實修改的是兩個文件,而不是同一個文件的同一個地方。Subversion 會在服務器端自動合并提交上來的更新,而 Git 則必須先在本地合并后才能推送。于是,John 不得不先把 Jessica 的更新拉下來:
$ git fetch origin
...
From john@githost:simplegit
+ 049d078...fbff5bc master -> origin/master
此刻,John 的本地倉庫如圖 5-4 所示:
圖 5-4. John 的倉庫歷史
雖然 John 下載了 Jessica 推送到服務器的最近更新(fbff5),但目前只是 origin/master 指針指向它,而當前的本地分支 master 仍然指向自己的更新(738ee),所以需要先把她的提交合并過來,才能繼續(xù)推送數(shù)據(jù):
$ git merge origin/master
Merge made by recursive.
TODO | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
還好,合并過程非常順利,沒有沖突,現(xiàn)在 John 的提交歷史如圖 5-5 所示:
圖 5-5. 合并 origin/master 后 John 的倉庫歷史
現(xiàn)在,John 應該再測試一下代碼是否仍然正常工作,然后將合并結果(72bbc)推送到服務器上:
$ git push origin master
...
To john@githost:simplegit.git
fbff5bc..72bbc59 master -> master
最終,John 的提交歷史變?yōu)閳D 5-6 所示:
圖 5-6. 推送后 John 的倉庫歷史
而在這段時間,Jessica 已經(jīng)開始在另一個特性分支工作了。她創(chuàng)建了 issue54 并提交了三次更新。她還沒有下載 John 提交的合并結果,所以提交歷史如圖 5-7 所示:
圖 5-7. Jessica 的提交歷史
Jessica 想要先和服務器上的數(shù)據(jù)同步,所以先下載數(shù)據(jù):
Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
fbff5bc..72bbc59 master -> origin/master
于是 Jessica 的本地倉庫歷史多出了 John 的兩次提交(738ee 和 72bbc),如圖 5-8 所示:
圖 5-8. 獲取 John 的更新之后 Jessica 的提交歷史
此時,Jessica 在特性分支上的工作已經(jīng)完成,但她想在推送數(shù)據(jù)之前,先確認下要并進來的數(shù)據(jù)究竟是什么,于是運行 git log 查看:
$ git log --no-merges origin/master ^issue54
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date: Fri May 29 16:01:27 2009 -0700
removed invalid default value
現(xiàn)在,Jessica 可以將特性分支上的工作并到 master 分支,然后再并入 John 的工作(origin/master)到自己的 master 分支,最后再推送回服務器。當然,得先切回主分支才能集成所有數(shù)據(jù):
$ 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(-)
正如所見,沒有沖突發(fā)生,僅是一次簡單快進。現(xiàn)在 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(-)
所有的合并都非常干凈?,F(xiàn)在 Jessica 的提交歷史如圖 5-9 所示:
圖 5-9. 合并 John 的更新后 Jessica 的提交歷史
現(xiàn)在 Jessica 已經(jīng)可以在自己的 master 分支中訪問 origin/master 的最新改動了,所以她應該可以成功推送最后的合并結果到服務器上(假設 John 此時沒再推送新數(shù)據(jù)上來):
$ git push origin master
...
To jessica@githost:simplegit.git
72bbc59..8059c15 master -> master
至此,每個開發(fā)者都提交了若干次,且成功合并了對方的工作成果,最新的提交歷史如圖 5-10 所示:
圖 5-10. Jessica 推送數(shù)據(jù)后的提交歷史
以上就是最簡單的協(xié)作方式之一:先在自己的特性分支中工作一段時間,完成后合并到自己的 master 分支;然后下載合并 origin/master 上的更新(如果有的話),再推回遠程服務器。一般的協(xié)作流程如圖 5-11 所示:
圖 5-11. 多用戶共享倉庫協(xié)作方式的一般工作流程時序
現(xiàn)在我們來看更大一點規(guī)模的私有團隊協(xié)作。如果有幾個小組分頭負責若干特性的開發(fā)和集成,那他們之間的協(xié)作過程是怎樣的。
假設 John 和 Jessica 一起負責開發(fā)某項特性 A,而同時 Jessica 和 Josie 一起負責開發(fā)另一項功能 B。公司使用典型的集成管理員式工作流,每個組都有一名管理員負責集成本組代碼,及更新項目主倉庫的 master 分支。所有開發(fā)都在代表小組的分支上進行。
讓我們跟隨 Jessica 的視角看看她的工作流程。她參與開發(fā)兩項特性,同時和不同小組的開發(fā)者一起協(xié)作??寺∩杀镜貍}庫后,她打算先著手開發(fā)特性 A。于是創(chuàng)建了新的 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 沒有權限推送數(shù)據(jù)到主倉庫的 master 分支(只有集成管理員有此權限),所以只能將此分支推上去同 John 共享協(xié)作:
$ git push origin featureA
...
To jessica@githost:simplegit.git
* [new branch] featureA -> featureA
Jessica 發(fā)郵件給 John 讓他上來看看 featureA 分支上的進展。在等待他的反饋之前,Jessica 決定繼續(xù)工作,和 Josie 一起開發(fā) featureB 上的特性 B。當然,先創(chuàng)建此分支,分叉點以服務器上的 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(-)
現(xiàn)在 Jessica 的更新歷史如圖 5-12 所示:
圖 5-12. Jessica 的更新歷史
Jessica 正準備推送自己的進展上去,卻收到 Josie 的來信,說是她已經(jīng)將自己的工作推到服務器上的 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 發(fā)郵件給 Jessica 告訴她,他看了之后作了些修改,已經(jīng)推回服務器 featureA 分支,請她過目下。于是 Jessica 運行 git fetch 下載最新數(shù)據(jù):
$ git fetch origin
...
From jessica@githost:simplegit
3300904..aad881d featureA -> origin/featureA
接下來便可以用 git log 查看更新了些什么:
$ git log origin/featureA ^featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
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 774b3ed] small tweak
1 files changed, 1 insertions(+), 1 deletions(-)
$ git push origin featureA
...
To jessica@githost:simplegit.git
3300904..774b3ed featureA -> featureA
現(xiàn)在的 Jessica 提交歷史如圖 5-13 所示:
圖 5-13. 在特性分支中提交更新后的提交歷史
現(xiàn)在,Jessica,Josie 和 John 通知集成管理員服務器上的 featureA 及 featureBee 分支已經(jīng)準備好,可以并入主線了。在管理員完成集成工作后,主分支上便多出一個新的合并提交(5399e),用 fetch 命令更新到本地后,提交歷史如圖 5-14 所示:
圖 5-14. 合并特性分支后的 Jessica 提交歷史
許多開發(fā)小組改用 Git 就是因為它允許多個小組間并行工作,而在稍后恰當時機再行合并。通過共享遠程分支的方式,無需干擾整體項目代碼便可以開展工作,因此使用 Git 的小型團隊間協(xié)作可以變得非常靈活自由。以上工作流程的時序如圖 5-15 所示:
圖 5-15. 團隊間協(xié)作工作流程基本時序
上面說的是私有項目協(xié)作,但要給公開項目作貢獻,情況就有些不同了。因為你沒有直接更新主倉庫分支的權限,得尋求其它方式把工作成果交給項目維護人。下面會介紹兩種方法,第一種使用 git 托管服務商提供的倉庫復制功能,一般稱作 fork,比如 repo.or.cz 和 GitHub 都支持這樣的操作,而且許多項目管理員都希望大家使用這樣的方式。另一種方法是通過電子郵件寄送文件補丁。
但不管哪種方式,起先我們總需要克隆原始倉庫,而后創(chuàng)建特性分支開展工作?;竟ぷ髁鞒倘缦拢?/p>
$ git clone (url)
$ cd project
$ git checkout -b featureA
$ (work)
$ git commit
$ (work)
$ git commit
你可能想到用 rebase -i 將所有更新先變作單個提交,又或者想重新安排提交之間的差異補丁,以方便項目維護者審閱 -- 有關交互式衍合操作的細節(jié)見第六章。
在完成了特性分支開發(fā),提交給項目維護者之前,先到原始項目的頁面上點擊“Fork”按鈕,創(chuàng)建一個自己可寫的公共倉庫(譯注:即下面的 url 部分,參照后續(xù)的例子,應該是 git://githost/simplegit.git)。然后將此倉庫添加為本地的第二個遠端倉庫,姑且稱為 myfork:
$ git remote add myfork (url)
你需要將本地更新推送到這個倉庫。要是將遠端 master 合并到本地再推回去,還不如把整個特性分支推上去來得干脆直接。而且,假若項目維護者未采納你的貢獻的話(不管是直接合并還是 cherry pick),都不用回退(rewind)自己的 master 分支。但若維護者合并或 cherry-pick 了你的工作,最后總還可以從他們的更新中同步這些代碼。好吧,現(xiàn)在先把 featureA 分支整個推上去:
$ git push myfork featureA
然后通知項目管理員,讓他來抓取你的代碼。通常我們把這件事叫做 pull request??梢灾苯佑?GitHub 等網(wǎng)站提供的 “pull request” 按鈕自動發(fā)送請求通知;或手工把 git request-pull 命令輸出結果電郵給項目管理員。
request-pull 命令接受兩個參數(shù),第一個是本地特性分支開始前的原始分支,第二個是請求對方來抓取的 Git 倉庫 URL(譯注:即下面 myfork 所指的,自己可寫的公共倉庫)。比如現(xiàn)在Jessica 準備要給 John 發(fā)一個 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(-)
輸出的內容可以直接發(fā)郵件給管理者,他們就會明白這是從哪次提交開始旁支出去的,該到哪里去抓取新的代碼,以及新的代碼增加了哪些功能等等。
像這樣隨時保持自己的 master 分支和官方 origin/master 同步,并將自己的工作限制在特性分支上的做法,既方便又靈活,采納和丟棄都輕而易舉。就算原始主干發(fā)生變化,我們也能重新衍合提供新的補丁。比如現(xiàn)在要開始第二項特性的開發(fā),不要在原來已推送的特性分支上繼續(xù),還是按原始 master 開始:
$ git checkout -b featureB origin/master
$ (work)
$ git commit
$ git push myfork featureB
$ (email maintainer)
$ git fetch origin
現(xiàn)在,A、B 兩個特性分支各不相擾,如同竹筒里的兩顆豆子,隊列中的兩個補丁,你隨時都可以分別從頭寫過,或者衍合,或者修改,而不用擔心特性代碼的交叉混雜。如圖 5-16 所示:
圖 5-16. featureB 以后的提交歷史
假設項目管理員接納了許多別人提交的補丁后,準備要采納你提交的第一個分支,卻發(fā)現(xiàn)因為代碼基準不一致,合并工作無法正確干凈地完成。這就需要你再次衍合到最新的 origin/master,解決相關沖突,然后重新提交你的修改:
$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA
自然,這會重寫提交歷史,如圖 5-17 所示:
圖 5-17. featureA 重新衍合后的提交歷史
注意,此時推送分支必須使用 -f 選項(譯注:表示 force,不作檢查強制重寫)替換遠程已有的 featureA 分支,因為新的 commit 并非原來的后續(xù)更新。當然你也可以直接推送到另一個新的分支上去,比如稱作 featureAv2。
再考慮另一種情形:管理員看過第二個分支后覺得思路新穎,但想請你改下具體實現(xiàn)。我們只需以當前 origin/master 分支為基準,開始一個新的特性分支 featureBv2,然后把原來的 featureB 的更新拿過來,解決沖突,按要求重新實現(xiàn)部分代碼,然后將此特性分支推送上去:
$ git checkout -b featureBv2 origin/master
$ git merge --no-commit --squash featureB
$ (change implementation)
$ git commit
$ git push myfork featureBv2
這里的 --squash 選項將目標分支上的所有更改全拿來應用到當前分支上,而 --no-commit 選項告訴 Git 此時無需自動生成和記錄(合并)提交。這樣,你就可以在原來代碼基礎上,繼續(xù)工作,直到最后一起提交。
好了,現(xiàn)在可以請管理員抓取 featureBv2 上的最新代碼了,如圖 5-18 所示:
圖 5-18. featureBv2 之后的提交歷史
許多大型項目都會立有一套自己的接受補丁流程,你應該注意下其中細節(jié)。但多數(shù)項目都允許通過開發(fā)者郵件列表接受補丁,現(xiàn)在我們來看具體例子。
整個工作流程類似上面的情形:為每個補丁創(chuàng)建獨立的特性分支,而不同之處在于如何提交這些補丁。不需要創(chuàng)建自己可寫的公共倉庫,也不用將自己的更新推送到自己的服務器,你只需將每次提交的差異內容以電子郵件的方式依次發(fā)送到郵件列表中即可。
$ git checkout -b topicA
$ (work)
$ git commit
$ (work)
$ git commit
如此一番后,有了兩個提交要發(fā)到郵件列表。我們可以用 git format-patch 命令來生成 mbox 格式的文件然后作為附件發(fā)送。每個提交都會封裝為一個 .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 命令依次創(chuàng)建補丁文件,并輸出文件名。上面的 -M 選項允許 Git 檢查是否有對文件重命名的提交。我們來看看補丁文件的內容:
$ cat 0001-add-limit-to-log-function.patch
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
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 部分。這樣,其它開發(fā)者能閱讀,但在采納補丁時不會將此合并進來。
你可以用郵件客戶端軟件發(fā)送這些補丁文件,也可以直接在命令行發(fā)送。有些所謂智能的郵件客戶端軟件會自作主張幫你調整格式,所以粘貼補丁到郵件正文時,有可能會丟失換行符和若干空格。Git 提供了一個通過 IMAP 發(fā)送補丁文件的工具。接下來我會演示如何通過 Gmail 的 IMAP 服務器發(fā)送。另外,在 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 命令把補丁作為郵件依次發(fā)送到指定的 IMAP 服務器上的文件夾中(譯注:這里就是 Gmail 的 [Gmail]/Drafts 文件夾。但如果你的語言設置不是英文,此處的文件夾 Drafts 字樣會變?yōu)閷恼Z言。):
$ cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done
然后,你應該去你到草稿箱去更改你要發(fā)送的補丁的收件人信息,以及需要抄送的人,然后發(fā)送它。
您也可以通過SMTP服務器發(fā)送補丁。和上面一樣,你可以通過git config命令單獨設置每個參數(shù),也可以在你的~/.gitconfig文件中的sendemail節(jié)點手動添加它們。
[sendemail]
smtpencryption = tls
smtpserver = smtp.gmail.com
smtpuser = user@gmail.com
smtpserverport = 587
配置完成后,您可以使用git send-email來發(fā)送你的補?。?/p>
$ 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 <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
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 會根據(jù)每個補丁依次輸出類似下面的日志:
(mbox) Adding cc: Jessica Smith <jessica@example.com> from
\line 'From: Jessica Smith <jessica@example.com>'
OK. Log says:
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith <jessica@example.com>
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: <y>
References: <y>
Result: OK
本節(jié)主要介紹了常見 Git 項目協(xié)作的工作流程,還有一些幫助處理這些工作的命令和工具。接下來我們要看看如何維護 Git 項目,并成為一個合格的項目管理員,或是集成經(jīng)理.
更多建議: