Git
章節 ▾ 第二版

5.2 分散式 Git - 參與專案

參與專案

描述如何參與專案的主要困難在於有許多種做法。因為 Git 非常彈性,人們可以用許多方式協同工作,要描述您「應該」如何貢獻是很麻煩的 — 每個專案都有些許不同。一些相關的變數包括活躍貢獻者人數、選擇的工作流程、您的提交存取權,以及可能的外部貢獻方法。

第一個變數是活躍貢獻者人數 — 有多少使用者積極貢獻程式碼到此專案,以及頻率有多高?在許多情況下,您會看到兩三個開發人員每天提交幾次,對於較為休眠的專案,可能更少。對於較大的公司或專案,開發人員的數量可能達到數千人,每天會有數百或數千次的提交。這很重要,因為隨著開發人員越來越多,您會遇到更多問題,需要確保您的程式碼可以順利套用或輕鬆合併。您提交的變更可能會因為您工作期間或您的變更等待核准或套用時合併的工作,而被淘汰或嚴重損壞。您如何保持程式碼持續更新,並且您的提交有效?

下一個變數是專案使用的工作流程。是集中式的嗎?每個開發人員是否都具有主程式碼線的同等寫入權限?專案是否有維護者或整合管理員來檢查所有修補程式?所有修補程式是否都經過同儕審查和核准?您是否參與該流程?是否有副手系統,您是否必須先將工作提交給他們?

下一個變數是您的提交存取權。如果您具有專案的寫入權限,則參與專案所需的工作流程,與您沒有寫入權限時大不相同。如果您沒有寫入權限,專案偏好如何接受貢獻的工作?它甚至有政策嗎?您一次貢獻多少工作量?您貢獻的頻率有多高?

所有這些問題都可能會影響您如何有效地參與專案,以及哪些工作流程是您偏好或可用的。我們將在一系列的使用案例中涵蓋這些方面的內容,從簡單到複雜;您應該可以從這些範例中建構出您在實務上需要的特定工作流程。

提交指南

在我們開始探討具體的應用案例之前,這裡先簡短說明一下提交訊息(commit messages)。為建立提交訊息訂定良好的規範並遵守它,能讓使用 Git 以及與他人協作變得更加容易。Git 專案提供了一份文件,其中列出了一些關於建立提交訊息以提交修補程式的好建議——你可以在 Git 原始碼中的 Documentation/SubmittingPatches 檔案中閱讀它。

首先,你的提交不應包含任何空白錯誤。Git 提供了一種簡單的方法來檢查這個問題——在提交之前,執行 git diff --check,它會找出可能的空白錯誤並將它們列出來。

Output of `git diff --check`
圖 56. git diff --check 的輸出

如果你在提交之前執行該命令,你可以知道你是否即將提交可能會讓其他開發人員感到困擾的空白問題。

接下來,盡量使每個提交成為邏輯上獨立的變更集。如果可以,盡量讓你的變更易於消化——不要在整個週末針對五個不同的問題編碼,然後在星期一將它們全部作為一個巨大的提交來提交。即使你週末沒有提交,也要在星期一使用暫存區域,將你的工作分成至少每個問題一個提交,並且每個提交都帶有有用的訊息。如果某些變更修改了相同的文件,請嘗試使用 git add --patch 來部分暫存檔案(詳細內容請參閱 互動式暫存)。分支頂端的專案快照無論你做一個提交還是五個提交都是相同的,只要所有的變更在某個時間點都被添加進去即可,因此當其他開發人員必須審查你的變更時,盡量讓事情對他們來說更容易些。

這種方法也可以讓你之後在需要時更容易取出或還原其中一個變更集。重寫歷史 描述了許多有用的 Git 技巧,用於重寫歷史記錄和互動式暫存檔案——使用這些工具來幫助你在將工作發送給其他人之前,建立一個乾淨且易於理解的歷史記錄。

