章節 ▾ 第二版

5.2 分散式 Git - 貢獻專案

貢獻專案

描述如何向專案貢獻程式碼的主要困難在於其多種多樣的變體。由於 Git 非常靈活,人們可以並且確實以多種方式協作,因此很難確切說明您應該如何貢獻——每個專案都有所不同。涉及的一些變數包括活躍貢獻者數量、所選工作流程、您的提交許可權以及可能的外部貢獻方式。

第一個變數是活躍貢獻者數量——有多少使用者正在積極地向此專案貢獻程式碼,以及頻率如何?在許多情況下,您會遇到兩三個開發者每天提交幾次,對於一些不那麼活躍的專案可能更少。對於大型公司或專案,開發者數量可能達到數千,每天有數百或數千次提交。這很重要,因為隨著開發者數量的增加,您會遇到更多問題,需要確保您的程式碼能夠乾淨地應用或輕鬆合併。您提交的更改可能會因為在您工作期間或您的更改等待批准或應用期間合併進來的工作而變得過時或嚴重損壞。您如何才能使程式碼持續保持最新並確保提交有效?

下一個變數是專案所使用的工作流程。它是中心化的嗎,每個開發者都對主程式碼線擁有相同的寫入許可權嗎?專案是否有維護者或整合管理員來檢查所有補丁?所有補丁都經過同行評審和批准嗎?您是否參與了該過程?是否存在一箇中尉(lieutenant)系統,您是否必須先向他們提交工作?

下一個變數是您的提交許可權。如果您擁有專案的寫入許可權,貢獻專案所需的工作流程與您沒有寫入許可權時大不相同。如果您沒有寫入許可權,專案偏好以何種方式接受貢獻的工作?它是否有相應的策略?您一次貢獻多少工作?您貢獻的頻率如何?

所有這些問題都會影響您如何有效地向專案貢獻程式碼,以及哪些工作流程是受青睞或可供您使用的。我們將在一系列用例中涵蓋這些方面的各個方面,從簡單到複雜;您應該能夠根據這些示例,在實踐中構建您所需的特定工作流程。

提交規範

在我們開始檢視具體用例之前,這裡有一個關於提交資訊的簡短說明。為建立提交制定並遵循良好的規範,將使使用 Git 和與他人協作變得容易得多。Git 專案提供了一份文件,其中列出了許多關於建立提交以提交補丁的好技巧——您可以在 Git 原始碼的 Documentation/SubmittingPatches 檔案中閱讀它。

首先,您的提交不應包含任何空格錯誤。Git 提供了一種簡單的方法來檢查這一點——在提交之前,執行 git diff --check,它會識別可能的空格錯誤併為您列出。

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

如果在提交前執行該命令,您可以知道自己是否即將提交可能令其他開發者感到惱火的空格問題。

接下來,嘗試將每個提交作為一個邏輯上獨立的程式碼集。如果可能,嘗試使您的更改易於理解——不要在週末花一整天時間處理五個不同的問題,然後週一將它們作為一次巨大的提交全部提交。即使您在週末不提交,週一也要使用暫存區將您的工作拆分為至少每個問題一個提交,併為每個提交提供有用的資訊。如果某些更改修改了同一個檔案,請嘗試使用 git add --patch 來部分暫存檔案(詳見互動式暫存)。無論您執行一次提交還是五次提交,只要所有更改都在某個時間點新增,分支末端的專案快照都是相同的,因此當您的同事開發者需要審查您的更改時,請儘量讓他們更容易一些。

這種方法也使得以後需要時更容易取出或恢復其中一個變更集。重寫歷史詳細介紹了許多有用的 Git 技巧,用於重寫歷史和互動式暫存檔案——在將工作傳送給其他人之前,使用這些工具來幫助構建一個清晰易懂的歷史。

最後要記住的是提交資訊。養成建立高質量提交資訊的習慣將使使用 Git 和與 Git 協作變得容易得多。通常,您的資訊應該以一行開頭,長度不超過大約 50 個字元,並簡潔地描述變更集,然後是空行,再是更詳細的解釋。Git 專案要求更詳細的解釋應包括您進行此更改的動機,並將其實現與以前的行為進行對比——這是一個很好的遵循準則。用命令式語氣編寫您的提交資訊:“修復錯誤”(Fix bug),而不是“已修復錯誤”(Fixed bug)或“修復了錯誤”(Fixes bug)。以下是一個您可以遵循的模板,我們對 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,看看一個格式優美的專案提交歷史是什麼樣子。

注意
照我們說的做,不要照我們做的做。

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

簡而言之,照我們說的做,不要照我們做的做。

私有小型團隊

您可能遇到的最簡單的設定是一個私有專案,其中有一兩個其他開發者。“私有”在此上下文中意味著閉源——外部世界無法訪問。您和其他開發者都擁有對倉庫的推送(push)許可權。

在這種環境中,您可以遵循類似於使用 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

上面輸出的最後一行顯示了 push 操作的一個有用返回資訊。基本格式是 <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 的更改後的歷史

現在 origin/master 可以從 Jessica 的 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 正在合作開發一個功能(稱之為“功能A”),而 Jessica 和第三位開發者 Josie 正在合作開發第二個功能(例如“功能B”)。在這種情況下,公司使用的是一種整合管理員工作流程,其中各個團隊的工作僅由特定的工程師整合,主倉庫的 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 的電子郵件,說一個包含一些初始“功能B”工作的分支已經以 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 想要將所有這些合併後的“功能B”工作推回到伺服器,但她不想僅僅推送她自己的 featureB 分支。相反,由於 Josie 已經開始了一個上游的 featureBee 分支,Jessica 想要推送到那個分支,她透過以下方式實現

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

這被稱為一個引用規範(refspec)。有關 Git 引用規範及其不同用法的更詳細討論,請參閱引用規範。另請注意 -u 標誌;這是 --set-upstream 的縮寫,它配置分支以便將來更輕鬆地進行推送和拉取。

突然,Jessica 收到 John 的電子郵件,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 分支已準備好整合到主線中。整合者將這些分支合併到主線後,一次 fetch 操作將拉取新的合併提交,使歷史記錄看起來像這樣

Jessica’s history after merging both her topic branches
圖 67. Jessica 在合併其兩個主題分支後的歷史

許多團隊轉用 Git 是因為 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 將您的工作壓縮成單個提交,或者重新排列提交中的工作,以便維護者更容易審查補丁——有關互動式變基的更多資訊,請參閱重寫歷史

當您的分支工作完成後,並且您準備好將其貢獻回維護者時,請轉到原始專案頁面並點選“Fork”按鈕,建立您自己的可寫專案分叉。然後您需要將此倉庫 URL 新增為您的本地倉庫的一個新遠端;在此示例中,我們將其命名為 myfork

$ git remote add myfork <url>

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

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

$ git push -u myfork featureA

一旦您的工作被推送到您的倉庫分叉,您需要通知原始專案的維護者,您有希望他們合併的工作。這通常被稱為拉取請求(pull request),您通常可以透過網站生成此類請求——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 工作後的提交歷史

因為您對分支進行了變基,所以您必須在 push 命令中指定 -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