公開的小型項目
上面說的是私有項目協作,但要給公開項目作貢獻,情況就有些不同了。因爲你沒有直接更新主倉庫分支的權限,得尋求其它方式把工作成果交給項目維護人。下面會介紹兩種方法,第一種使用 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 所示:
圖 5-16. featureB 以後的提交歷史
假設項目管理員接納了許多別人提交的補丁後,準備要採納你提交的第一個分支,卻發現因爲代碼基準不一致,合併工作無法正確乾淨地完成。這就需要你再次衍合到最新的 origin/master
,解決相關衝突,然後重新提交你的修改:
$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA
自然,這會重寫提交歷史,如圖 5-17 所示:
圖 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
所示:
圖 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 <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
部分。這樣,其它開發者能閱讀,但在採納補丁時不會將此合併進來。
你可以用郵件客戶端軟件發送這些補丁文件,也可以直接在命令行發送。有些所謂智能的郵件客戶端軟件會自作主張幫你調整格式,所以粘貼補丁到郵件正文時,有可能會丟失換行符和若干空格。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 <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 會根據每個補丁依次輸出類似下面的日誌:
(mbox) Adding cc: Jessica Smith <jessica@example.com> from
\line 'From: Jessica Smith <[email protected]>'
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
最後,到 Gmail 上打開 Drafts 文件夾,編輯這些郵件,修改收件人地址爲郵件列表地址,另外給要抄送的人也加到 Cc 列表中,最後發送。