最後要記住的是提交訊息。養成建立高品質提交訊息的習慣,能讓使用 Git 和與他人協作變得更加容易。一般來說,你的訊息應該以一行不超過 50 個字元的簡潔描述變更集開始,後面接著一個空白行,然後是更詳細的說明。Git 專案要求更詳細的說明包含你變更的動機,並將其實現與先前的行為進行對比——這是一個很好的遵循準則。以祈使語氣編寫你的提交訊息:「修正錯誤」,而不是「已修正錯誤」或「正在修正錯誤」。以下是一個你可以遵循的範本,我們從 Tim Pope 最初撰寫的範本 稍作調整。

Capitalized, short (50 chars or less) summary

More detailed explanatory text, if necessary.  Wrap it to about 72
characters or so.  In some contexts, the first line is treated as the
subject of an email and the rest of the text as the body.  The blank
line separating the summary from the body is critical (unless you omit
the body entirely); tools like rebase will confuse you if you run the
two together.

Write your commit message in the imperative: "Fix bug" and not "Fixed bug"
or "Fixes bug."  This convention matches up with commit messages generated
by commands like git merge and git revert.

Further paragraphs come after blank lines.

- Bullet points are okay, too

- Typically a hyphen or asterisk is used for the bullet, followed by a
  single space, with blank lines in between, but conventions vary here

- Use a hanging indent

如果你的所有提交訊息都遵循這個模型,對你和與你協作的開發人員來說,事情會變得更加容易。Git 專案有格式良好的提交訊息——嘗試在那裡執行 git log --no-merges,看看格式良好的專案提交歷史記錄是什麼樣子。

注意
照我們說的做,而不是照我們做的。

為了簡潔起見,本書中的許多範例都沒有像這樣的格式良好的提交訊息;相反地,我們只使用了 git commit-m 選項。

總之,照我們說的做,而不是照我們做的。

私人小型團隊

你可能遇到的最簡單的設定是一個私人專案,只有一或兩個其他開發人員。「私人」在這個上下文中,意味著閉源——外部世界無法訪問。你和其他開發人員都對儲存庫具有推送權限。

在這種環境中,你可以遵循類似於使用 Subversion 或其他集中式系統時的工作流程。你仍然可以獲得離線提交以及更簡單的分支和合併等優勢,但工作流程可以非常相似;主要區別在於合併發生在用戶端,而不是在提交時發生在伺服器上。讓我們看看當兩個開發人員開始使用共享儲存庫協同工作時,情況可能會是什麼樣子。第一位開發人員 John,克隆儲存庫,進行變更,並在本地提交。為了縮短篇幅,這些範例中的協議訊息已替換為 …​

# John's Machine
$ git clone john@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'Remove invalid default value'
[master 738ee87] Remove invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

第二位開發人員 Jessica,也做了相同的事情——克隆儲存庫並提交變更

# Jessica's Machine
$ git clone jessica@githost:simplegit.git
Cloning into 'simplegit'...
...
$ 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

上述輸出的最後一行顯示了推送操作的有用回傳訊息。基本格式是 <oldref>..<newref> fromref → toref,其中 oldref 表示舊的參考,newref 表示新的參考,fromref 是正在推送的本地參考名稱,toref 是正在更新的遠端參考名稱。你將在下面的討論中看到類似的輸出,因此對其含義有一個基本的了解將有助於理解儲存庫的各種狀態。有關更多詳細資訊,請參閱 git-push 的文件。

繼續這個範例,不久之後,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 的上游變更,並將它們合併到他的本地儲存庫中,然後才能被允許推送。

第一步,John 提取 Jessica 的工作(這僅提取 Jessica 的上游工作,它尚未將其合併到 John 的工作中)

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

此時,John 的本地儲存庫看起來像這樣

John’s divergent history
圖 57. John 的分歧歷史

現在 John 可以將他提取的 Jessica 的工作合併到他自己的本地工作中

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

只要該本地合併順利進行,John 更新後的歷史記錄現在看起來像這樣

John’s repository after merging `origin/master`
圖 58. John 合併 origin/master 後的儲存庫

此時,John 可能想測試此新程式碼,以確保 Jessica 的工作沒有影響到他的任何程式碼,並且只要一切看起來都沒問題,他最終就可以將新的合併工作推送到伺服器

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

最後,John 的提交歷史記錄看起來像這樣

John’s history after pushing to the `origin` server
圖 59. John 推送到 origin 伺服器後的歷史記錄

與此同時,Jessica 建立了一個名為 issue54 的新主題分支,並對該分支進行了三次提交。她尚未提取 John 的變更,因此她的提交歷史記錄看起來像這樣

Jessica’s topic branch
圖 60. Jessica 的主題分支

突然間,Jessica 得知 John 已將一些新工作推送到伺服器,她想看看,因此她可以使用以下命令從伺服器提取她尚未擁有的所有新內容

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

這會提取 John 同時推送的所有工作。Jessica 的歷史記錄現在看起來像這樣

Jessica’s history after fetching John’s changes
圖 61. Jessica 提取 John 的變更後的歷史記錄

Jessica 認為她的主題分支已準備就緒,但她想知道她必須將 John 的哪些提取工作合併到她的工作中才能推送。她執行 git log 來找出答案

$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 16:01:27 2009 -0700

   Remove invalid default value

issue54..origin/master 語法是一個日誌篩選器,它會要求 Git 僅顯示後者分支(在本例中為 origin/master)上的提交,而這些提交不在第一個分支(在本例中為 issue54)上。我們將在 提交範圍 中詳細介紹此語法。

從上面的輸出中,我們可以看到 John 有一個 Jessica 尚未合併到她的本地工作的單一提交。如果她合併 origin/master,則該單一提交將修改她的本地工作。

現在,Jessica 可以將她的主題工作合併到她的 master 分支,將 John 的工作(origin/master)合併到她的 master 分支,然後再次推回伺服器。

首先(在她 issue54 主題分支上提交了所有工作後),Jessica 切換回她的 master 分支,準備整合所有這些工作

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

Jessica 可以先合併 origin/masterissue54——它們都是上游分支,因此順序無關緊要。無論她選擇哪個順序,最終的快照應該都是相同的;只有歷史記錄會有所不同。她選擇先合併 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 the 'recursive' strategy.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

一切都順利合併,Jessica 的歷史記錄現在看起來像這樣

Jessica’s history after merging John’s changes
圖 62. Jessica 合併 John 的變更後的歷史記錄

現在可以從 Jessica 的 master 分支訪問 origin/master,因此她應該能夠成功推送(假設 John 同時沒有推送更多變更)

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

每位開發人員都提交了幾次並成功合併了彼此的工作。

Jessica’s history after pushing all changes back to the server
圖 63. Jessica 將所有變更推回伺服器後的歷史記錄

這是一個最簡單的工作流程之一。你會工作一段時間(通常在主題分支中),並在準備好整合時將該工作合併到你的 master 分支中。當你想共享該工作時,如果 origin/master 已變更,你會從 origin/master 提取並合併你的 master,最後推送到伺服器上的 master 分支。一般順序如下所示

General sequence of events for a simple multiple-developer Git workflow
圖 64. 簡單的多開發人員 Git 工作流程的一般事件順序

私人管理團隊

在接下來的場景中,你將了解較大型私人群體中的貢獻者角色。你將學習如何在小組協作處理功能後,由另一方整合這些基於團隊的貢獻的環境中工作。

假設 John 和 Jessica 正在一起處理一個功能(稱之為「featureA」),而 Jessica 和第三位開發人員 Josie 正在處理第二個功能(例如,「featureB」)。在這種情況下,公司正在使用一種整合管理者工作流程,其中個別小組的工作僅由某些工程師整合,並且主儲存庫的 master 分支只能由這些工程師更新。在這種情況下,所有工作都是在基於團隊的分支中完成,稍後由整合人員匯總在一起。

讓我們跟隨 Jessica 的工作流程,因為她在這種環境中與兩位不同的開發人員平行協作處理她的兩個功能。假設她已經克隆了她的儲存庫,她決定先處理 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 -u origin featureA
...
To jessica@githost:simplegit.git
 * [new branch]      featureA -> featureA

Jessica 電子郵件通知 John,她已將一些工作推送到名為 featureA 的分支,他現在可以查看。在等待 John 回饋的同時,Jessica 決定開始與 Josie 合作處理 featureB。首先,她從伺服器的 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 'Make ls-tree function recursive'
[featureB e5b0fdc] Make 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 的儲存庫現在看起來像這樣。

Jessica’s initial commit history
圖 65. Jessica 的初始提交歷史記錄

她準備推送她的工作,但收到 Josie 的電子郵件,指出伺服器上已經推送了一個包含一些初始「featureB」工作的分支,名為 featureBee 分支。Jessica 需要將這些變更與自己的變更合併,才能將她的工作推送到伺服器。Jessica 首先使用 git fetch 擷取 Josie 的變更。

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

假設 Jessica 仍然在她已檢出的 featureB 分支上,她現在可以使用 git merge 將 Josie 的工作合併到該分支中。

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

此時,Jessica 想要將所有合併的「featureB」工作推回伺服器,但她不想簡單地推送她自己的 featureB 分支。相反地,由於 Josie 已經啟動了上游的 featureBee 分支,Jessica 想要推送到 *那個* 分支,她可以使用以下命令完成:

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

這稱為 *refspec*。有關 Git refspec 和您可以執行的各種操作的更詳細討論,請參閱 Refspec。另請注意 -u 標記;這是 --set-upstream 的縮寫,它會設定分支以便稍後更容易推送和提取。

突然間,Jessica 收到 John 的電子郵件,他告訴她他已將一些變更推送到他們正在協作的 featureA 分支,並要求 Jessica 查看。同樣地,Jessica 執行簡單的 git fetch 來擷取伺服器上的 *所有* 新內容,包括(當然)John 的最新工作。

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

Jessica 可以透過比較新擷取的 featureA 分支與她本地副本的相同分支的內容,來顯示 John 新工作的日誌。

$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 19:57:33 2009 -0700

    Increase log output to 30 from 25

如果 Jessica 喜歡她看到的內容,她可以使用以下命令將 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 可能想要對所有合併的內容進行一些小的變更,因此她可以自由地進行這些變更,將它們提交到她本地的 featureA 分支,然後將最終結果推回伺服器。

$ git commit -am 'Add small tweak to merged content'
[featureA 774b3ed] Add small tweak to merged content
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git push
...
To jessica@githost:simplegit.git
   3300904..774b3ed  featureA -> featureA

Jessica 的提交歷史現在看起來像這樣。

Jessica’s history after committing on a feature branch
圖 66. Jessica 在功能分支上提交後的歷史記錄

在某個時間點,Jessica、Josie 和 John 通知整合者,伺服器上的 featureAfeatureBee 分支已準備好整合到主線中。在整合者將這些分支合併到主線後,提取操作將會擷取新的合併提交,使歷史記錄看起來像這樣。

Jessica’s history after merging both her topic branches
圖 67. Jessica 在合併她所有主題分支後的歷史記錄

許多團隊轉向 Git 是因為它具有允許多個團隊並行工作的能力,並在流程後期合併不同的工作線。團隊中較小的子群組透過遠端分支進行協作,而不必讓整個團隊參與或阻礙的這種能力,是 Git 的巨大優勢。您在此處看到的流程順序如下:

Basic sequence of this managed-team workflow
圖 68. 這個受管理團隊工作流程的基本順序

分支公用專案

向公用專案貢獻有點不同。由於您沒有權限直接更新專案上的分支,您必須以其他方式將工作交付給維護者。第一個範例描述了如何在支援輕鬆分支的 Git 主機上進行貢獻。許多託管網站都支援此功能(包括 GitHub、BitBucket、repo.or.cz 等),而且許多專案維護者都期望這種貢獻風格。下一節將介紹偏好透過電子郵件接受貢獻修補程式的專案。

首先,您可能需要複製主儲存庫,為您計劃貢獻的修補程式或修補程式系列建立主題分支,並在那裡完成您的工作。順序大致如下:

$ git clone <url>
$ cd project
$ git checkout -b featureA
  ... work ...
$ git commit
  ... work ...
