除了如何有效地參與一個項(xiàng)目的貢獻(xiàn)之外,你可能也需要了解如何維護(hù)項(xiàng)目。這包含接受并應(yīng)用別人使用 format-patch
生成并通過電子郵件發(fā)送過來的補(bǔ)丁,或?qū)?xiàng)目添加的遠(yuǎn)程版本庫分支中的更改進(jìn)行整合。但無論是管理版本庫,還是幫忙驗(yàn)證、審核收到的補(bǔ)丁,都需要同其他貢獻(xiàn)者約定某種長期可持續(xù)的工作方式。
如果你想向項(xiàng)目中整合一些新東西,最好將這些嘗試局限在特性分支——一種通常用來嘗試新東西的臨時分支中。這樣便于單獨(dú)調(diào)整補(bǔ)丁,如果遇到無法正常工作的情況,可以先不用管,等到有時間的時候再來處理。如果你基于你所嘗試進(jìn)行工作的特性為分支創(chuàng)建一個簡單的名字,比如 ruby_client
或者具有類似描述性的其他名字,這樣即使你必須暫時拋棄它,以后回來時也不會忘記。項(xiàng)目的維護(hù)者一般還會為這些分支附帶命名空間,比如 sc/ruby_client
(其中 sc
是貢獻(xiàn)該項(xiàng)工作的人名稱的簡寫)。你應(yīng)該記得,可以使用如下方式基于 master 分支建立特性分支:
$ git branch sc/ruby_client master
或者如果你同時想立刻切換到新分支上的話,可以使用 checkout -b
選項(xiàng):
$ git checkout -b sc/ruby_client master
現(xiàn)在你已經(jīng)準(zhǔn)備好將別人貢獻(xiàn)的工作加入到這個特性分支,并考慮是否將其合并到長期分支中去了。
如果你通過電子郵件收到了一個需要整合進(jìn)入項(xiàng)目的補(bǔ)丁,你需要將其應(yīng)用到特性分支中進(jìn)行評估。有兩種應(yīng)用該種補(bǔ)丁的方法:使用 git apply
,或者使用 git am
。
apply
命令應(yīng)用補(bǔ)丁如果你收到了一個使用 git diff
或 Unix diff
命令(不推薦使用這種方式,具體見下一節(jié))創(chuàng)建的補(bǔ)丁,可以使用 git apply
命令來應(yīng)用。假設(shè)你將補(bǔ)丁保存在了 /tmp/patch-ruby-client.patch
中,可以這樣應(yīng)用補(bǔ)?。?/p>
$ git apply /tmp/patch-ruby-client.patch
這會修改工作目錄中的文件。它與運(yùn)行 patch -p1
命令來應(yīng)用補(bǔ)丁幾乎是等效的,但是這種方式更加嚴(yán)格,相對于 patch 來說,它能夠接受的模糊匹配更少。它也能夠處理 git diff
格式文件所描述的文件添加、刪除和重命名操作,而 patch
則不會。最后,git apply
命令采用了一種“全部應(yīng)用,否則就全部撤銷(apply all or abort all)”的模型,即補(bǔ)丁只有全部內(nèi)容都被應(yīng)用和完全不被應(yīng)用兩個狀態(tài),而 patch
可能會導(dǎo)致補(bǔ)丁文件被部分應(yīng)用,最后使你的工作目錄保持在一個比較奇怪的狀態(tài)。總體來看,git apply
命令要比 patch
謹(jǐn)慎得多。并且,它不會為你創(chuàng)建提交——在運(yùn)行之后,你需要手動暫存并提交補(bǔ)丁所引入的更改。
在實(shí)際應(yīng)用補(bǔ)丁前,你還可以使用 git apply 來檢查補(bǔ)丁是否可以順利應(yīng)用——即對補(bǔ)丁運(yùn)行 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
如果沒有產(chǎn)生輸出,則該補(bǔ)丁可以順利應(yīng)用。如果檢查失敗了,該命令還會以一個非零的狀態(tài)退出,所以需要時你也可以在腳本中使用它。
am
命令應(yīng)用補(bǔ)丁如果補(bǔ)丁的貢獻(xiàn)者也是一個 Git 用戶,并且其能熟練使用 format-patch
命令來生成補(bǔ)丁,這樣的話你的工作會變得更加輕松,因?yàn)檫@種補(bǔ)丁中包含了作者信息和提交信息供你參考。如果可能的話,請鼓勵貢獻(xiàn)者使用 format-patch
而不是 diff
來為你生成補(bǔ)丁。而只有對老式的補(bǔ)丁,你才必須使用 git apply
命令。
要應(yīng)用一個由 format-patch
命令生成的補(bǔ)丁,你應(yīng)該使用 git am
命令。從技術(shù)的角度看,git am
是為了讀取 mbox 文件而構(gòu)建的,mbox 是一種用來在單個文本文件中存儲一個或多個電子郵件消息的簡單純文本格式。其大致格式如下所示:
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
這其實(shí)就是你前面看到的 format-patch 命令輸出的開始幾行。而同時它也是有效的 mbox 電子郵件格式。如果有人使用 git send-email 命令將補(bǔ)丁以電子郵件的形式發(fā)送給你,你便可以將它下載為 mbox 格式的文件,之后將 git am 命令指向該文件,它會應(yīng)用其中包含的所有補(bǔ)丁。如果你所使用的郵件客戶端能夠同時將多封郵件保存為 mbox 格式的文件,你甚至能夠?qū)⒁幌盗醒a(bǔ)丁打包為單個 mbox 文件,并利用 git am
命令將它們一次性全部應(yīng)用。
然而,如果貢獻(xiàn)者將 format-patch
生成的補(bǔ)丁文件上傳到類似 Request Ticket 的任務(wù)處理系統(tǒng),你可以先將其保存到本地,之后通過 git am
來應(yīng)用補(bǔ)?。?/p>
$ git am 0001-limit-log-function.patch
Applying: add limit to log function
你會看到補(bǔ)丁被順利地應(yīng)用,并且為你自動創(chuàng)建了一個新的提交。其中的作者信息來自于電子郵件頭部的 From
和 Date
字段,提交消息則取自 Subject
和郵件正文中補(bǔ)丁之前的內(nèi)容。比如,應(yīng)用上面那個 mbox 示例后生成的提交是這樣的:
$ git log --pretty=fuller -1
commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0
Author: Jessica Smith <jessica@example.com>
AuthorDate: Sun Apr 6 10:17:23 2008 -0700
Commit: Scott Chacon <schacon@gmail.com>
CommitDate: Thu Apr 9 09:19:06 2009 -0700
add limit to log function
Limit log functionality to the first 20
其中 Commit
信息表示的是應(yīng)用補(bǔ)丁的人和應(yīng)用補(bǔ)丁的時間。Author
信息則表示補(bǔ)丁的原作者和原本的創(chuàng)建時間。
但是,有時候無法順利地應(yīng)用補(bǔ)丁。這也許是因?yàn)槟愕闹鞣种Ш蛣?chuàng)建補(bǔ)丁的分支相差較多,也有可能是因?yàn)檫@個補(bǔ)丁依賴于其他你尚未應(yīng)用的補(bǔ)丁。這種情況下,git am
進(jìn)程將會報錯并且詢問你要做什么:
$ 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".
該命令將會在所有出現(xiàn)問題的文件內(nèi)加入沖突標(biāo)記,就和發(fā)生沖突的合并或變基操作一樣。而你解決問題的手段很大程度上也是一樣的——即手動編輯那些文件來解決沖突,暫存新的文件,之后運(yùn)行 git am --resolved
繼續(xù)應(yīng)用下一個補(bǔ)?。?/p>
$ (fix the file)
$ git add ticgit.gemspec
$ git am --resolved
Applying: seeing if this helps the gem
如果你希望 Git 能夠嘗試以更加智能的方式解決沖突,你可以對其傳遞 -3
選項(xiàng)來使 Git 嘗試進(jìn)行三方合并。該選項(xiàng)默認(rèn)并沒有打開,因?yàn)槿绻糜趧?chuàng)建補(bǔ)丁的提交并不在你的版本庫內(nèi)的話,這樣做是沒有用處的。而如果你確實(shí)有那個提交的話——比如補(bǔ)丁是基于某個公共提交的——那么通常 -3
選項(xiàng)對于應(yīng)用有沖突的補(bǔ)丁是更加明智的選擇。
$ 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.
比如上面這種情況,我在之前已經(jīng)應(yīng)用過同樣的補(bǔ)丁。如果沒有 -3
選項(xiàng)的話,這看起來就像是存在一個沖突。
如果你正在利用一個 mbox 文件應(yīng)用多個補(bǔ)丁,也可以在交互模式下運(yùn)行 am
命令,這樣在每個補(bǔ)丁之前,它會停住詢問你是否要應(yīng)用該補(bǔ)?。?/p>
$ 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
這在你保存的補(bǔ)丁較多時很好用,因?yàn)槟憧梢栽趹?yīng)用之前查看忘掉內(nèi)容的補(bǔ)丁,并且跳過已經(jīng)應(yīng)用過的補(bǔ)丁。
當(dāng)與你的特性相關(guān)的所有補(bǔ)丁都被應(yīng)用并提交到分支中之后,你就可以選擇是否以及如何將其整合到更長期的分支中去了。
如果你的貢獻(xiàn)者建立了自己的版本庫,并且向其中推送了若干修改,之后將版本庫的 URL 和包含更改的遠(yuǎn)程分支發(fā)送給你,那么你可以將其添加為一個遠(yuǎn)程分支,并且在本地進(jìn)行合并。
比如 Jessica 向你發(fā)送了一封電子郵件,內(nèi)容是在她的版本庫中的 ruby-client
分支中有一個很不錯的新功能,為了測試該功能,你可以將其添加為一個遠(yuǎn)程分支,并在本地檢出:
$ git remote add jessica git://github.com/jessica/myproject.git
$ git fetch jessica
$ git checkout -b rubyclient jessica/ruby-client
如果她再次發(fā)郵件說另一個分支中包含另一個優(yōu)秀功能,因?yàn)橹耙呀?jīng)設(shè)置好遠(yuǎn)程分支了,你就可以直接進(jìn)行抓取及檢出操作。
這對于與他人長期合作工作來說很有用。而對于提交補(bǔ)丁頻率較小的貢獻(xiàn)者,相對于每個人維護(hù)自己的服務(wù)器,不斷增刪遠(yuǎn)程分支的做法,使用電子郵件來接收可能會比較省時。況且你也不會想要加入數(shù)百個只提供一兩個補(bǔ)丁的遠(yuǎn)程分支。然而,腳本和托管服務(wù)在一定程度上可以簡化這些工作——這很大程度上依賴于你和你的貢獻(xiàn)者開發(fā)的方式。
這種方式的另一種優(yōu)點(diǎn)是你可以同時得到提交歷史。雖然代碼合并中可能會出現(xiàn)問題,但是你能獲知他人的工作是基于你的歷史中的具體哪一個位置;所以Git 會默認(rèn)進(jìn)行三方合并,不需要提供 -3
選項(xiàng),你也不需要擔(dān)心補(bǔ)丁是基于某個你無法訪問的提交生成的。
對于非持續(xù)性的合作,如果你依然想要以這種方式拉取數(shù)據(jù)的話,你可以對遠(yuǎn)程版本庫的 URL 調(diào)用 git pull
命令。這會執(zhí)行一個一次性的抓取,而不會將該 URL 存為遠(yuǎn)程引用:
$ git pull https://github.com/onetimeguy/project
From https://github.com/onetimeguy/project
* branch HEAD -> FETCH_HEAD
Merge made by recursive.
你已經(jīng)有了一個包含其他人貢獻(xiàn)的特性分支?,F(xiàn)在你可以決定如何處理它們了。本節(jié)回顧了若干命令,以便于你檢查若將其合并入主分支所引入的更改。
一般來說,你應(yīng)該對該分支中所有 master 分支尚未包含的提交進(jìn)行檢查。通過在分支名稱前加入 --not
選項(xiàng),你可以排除 master 分支中的提交。這和我們之前使用的 master..contrib
格式是一樣的。假設(shè)貢獻(xiàn)者向你發(fā)送了兩個補(bǔ)丁,為此你創(chuàng)建了一個名叫 contrib
的分支并在其上應(yīng)用補(bǔ)丁,你可以運(yùn)行:
$ git log contrib --not master
commit 5b6235bd297351589efc4d73316f0a68d484f118
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Oct 24 09:53:59 2008 -0700
seeing if this helps the gem
commit 7482e0d16d04bea79d0dba8988cc78df655f16a0
Author: Scott Chacon <schacon@gmail.com>
Date: Mon Oct 22 19:38:36 2008 -0700
updated the gemspec to hopefully work better
如果要查看每次提交所引入的具體修改,你應(yīng)該記得可以給 git log
命令傳遞 -p
選項(xiàng),這樣它會在每次提交后面附加對應(yīng)的差異(diff)。
而要查看將該特性分支與另一個分支合并的完整 diff,你可能需要使用一個有些奇怪的技巧來得到正確的結(jié)果。你可能會想到這種方式:
$ git diff master
這個命令會輸出一個 diff,但它可能并不是我們想要的。如果在你創(chuàng)建特性分支之后,master
分支向前移動了,你獲得的結(jié)果就會顯得有些不對。這是因?yàn)?Git 會直接將該特性分支與 master
分支的最新提交快照進(jìn)行比較。比如說你在 master
分支中向某個文件添加了一行內(nèi)容,那么直接比對最新快照的結(jié)果看上去就像是你在特性分支中將這一行刪除了。
如果 master
分支是你的特性分支的直接祖先,其實(shí)是沒有任何問題的;但是一旦兩個分支的歷史產(chǎn)生了分叉,上述比對產(chǎn)生的 diff 看上去就像是將特性分支中所有的新東西加入,并且將 master
分支所獨(dú)有的東西刪除。
而你真正想要檢查的東西,實(shí)際上僅僅是特性分支所添加的更改——也就是該分支與 master 分支合并所要引入的工作。要達(dá)到此目的,你需要讓 Git 對特性分支上最新的提交與該分支與 master 分支的首個公共祖先進(jìn)行比較。
從技術(shù)的角度講,你可以以手工的方式找出公共祖先,并對其顯式運(yùn)行 diff 命令:
$ git merge-base contrib master
36c7dba2c95e6bbb78dfa822519ecfec6e1ca649
$ git diff 36c7db
然而,這種做法比較麻煩,所以 Git 提供了一種比較便捷的方式:三點(diǎn)語法。對于 diff
命令來說,你可以通過把 ...
置于另一個分支名后來對該分支的最新提交與兩個分支的共同祖先進(jìn)行比較:
$ git diff master...contrib
該命令僅會顯示自當(dāng)前特性分支與 master 分支的共同祖先起,該分支中的工作。這個語法很有用,應(yīng)該牢記。
當(dāng)特性分支中所有的工作都已經(jīng)準(zhǔn)備好整合進(jìn)入更靠近主線的分支時,接下來的問題就是如何進(jìn)行整合了。此外,還有一個問題是,你想使用怎樣的總體工作流來維護(hù)你的項(xiàng)目?你的選擇有很多,我們會介紹其中的一部分。
一種非常簡單的工作流會直接將工作合并進(jìn)入 master
分支。在這種情況下,master
分支包含的代碼是基本穩(wěn)定的。當(dāng)你完成某個特性分支的工作,或?qū)徍送ㄟ^了其他人所貢獻(xiàn)的工作時,你會將其合并進(jìn)入 master 分支,之后將特性分支刪除,如此反復(fù)。如果我們的版本庫包含類似 Figure?5-20 的兩個名稱分別為 ruby_client
和 php_client
的分支,并且我們先合并 ruby_client
分支,之后合并 php_client
分支,那么提交歷史最后會變成 Figure?5-21 的樣子。
Figure 5-21. 合并特性分支之后。
這也許是最簡單的工作流了,但是當(dāng)項(xiàng)目更大,或更穩(wěn)定,你對自己所引入的工作更加在意時,它可能會帶來問題。
如果你的項(xiàng)目非常重要,你可能會使用兩階段合并循環(huán)。在這種情況下,你會維護(hù)兩個長期分支,分別是 master
和 develop
,master
分支只會在一個非常穩(wěn)定的版本發(fā)布時才會更新,而所有的新代碼會首先整合進(jìn)入 develop
分支。你定期將這兩個分支推送到公共版本庫中。每次需要合并新的特性分支時(Figure?5-22),你都應(yīng)該合并進(jìn)入 develop
分支(Figure?5-23);當(dāng)打標(biāo)簽發(fā)布的時候,你會將 master
分支快進(jìn)到已經(jīng)穩(wěn)定的 develop
分支(Figure?5-24)。
Figure 5-23. 合并特性分支后。
這樣會拉取和 e43a6
相同的更改,但是因?yàn)閼?yīng)用的日期不同,你會得到一個新的提交 SHA-1 值?,F(xiàn)在你的歷史會變成這樣:
更多建議: