為項目作貢獻

2018-07-10 17:17 更新

為項目作貢獻

接下來,我們來學(xué)習(xí)一下作為項目貢獻者,會有哪些常見的工作模式。

不過要說清楚整個協(xié)作過程真的很難,Git 如此靈活,人們的協(xié)作方式便可以各式各樣,沒有固定不變的范式可循,而每個項目的具體情況又多少會有些不同,比如說參與者的規(guī)模,所選擇的工作流程,每個人的提交權(quán)限,以及 Git 以外貢獻等等,都會影響到具體操作的細節(jié)。

首當(dāng)其沖的是參與者規(guī)模。項目中有多少開發(fā)者是經(jīng)常提交代碼的?經(jīng)常又是多久呢?大多數(shù)兩至三人的小團隊,一天大約只有幾次提交,如果不是什么熱門項目的話就更少了??梢窃诖蠊纠铮蛘叽箜椖恐?,參與者可以多到上千,每天都會有十幾個上百個補丁提交上來。這種差異帶來的影響是顯著的,越是多的人參與進來,就越難保證每次合并正確無誤。你正在工作的代碼,可能會因為合并進來其他人的更新而變得過時,甚至受創(chuàng)無法運行。而已經(jīng)提交上去的更新,也可能在等著審核合并的過程中變得過時。那么,我們該怎樣做才能確保代碼是最新的,提交的補丁也是可用的呢?

接下來便是項目所采用的工作流。是集中式的,每個開發(fā)者都具有等同的寫權(quán)限?項目是否有專人負責(zé)檢查所有補???是不是所有補丁都做過同行復(fù)閱(peer-review)再通過審核的?你是否參與審核過程?如果使用副官系統(tǒng),那你是不是限定于只能向此副官提交?

還有你的提交權(quán)限。有或沒有向主項目提交更新的權(quán)限,結(jié)果完全不同,直接決定最終采用怎樣的工作流。如果不能直接提交更新,那該如何貢獻自己的代碼呢?是不是該有個什么策略?你每次貢獻代碼會有多少量?提交頻率呢?

所有以上這些問題都會或多或少影響到最終采用的工作流。接下來,我會在一系列由簡入繁的具體用例中,逐一闡述。此后在實踐時,應(yīng)該可以借鑒這里的例子,略作調(diào)整,以滿足實際需要構(gòu)建自己的工作流。

提交指南

開始分析特定用例之前,先來了解下如何撰寫提交說明。一份好的提交指南可以幫助協(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ā)者。

接下來,請將每次提交限定于完成一次邏輯功能。并且可能的話,適當(dāng)?shù)胤纸鉃槎啻涡「?,以便每次小型提交都更易于理解。請不要在周末窮追猛打一次性解決五個問題,而最后拖到周一再提交。就算是這樣也請盡可能利用暫存區(qū)域,將之前的改動分解為每次修復(fù)一個問題,再分別提交和加注說明。如果針對兩個問題改動的是同一個文件,可以試試看 git add --patch 的方式將部分內(nèi)容置入暫存區(qū)域(我們會在第六章再詳細介紹)。無論是五次小提交還是混雜在一起的大提交,最終分支末端的項目快照應(yīng)該還是一樣的,但分解開來之后,更便于其他開發(fā)者復(fù)閱。這么做也方便自己將來取消某個特定問題的修復(fù)。我們將在第六章介紹一些重寫提交歷史,同暫存區(qū)域交互的技巧和工具,以便最終得到一個干凈有意義,且易于理解的提交歷史。

最后需要謹記的是提交說明的撰寫。寫得好可以讓大家協(xié)作起來更輕松。一般來說,提交說明最好限制在一行以內(nèi),50 個字符以下,簡明扼要地描述更新內(nèi)容,空開一行后,再展開詳細注解。Git 項目本身需要開發(fā)者撰寫詳盡注解,包括本次修訂的因由,以及前后不同實現(xiàn)之間的比較,我們也該借鑒這種做法。另外,提交說明應(yīng)該用祈使現(xiàn)在式語態(tài),比如,不要說成 “I added tests for” 或 “Adding tests for” 而應(yīng)該用 “Add tests for”。 下面是來自 tpope.net 的 Tim Pope 原創(chuàng)的提交說明格式模版,供參考:

本次更新的簡要描述(50 個字符以內(nèi))

如果必要,此處展開詳盡闡述。段落寬度限定在 72 個字符以內(nèi)。
某些情況下,第一行的簡要描述將用作郵件標題,其余部分作為郵件正文。
其間的空行是必要的,以區(qū)分兩者(當(dāng)然沒有正文另當(dāng)別論)。
如果并在一起,rebase 這樣的工具就可能會迷惑。

另起空行后,再進一步補充其他說明。

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

 - 一般以單個空格緊跟短劃線或者星號作為每項條目的起始符。每個條目間用一空行隔開。
   不過這里按自己項目的約定,可以略作變化。

如果你的提交說明都用這樣的格式來書寫,好多事情就可以變得十分簡單。Git 項目本身就是這樣要求的,我強烈建議你到 Git 項目倉庫下運行 git log --no-merges 看看,所有提交歷史的說明是怎樣撰寫的。(譯注:如果現(xiàn)在還沒有克隆 git 項目源代碼,是時候 git clone git://git.kernel.org/pub/scm/git/git.git 了。)

為簡單起見,在接下來的例子(及本書隨后的所有演示)中,我都不會用這種格式,而使用 -m 選項提交 git commit。不過請還是按照我之前講的做,別學(xué)我這里偷懶的方式。

私有的小型團隊

我們從最簡單的情況開始,一個私有項目,與你一起協(xié)作的還有另外一到兩位開發(fā)者。這里說私有,是指源代碼不公開,其他人無法訪問項目倉庫。而你和其他開發(fā)者則都具有推送數(shù)據(jù)到倉庫的權(quán)限。

這種情況下,你們可以用 Subversion 或其他集中式版本控制系統(tǒng)類似的工作流來協(xié)作。你仍然可以得到 Git 帶來的其他好處:離線提交,快速分支與合并等等,但工作流程還是差不多的。主要區(qū)別在于,合并操作發(fā)生在客戶端而非服務(wù)器上。 讓我們來看看,兩個開發(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 將她的工作推送到服務(wù)器上:

# 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 會在服務(wù)器端自動合并提交上來的更新,而 Git 則必須先在本地合并后才能推送。于是,John 不得不先把 Jessica 的更新拉下來:

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

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

圖 5-4. John 的倉庫歷史

雖然 John 下載了 Jessica 推送到服務(wù)器的最近更新(fbff5),但目前只是 origin/master 指針指向它,而當(dāng)前的本地分支 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 應(yīng)該再測試一下代碼是否仍然正常工作,然后將合并結(jié)果(72bbc)推送到服務(wù)器上:

$ 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 提交的合并結(jié)果,所以提交歷史如圖 5-7 所示:

圖 5-7. Jessica 的提交歷史

Jessica 想要先和服務(wù)器上的數(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 分支,最后再推送回服務(wù)器。當(dāng)然,得先切回主分支才能集成所有數(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 分支,誰先誰后都沒有關(guān)系,因為它們都在上游(upstream)(譯注:想像分叉的更新像是匯流成河的源頭,所以上游 upstream 是指最新的提交),所以無所謂先后順序,最終合并后的內(nèi)容快照都是一樣的,而僅是提交歷史看起來會有些先后差別。Jessica 選擇先合并 issue54

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

正如所見,沒有沖突發(fā)生,僅是一次簡單快進?,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 的最新改動了,所以她應(yīng)該可以成功推送最后的合并結(jié)果到服務(wù)器上(假設(shè) 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 上的更新(如果有的話),再推回遠程服務(wù)器。一般的協(xié)作流程如圖 5-11 所示:

圖 5-11. 多用戶共享倉庫協(xié)作方式的一般工作流程時序

私有團隊間協(xié)作

現(xiàn)在我們來看更大一點規(guī)模的私有團隊協(xié)作。如果有幾個小組分頭負責(zé)若干特性的開發(fā)和集成,那他們之間的協(xié)作過程是怎樣的。

假設(shè) John 和 Jessica 一起負責(zé)開發(fā)某項特性 A,而同時 Jessica 和 Josie 一起負責(zé)開發(fā)另一項功能 B。公司使用典型的集成管理員式工作流,每個組都有一名管理員負責(zé)集成本組代碼,及更新項目主倉庫的 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 分支提交到服務(wù)器。由于 Jessica 沒有權(quán)限推送數(shù)據(jù)到主倉庫的 master 分支(只有集成管理員有此權(quán)限),所以只能將此分支推上去同 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。當(dāng)然,先創(chuàng)建此分支,分叉點以服務(wù)器上的 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)將自己的工作推到服務(wù)器上的 featureBee 分支了。這樣,Jessica 就必須先將 Josie 的代碼合并到自己本地分支中,才能再一起推送回服務(wù)器。她用 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 分支到服務(wù)器上的 featureBee 分支上去。當(dāng)然,她可以使用冒號(:)格式指定目標分支:

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

我們稱此為refspec。更多有關(guān)于 Git refspec 的討論和使用方式會在第九章作詳細闡述。

接下來,John 發(fā)郵件給 Jessica 告訴她,他看了之后作了些修改,已經(jīng)推回服務(wù)器 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 稍做一番修整后同步到服務(wù)器:

$ 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 通知集成管理員服務(wù)器上的 featureA 及 featureBee 分支已經(jīng)準備好,可以并入主線了。在管理員完成集成工作后,主分支上便多出一個新的合并提交(5399e),用 fetch 命令更新到本地后,提交歷史如圖 5-14 所示:

圖 5-14. 合并特性分支后的 Jessica 提交歷史

許多開發(fā)小組改用 Git 就是因為它允許多個小組間并行工作,而在稍后恰當(dāng)時機再行合并。通過共享遠程分支的方式,無需干擾整體項目代碼便可以開展工作,因此使用 Git 的小型團隊間協(xié)作可以變得非常靈活自由。以上工作流程的時序如圖 5-15 所示:

圖 5-15. 團隊間協(xié)作工作流程基本時序

公開的小型項目

上面說的是私有項目協(xié)作,但要給公開項目作貢獻,情況就有些不同了。因為你沒有直接更新主倉庫分支的權(quán)限,得尋求其它方式把工作成果交給項目維護人。下面會介紹兩種方法,第一種使用 git 托管服務(wù)商提供的倉庫復(fù)制功能,一般稱作 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 將所有更新先變作單個提交,又或者想重新安排提交之間的差異補丁,以方便項目維護者審閱 -- 有關(guān)交互式衍合操作的細節(jié)見第六章。

在完成了特性分支開發(fā),提交給項目維護者之前,先到原始項目的頁面上點擊“Fork”按鈕,創(chuàng)建一個自己可寫的公共倉庫(譯注:即下面的 url 部分,參照后續(xù)的例子,應(yīng)該是 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 命令輸出結(jié)果電郵給項目管理員。

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

$ 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(-)

輸出的內(nèi)容可以直接發(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 兩個特性分支各不相擾,如同竹筒里的兩顆豆子,隊列中的兩個補丁,你隨時都可以分別從頭寫過,或者衍合,或者修改,而不用擔(dān)心特性代碼的交叉混雜。如圖 5-16 所示:

圖 5-16. featureB 以后的提交歷史

假設(shè)項目管理員接納了許多別人提交的補丁后,準備要采納你提交的第一個分支,卻發(fā)現(xiàn)因為代碼基準不一致,合并工作無法正確干凈地完成。這就需要你再次衍合到最新的 origin/master,解決相關(guān)沖突,然后重新提交你的修改:

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

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

圖 5-17. featureA 重新衍合后的提交歷史

注意,此時推送分支必須使用 -f 選項(譯注:表示 force,不作檢查強制重寫)替換遠程已有的 featureA分支,因為新的 commit 并非原來的后續(xù)更新。當(dāng)然你也可以直接推送到另一個新的分支上去,比如稱作 featureAv2。

再考慮另一種情形:管理員看過第二個分支后覺得思路新穎,但想請你改下具體實現(xiàn)。我們只需以當(dāng)前 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 選項將目標分支上的所有更改全拿來應(yīng)用到當(dāng)前分支上,而 --no-commit 選項告訴 Git 此時無需自動生成和記錄(合并)提交。這樣,你就可以在原來代碼基礎(chǔ)上,繼續(xù)工作,直到最后一起提交。

好了,現(xiàn)在可以請管理員抓取 featureBv2 上的最新代碼了,如圖 5-18 所示:

圖 5-18. featureBv2 之后的提交歷史

公開的大型項目

許多大型項目都會立有一套自己的接受補丁流程,你應(yīng)該注意下其中細節(jié)。但多數(shù)項目都允許通過開發(fā)者郵件列表接受補丁,現(xiàn)在我們來看具體例子。

整個工作流程類似上面的情形:為每個補丁創(chuàng)建獨立的特性分支,而不同之處在于如何提交這些補丁。不需要創(chuàng)建自己可寫的公共倉庫,也不用將自己的更新推送到自己的服務(wù)器,你只需將每次提交的差異內(nèi)容以電子郵件的方式依次發(fā)送到郵件列表中即可。

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

如此一番后,有了兩個提交要發(fā)到郵件列表。我們可以用 git format-patch 命令來生成 mbox 格式的文件然后作為附件發(fā)送。每個提交都會封裝為一個 .patch 后綴的 mbox 文件,但其中只包含一封郵件,郵件標題就是提交消息(譯注:額外有前綴,看例子),郵件內(nèi)容包含補丁正文和 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 檢查是否有對文件重命名的提交。我們來看看補丁文件的內(nèi)容:

$ 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ā)送。有些所謂智能的郵件客戶端軟件會自作主張幫你調(diào)整格式,所以粘貼補丁到郵件正文時,有可能會丟失換行符和若干空格。Git 提供了一個通過 IMAP 發(fā)送補丁文件的工具。接下來我會演示如何通過 Gmail 的 IMAP 服務(wù)器發(fā)送。另外,在 Git 源代碼中有個 Documentation/SubmittingPatches 文件,可以仔細讀讀,看看其它郵件程序的相關(guān)導(dǎo)引。

首先在 ~/.gitconfig 文件中配置 imap 項。每個選項都可用 git config 命令分別設(shè)置,當(dāng)然直接編輯文件添加以下內(nèi)容更便捷:

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

如果你的 IMAP 服務(wù)器沒有啟用 SSL,就無需配置最后那兩行,并且 host 應(yīng)該以 imap:// 開頭而不再是有 s的 imaps://。 保存配置文件后,就能用 git send-email 命令把補丁作為郵件依次發(fā)送到指定的 IMAP 服務(wù)器上的文件夾中(譯注:這里就是 Gmail 的 [Gmail]/Drafts 文件夾。但如果你的語言設(shè)置不是英文,此處的文件夾 Drafts 字樣會變?yōu)閷?yīng)的語言。):

$ 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

At this point, you should be able to go to your Drafts folder, change the To field to the mailing list you’re sending the patch to, possibly CC the maintainer or person responsible for that section, and send it off.

You can also send the patches through an SMTP server. As before, you can set each value separately with a series of git config commands, or you can add them manually in the sendemail section in your ~/.gitconfig file:

[sendemail]
  smtpencryption = tls
  smtpserver = smtp.gmail.com
  smtpuser = user@gmail.com
  smtpserverport = 587

After this is done, you can use git send-email to send your patches:

$ 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é)

本節(jié)主要介紹了常見 Git 項目協(xié)作的工作流程,還有一些幫助處理這些工作的命令和工具。接下來我們要看看如何維護 Git 項目,并成為一個合格的項目管理員,或是集成經(jīng)理。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號