$ git commit
注意

您可能想要使用 rebase -i 將您的工作縮減為單次提交,或重新排列提交中的工作,以使維護者更容易檢閱修補程式 — 有關互動式變基的更多資訊,請參閱 重寫歷史記錄

當您的分支工作完成並準備好將其貢獻回維護者時,請前往原始專案頁面並按一下「分支」按鈕,建立您自己的專案可寫入分支。然後,您需要將此儲存庫 URL 作為本地儲存庫的新遠端加入;在此範例中,我們將其稱為 myfork

$ git remote add myfork <url>

然後,您需要將您的新工作推送到此儲存庫。最簡單的方法是將您正在處理的主題分支推送到您的分支儲存庫,而不是將該工作合併到您的 master 分支並推送。原因是如果您的工作未被接受或被挑選,您不必回溯您的 master 分支(Git cherry-pick 操作在 變基和挑選工作流程 中有更詳細的介紹)。如果維護者 mergerebasecherry-pick 您的工作,您最終會透過從他們的儲存庫提取來獲得它。

無論如何,您可以使用以下命令推送您的工作:

$ git push -u myfork featureA

一旦您的工作被推送到您的儲存庫分支,您需要通知原始專案的維護者,您有他們希望合併的工作。這通常稱為 *提取請求*,您通常透過網站產生此類請求 — GitHub 有其自己的「提取請求」機制,我們將在 GitHub 中介紹 — 或者您可以執行 git request-pull 命令並將後續輸出以電子郵件手動寄給專案維護者。

git request-pull 命令會接收您希望將主題分支提取到的基礎分支以及您希望他們從中提取的 Git 儲存庫 URL,並產生您要求提取的所有變更的摘要。例如,如果 Jessica 想要向 John 發送提取請求,並且她在她剛剛推送的主題分支上完成了兩次提交,她可以執行以下命令:

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

are available in the git repository at:

  https://githost/simplegit.git featureA

Jessica Smith (2):
      Add limit to log function
      Increase 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
$ git request-pull origin/master myfork
  ... email generated request pull to maintainer ...
$ git fetch origin

現在,您的每個主題都包含在一個筒倉中 — 類似於修補程式佇列 — 您可以重寫、變基和修改,而主題之間不會相互干擾或相互依賴,如下所示:

Initial commit history with `featureB` work
圖 69. 具有 featureB 工作的初始提交歷史記錄

假設專案維護者已提取許多其他修補程式並嘗試了您的第一個分支,但它不再乾淨地合併。在這種情況下,您可以嘗試將該分支變基在 origin/master 之上,為維護者解決衝突,然後重新提交您的變更。

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

這會重寫您的歷史記錄,現在看起來像 featureA 工作後的提交歷史記錄

Commit history after `featureA` work
圖 70. featureA 工作後的提交歷史記錄

由於您已變基分支,因此您必須在推送命令中指定 -f,才能用不是其後代的提交來取代伺服器上的 featureA 分支。另一種方法是將此新工作推送到伺服器上的另一個分支(可能稱為 featureAv2)。

讓我們看一下另一種可能的情況:維護者已查看您第二個分支中的工作,並喜歡這個概念,但希望您變更實作細節。您也會藉此機會將工作移至基於專案目前 master 分支的位置。您根據目前的 origin/master 分支開始一個新分支,將 featureB 變更壓縮到那裡,解決任何衝突,進行實作變更,然後將其作為一個新分支推送:

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

--squash 選項會取得合併分支上的所有工作,並將其壓縮為一個變更集,產生儲存庫狀態,就好像發生了真正的合併一樣,而實際上並未建立合併提交。這表示您未來的提交只會有一個父項,並允許您從另一個分支引入所有變更,然後在記錄新提交之前進行更多變更。如果使用預設的合併流程,--no-commit 選項也可用於延遲合併提交。

此時,您可以通知維護者您已完成要求的變更,他們可以在您的 featureBv2 分支中找到這些變更。

Commit history after `featureBv2` work
圖 71. featureBv2 工作後的提交歷史記錄

透過電子郵件的公開專案

許多專案都有接受修補程式的既定程序 — 您需要檢查每個專案的具體規則,因為它們會有所不同。由於有幾個較舊、較大的專案透過開發人員郵件列表接受修補程式,因此我們現在將介紹一個範例。

工作流程與先前的使用案例類似 — 您為您處理的每個修補程式系列建立主題分支。不同之處在於您如何將它們提交到專案。您不是分支專案並推送到您自己的可寫入版本,而是產生每個提交系列的電子郵件版本,並將其以電子郵件寄給開發人員郵件列表。

$ git checkout -b topicA
  ... work ...
$ git commit
  ... work ...
$ git commit

現在您有兩個想要傳送到郵件列表的提交。您可以使用 git format-patch 來產生 mbox 格式的檔案,這些檔案可以透過電子郵件發送到列表 — 它會將每個提交轉換成一封電子郵件訊息,並將提交訊息的第一行作為主旨,其餘訊息加上提交引入的修補程式作為內文。這樣做的好處是,從使用 format-patch 產生的電子郵件套用修補程式時,可以正確地保留所有的提交資訊。

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-increase-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')
--
2.1.0

您也可以編輯這些修補程式檔案,為電子郵件列表新增更多您不想顯示在提交訊息中的資訊。如果您在 --- 行和修補程式的開頭(diff --git 行)之間加入文字,開發人員可以閱讀它,但該內容會被修補程式處理程序忽略。

若要將此內容以電子郵件寄送到郵件列表,您可以將檔案貼到您的電子郵件程式中,或透過命令列程式傳送。貼上文字通常會導致格式問題,尤其是一些「更聰明」的客戶端無法正確保留換行符號和其他空白字元。幸運的是,Git 提供了一個工具來幫助您透過 IMAP 發送格式正確的修補程式,這對您來說可能更容易。我們將示範如何透過 Gmail 發送修補程式,這剛好是我們最熟悉的電子郵件代理程式;您可以在 Git 原始碼中上述的 Documentation/SubmittingPatches 檔案末尾找到多種郵件程式的詳細說明。

首先,您需要在您的 ~/.gitconfig 檔案中設定 imap 區段。您可以透過一系列 git config 命令分別設定每個值,或者您可以手動加入它們,但最終您的設定檔應該看起來像這樣

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = YX]8g76G_2^sFbd
  port = 993
  sslverify = false

如果您的 IMAP 伺服器不使用 SSL,則最後兩行可能不是必要的,而 host 值將會是 imap:// 而不是 imaps://。設定好之後,您可以使用 git imap-send 將修補程式序列放入指定 IMAP 伺服器的草稿資料夾中

$ 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

此時,您應該可以前往您的草稿資料夾,將「收件人」欄位變更為您要將修補程式傳送到的郵件列表,並可能抄送給維護人員或負責該區段的人員,然後將其寄出。

您也可以透過 SMTP 伺服器傳送修補程式。和之前一樣,您可以透過一系列 git config 命令分別設定每個值,或者您可以手動將它們加入您 ~/.gitconfig 檔案的 sendemail 區段中

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

完成此動作後,您可以使用 git send-email 來傳送您的修補程式

$ git send-email *.patch
0001-add-limit-to-log-function.patch
0002-increase-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 <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] Add 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
提示

如需有關設定系統和電子郵件、更多訣竅與技巧,以及透過電子郵件傳送試用修補程式的沙箱的協助,請前往 git-send-email.io

總結

在本節中,我們涵蓋了多種工作流程,並討論了在小型團隊中進行封閉原始碼專案與為大型公開專案做出貢獻之間的差異。您知道在提交之前檢查空白字元錯誤,並且可以撰寫出色的提交訊息。您學習了如何格式化修補程式,並將其以電子郵件寄送給開發人員郵件列表。我們也在不同工作流程的背景下涵蓋了處理合併的情況。您現在已經準備好在任何專案上進行協作。

接下來,您將看到如何從另一方面著手:維護 Git 專案。您將學習如何成為一個仁慈的獨裁者或整合經理。

scroll-to-top