# 對項目做出貢獻
賬戶已經建立好了,現在我們來了解一些能幫助你對現有的項目做出貢獻的知識。
## 派生(Fork)項目
如果你想要參與某個項目,但是并沒有推送權限,這時可以對這個項目進行“派生”。派生的意思是指,GitHub 將在你的空間中創建一個完全屬于你的項目副本,且你對其具有推送權限。
> 在以前,“fork”是一個貶義詞,指的是某個人使開源項目向不同的方向發展,或者創建一個競爭項目,使得原項目的貢獻者分裂。在 GitHub,“fork”指的是你自己的空間中創建的項目副本,這個副本允許你以一種更開放的方式對其進行修改。
通過這種方式,項目的管理者不再需要忙著把用戶添加到貢獻者列表并給予他們推送權限。人們可以派生這個項目,將修改推送到派生出的項目副本中,并通過創建合并請求(Pull Request)來讓他們的改動進入源版本庫,下文我們會詳細說明。創建了合并請求后,就會開啟一個可供審查代碼的板塊,項目的擁有者和貢獻者可以在此討論相關修改,直到項目擁有者對其感到滿意,并且認為這些修改可以被合并到版本庫。
你可以通過點擊項目頁面右上角的“Fork”按鈕,來派生這個項目。

Figure 6-8. “Fork”按鈕
稍等片刻,你將被轉到新項目頁面,該項目包含可寫的代碼副本。
## GitHub 流程
GitHub 設計了一個以合并請求為中心的特殊合作流程。它基于我們在 [Chapter?3](#) 的 [“特性分支”](#) 中提到的工作流程。不管你是在一個緊密的團隊中使用單獨的版本庫,或者使用許多的“Fork”來為一個由陌生人組成的國際企業或網絡做出貢獻,這種合作流程都能應付。
流程通常如下:
1. 從 `master` 分支中創建一個新分支
1. 提交一些修改來改進項目
1. 將這個分支推送到 GitHub 上
1. 創建一個合并請求
1. 討論,根據實際情況繼續修改
1. 項目的擁有者合并或關閉你的合并請求
這基本和 [“集成管理者工作流”](#) 中的一體化管理流程差不多,但是團隊可以使用 GitHub 提供的網頁工具替代電子郵件來交流和審查修改。
現在我們來看一個使用這個流程的例子。
### 創建合并請求
Tony 在找一些能在他的 Arduino 微控制器上運行的代碼,他覺得 [*https://github.com/schacon/blink*](https://github.com/schacon/blink) 中的代碼不錯。

Figure 6-9. 他想要做出貢獻的項目
但是有個問題,這個代碼中的的閃爍頻率太高,我們覺得 3 秒一次比 1 秒一次更好一些。所以讓我們來改進這個程序,并將修改后的代碼提交給這個項目。
首先,單擊“Fork”按鈕來獲得這個項目的副本。我們使用的用戶名是“tonychacon”,所以這個項目副本的訪問地址是: `https://github.com/tonychacon/blink` 。我們將它克隆到本地,創建一個分支,修改代碼,最后再將改動推送到 GitHub。
~~~
$ git clone https://github.com/tonychacon/blink
Cloning into 'blink'...
$ cd blink
$ git checkout -b slow-blink
Switched to a new branch 'slow-blink'
$ sed -i '' 's/1000/3000/' blink.ino
$ git diff --word-diff
diff --git a/blink.ino b/blink.ino
index 15b9911..a6cc5a5 100644
--- a/blink.ino
+++ b/blink.ino
@@ -18,7 +18,7 @@ void setup() {
// the loop routine runs over and over again forever:
void loop() {
digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
[-delay(1000);-]{+delay(3000);+} // wait for a second
digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
[-delay(1000);-]{+delay(3000);+} // wait for a second
}
$ git commit -a -m 'three seconds is better'
[slow-blink 5ca509d] three seconds is better
1 file changed, 2 insertions(+), 2 deletions(-)
$ git push origin slow-blink
Username for 'https://github.com': tonychacon
Password for 'https://tonychacon@github.com':
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 340 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To https://github.com/tonychacon/blink
* [new branch] slow-blink -> slow-blink
~~~
[](#) 將派生出的副本克隆到本地
[](#) 創建出名稱有意義的分支
[](#) 修改代碼
[](#) 檢查改動
[](#) 將改動提交到分支中
[](#) 將新分支推送到 GitHub 的副本中
現在到 GitHub 上查看之前的項目副本,可以看到 GitHub 提示我們有新的分支,并且顯示了一個大大的綠色按鈕讓我們可以檢查我們的改動,并給源項目創建合并請求。
你也可以到“Branches”(分支)頁面查看分支并創建合并請求: `https://github.com/<用戶名>/<項目名>/branches`

Figure 6-10. 合并請求按鈕
如果你點擊了那個綠色按鈕,就會看到一個新頁面,在這里我們可以對改動填寫標題和描述,讓項目的擁有者考慮一下我們的改動。通常花點時間來編寫個清晰有用的描述是個不錯的主意,這能讓作者明白為什么這個改動可以給他的項目帶來好處,并且讓他接受合并請求。
同時我們也能看到比主分支中所“領先”(ahead)的提交(在這個例子中只有一個)以及所有將會被合并的改動與之前代碼的對比。

Figure 6-11. 合并請求創建頁面
當你單擊了“Create pull request”(創建合并請求)的按鈕后,這個項目的擁有者將會收到一條包含關改動和合并請求頁面的鏈接的提醒。
雖然合并請求通常是在貢獻者準備好在公開項目中提交改動的時候提交,但是也常被用在仍處于開發階段的內部項目中。因為合并請求在提交后 **依然可以加入新的改動** ,它也經常被用來建立團隊合作的環境,而不只是在最終階段使用。
### 利用合并請求
現在,項目的擁有者可以看到你的改動并合并它,拒絕它或是發表評論。在這里我們就當作他喜歡這個點子,但是他想要讓燈熄滅的時間比點亮的時間稍長一些。
接下來可能會通過電子郵件進行互動,就像我們在 [Chapter?5](#) 中提到的工作流程那樣,但是在 GitHub,這些都在線上完成。項目的擁有者可以審查修改,只需要單擊某一行,就可以對其發表評論。

Figure 6-12. 對合并請求內的特定一行發表評論
當維護者發表評論后,提交合并請求的人,以及所有正在關注(Watching)這個版本庫的用戶都會收到通知。我們待會兒將會告訴你如何修改這項設置。現在,如果 Tony 有開啟電子郵件提醒,他將會收到這樣的一封郵件:

Figure 6-13. 通過電子郵件發送的評論提醒
每個人都能在合并請求中發表評論。在 [Figure?6-14](#) 里我們可以看到項目擁有者對某行代碼發表評論,并在討論區留下了一個普通評論。你可以看到被評論的代碼也會在互動中顯示出來。

Figure 6-14. 合并請求討論頁面
現在貢獻者可以看到如何做才能讓他們的改動被接受。幸運的是,這也是一件輕松的事情。如果你使用的是電子郵件進行交流,你需要再次對代碼進行修改并重新提交至郵件列表,在 GitHub 上,你只需要再次提交到你的分支中并推送即可。
如果貢獻者完成了以上的操作,項目的擁有者會再次收到提醒,當他們查看頁面時,將會看到最新的改動。事實上,只要提交中有一行代碼改動,GitHub 都會注意到并處理掉舊的變更集。

Figure 6-15. 最終的合并請求
如果你點開合并請求的“Files Changed”(更改的文件)選項卡,你將會看到“整理過的”差異表 —— 也就是這個分支被合并到主分支之后將會產生的所有改動,其實就是 `git diff master...<分支名>` 命令的執行結果。你可以瀏覽 [“確定引入了哪些東西”](#) 來了解更多關于差異表的知識。
你還會注意到,GitHub 會檢查你的合并請求是否能直接合并,如果可以,將會提供一個按鈕來進行合并操作。這個按鈕只在你對版本庫有寫入權限并且可以進行簡潔合并時才會顯示。你點擊后 GitHub 將做出一個“非快進式”(non-fast-forward)合并,即使這個合并 **能夠** 快進式(fast-forward)合并,GitHub 依然會創建一個合并提交。
如果你需要,你還可以將分支拉取并在本地合并。如果你將這個分支合并到 `master` 分支中并推送到 GitHub,這個合并請求會被自動關閉。
這就是大部分 GitHub 項目使用的工作流程。創建分支,基于分支創建合并請求,進行討論,根據需要繼續在分支上進行修改,最終關閉或合并合并請求。
> ### 不必總是 Fork
> 有件很重要的事情:你可以在同一個版本庫中不同的分支提交合并請求。如果你正在和某人實現某個功能,而且你對項目有寫權限,你可以推送分支到版本庫,并在 `master` 分支提交一個合并請求并在此進行代碼審查和討論的操作。不需要進行“Fork”。
### 合并請求的進階用法
目前,我們學到了如何在 GitHub 平臺對一個項目進行最基礎的貢獻。現在我們會教給你一些小技巧,讓你可以更加有效率地使用合并請求。
### 將合并請求制作成補丁
有一件重要的事情:許多項目并不認為合并請求可以作為補丁,就和通過郵件列表工作的的項目對補丁貢獻的看法一樣。大多數的 GitHub 項目將合并請求的分支當作對改動的交流方式,并將變更集合起來統一進行合并。
這是個重要的差異,因為一般來說改動會在代碼完成前提出,這和基于郵件列表的補丁貢獻有著天差地別。這使得維護者們可以更早的溝通,由社區中的力量能提出更好的方案。當有人從合并請求提交了一些代碼,并且維護者和社區提出了一些意見,這個補丁系列并不需要從頭來過,只需要將改動重新提交并推送到分支中,這使得討論的背景和過程可以齊頭并進。
舉個例子,你可以回去看看 [Figure?6-15](#),你會注意到貢獻者沒有變基他的提交再提交一個新的合并請求,而是直接增加了新的提交并推送到已有的分支中。如果你之后再回去查看這個合并請求,你可以輕松地找到這個修改的原因。點擊網頁上的“Merge”(合并)按鈕后,會建立一個合并提交并指向這個合并請求,你就可以很輕松的研究原來的討論內容。
### 與上游保持同步
如果你的合并請求由于過時或其他原因不能干凈地合并,你需要進行修復才能讓維護者對其進行合并。GitHub 會對每個提交進行測試,讓你知道你的合并請求能否簡潔的合并。

Figure 6-16. 不能進行干凈合并
如果你看到了像 [Figure?6-16](#) 中的畫面,你就需要修復你的分支讓這個提示變成綠色,這樣維護者就不需要再做額外的工作。
你有兩種方法來解決這個問題。你可以把你的分支變基到目標分支中去(通常是你派生出的版本庫中的 `master` 分支),或者你可以合并目標分支到你的分支中去。
GitHub 上的大多數的開發者會使用后一種方法,基于我們在上一節提到的理由:我們最看重的是歷史記錄和最后的合并,變基除了給你帶來看上去簡潔的歷史記錄,只會讓你的工作變得更加困難且更容易犯錯。
如果你想要合并目標分支來讓你的合并請求變得可合并,你需要將源版本庫添加為一個新的遠端,并從遠端抓取內容,合并主分支的內容到你的分支中去,修復所有的問題并最終重新推送回你提交合并請求使用的分支。
在這個例子中,我們再次使用之前的“tonychacon”用戶來進行示范,源作者提交了一個改動,使得合并請求和它產生了沖突。現在來看我們解決這個問題的步驟。
~~~
$ git remote add upstream https://github.com/schacon/blink
$ git fetch upstream
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
Unpacking objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
From https://github.com/schacon/blink
* [new branch] master -> upstream/master
$ git merge upstream/master
Auto-merging blink.ino
CONFLICT (content): Merge conflict in blink.ino
Automatic merge failed; fix conflicts and then commit the result.
$ vim blink.ino
$ git add blink.ino
$ git commit
[slow-blink 3c8d735] Merge remote-tracking branch 'upstream/master' \
into slower-blink
$ git push origin slow-blink
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 682 bytes | 0 bytes/s, done.
Total 6 (delta 2), reused 0 (delta 0)
To https://github.com/tonychacon/blink
ef4725c..3c8d735 slower-blink -> slow-blink
~~~
[](#) 將源版本庫添加為一個遠端,并命名為“upstream”(上游)
[](#) 從遠端抓取最新的內容
[](#) 將主分支的內容合并到你的分支中
[](#) 修復產生的沖突
[](#) 再推送回同一個分支
你完成了上面的步驟后,合并請求將會自動更新并重新檢查是否能干凈的合并。

Figure 6-17. 合并請求現在可以干凈地合并了
Git 的偉大之處就是你可以一直重復以上操作。如果你有一個運行了十分久的項目,你可以輕松地合并目標分支且只需要處理最近的一次沖突,這使得管理流程更加容易。
如果你一定想對分支做變基并進行清理,你可以這么做,但是強烈建議你不要強行的提交到已經提交了合并請求的分支。如果其他人拉取了這個分支并進行一些修改,你將會遇到 [“變基的風險”](#) 中提到的問題。相對的,將變基后的分支推送到 GitHub 上的一個新分支中,并且創建一個全新的合并請求引用舊的合并請求,然后關閉舊的合并請求。
### 參考
你的下個問題可能是“我該如何引用舊的合并請求?”。有許多方法可以讓你在 GitHub 上的幾乎任何地方引用其他東西。
先從如何對合并請求或議題(Issue)進行相互引用開始。所有的合并請求和議題在項目中都會有一個獨一無二的編號。舉個例子,你無法同時擁有 3 號合并請求和 3 號議題。如果你想要引用任何一個合并請求或議題,你只需要在提交或描述中輸入 `#<編號>` 即可。你也可以指定引用其他版本庫的議題或合并請求,如果你想要引用其他人對該版本庫的“Fork”中的議題或合并請求,輸入 `用戶名#<編號>` ,如果在不同的版本庫中,輸入 `用戶名/版本庫名#<編號>` 。
我們來看一個例子。假設我們對上個例子中的分支進行了變基,并為此創建一個新的合并請求,現在我們希望能在新的合并請求中引用舊的合并請求。我們同時希望引用一個派生出的項目中的議題和一個完全不同的項目中的議題,就可以像 [Figure?6-18](#) 這樣填寫描述。

Figure 6-18. 在合并請求中的交叉引用
當我們提交了這個合并請求,我們將會看到以上內容被渲染成這樣:[Figure?6-19](#)

Figure 6-19. 在合并請求中渲染后的交叉引用
你會注意到完整的 GitHub 地址被簡化了,只留下了必要的信息。
如果 Tony 回去關閉了源合并請求,我們可以看到一個被引用的提示,GitHub 會自動的反向追蹤事件并顯示在合并請求的時間軸上。這意味著任何查看這個合并請求的人可以輕松地訪問新的合并請求。這個鏈接就像 [Figure?6-20](#) 中展示的那樣。

Figure 6-20. 在合并請求中渲染后的交叉引用
除了議題編號外,你還可以通過使用提交的 SHA-1 來引用提交。你必須完整的寫出 40 位長的 SHA,GitHub 會在評論中自動地產生指向這個提交的鏈接。同樣的,你可以像引用議題一樣對“Fork”出的項目中的提交或者其他項目中的提交進行引用。
## Markdown
對于在 GitHub 中絕大多數文本框中能夠做到的事,引用其他議題只是個開始。在議題和合并請求的描述,評論和代碼評論還有其他地方,都可以使用“GitHub 風格的 Markdown”。Markdown 可以讓你輸入純文本,但是渲染出豐富的內容。
查看 [Figure?6-21](#) 里的例子來了解如何書寫評論或文本,并通過 Markdown 進行渲染。

Figure 6-21. 一個 Markdown 的例子和渲染效果
### GitHub 風格的 Markdown
GitHub 風格的 Markdown 增加了一些基礎的 Markdown 中做不到的東西。它在創建合并請求和議題中的評論和描述時十分有用。
#### 任務列表
第一個 GitHub 專屬的 Markdown 功能,特別是用在合并請求中,就是任務列表。一個任務列表可以展示出一系列你想要完成的事情,并帶有復選框。把它們放在議題或合并請求中時,通常可以展示你想要完成的事情。
你可以這樣創建一個任務列表:
~~~
- [X] 編寫代碼
- [ ] 編寫所有測試程序
- [ ] 為代碼編寫文檔
~~~
如果我們將這個列表加入合并請求或議題的描述中,它將會被渲染 [Figure?6-22](#) 這樣。

Figure 6-22. Markdown 評論中渲染后的任務列表
在合并請求中,任務列表經常被用來在合并之前展示這個分支將要完成的事情。最酷的地方就是,你只需要點擊復選框,就能更新評論 —— 你不需要直接修改 Markdown。
不僅如此,GitHub 還會將你在議題和合并請求中的任務列表整理起來集中展示。舉個例子,如果你在一個合并請求中有任務清單,你將會在所有合并請求的總覽頁面上看到它的進度。這使得人們可以把一個合并請求分解成不同的小任務,同時便于其他人了解分支的進度。你可以在 [Figure?6-23](#) 看到一個例子。

Figure 6-23. 在合并請求列表中的任務列表總結
當你在實現一個任務的早期就提交合并請求,并使用任務清單追蹤你的進度,這個功能會十分的有用。
#### 摘錄代碼
你也可以在評論中摘錄代碼。這在你想要展示尚未提交到分支中的代碼時會十分有用。它也經常被用在展示無法正常工作的代碼或這個合并請求需要的代碼。
你需要用“反引號”將需要添加的摘錄代碼包起來。
~~~
```java
for(int i=0 ; i < 5 ; i++)
{
System.out.println("i is : " + i);
}
```
~~~
如果加入語言的名稱,就像我們這里加入的“java”一樣,GitHub 會自動嘗試對摘錄的片段進行語法高亮。在下面的例子中,它最終會渲染成這個樣子: [Figure?6-24](#) 。

Figure 6-24. 渲染后的摘錄代碼示例
#### 引用
如果你在回復一個很長的評論之中的一小段,你只需要復制你需要的片段,并在每行前添加 `>` 符號即可。事實上,因為這個功能會被經常用到,它也有一個快捷鍵。只要你把你要回應的文字選中,并按下 `r` 鍵,選中的問題會自動引用并填入評論框。
引用的部分就像這樣:
~~~
> Whether 'tis Nobler in the mind to suffer
> The Slings and Arrows of outrageous Fortune,
How big are these slings and in particular, these arrows?
~~~
經過渲染后,就會變成這樣: [Figure?6-25](#)

Figure 6-25. 渲染后的引用示例
#### 表情符號(Emoji)
最后,我們可以在評論中使用表情符號。這經常出現在 GitHub 的議題和合并請求的評論中。GitHub 上甚至有表情助手。如果你在輸入評論時以 `:` 開頭,自動完成器會幫助你找到你需要的表情。

Figure 6-26. 表情符號自動完成器
你也可以在評論的任何地方使用 `:<表情名稱>:` 來添加表情符號。舉個例子,你可以輸入以下文字:
~~~
I :eyes: that :bug: and I :cold_sweat:.
:trophy: for :microscope: it.
:+1: and :sparkles: on this :ship:, it's :fire::poop:!
:clap::tada::panda_face:
~~~
渲染之后,就會變成這樣: [Figure?6-27](#)

Figure 6-27. 使用了大量表情符號的評論
雖然這個功能并不是非常實用,但是它在這種不方便表達感情的媒體里,加入了趣味的元素。
事實上現在已經有大量的在線服務可以使用表情符號,這里有個列表可以讓你快速的找到能表達你的情緒的表情符號:
[*http://www.emoji-cheat-sheet.com*](http://www.emoji-cheat-sheet.com)
#### 圖片
從技術層面來說,這并不是 GitHub 風格 Markdown 的功能,但是也很有用。如果不想使用 Markdown 語法來插入圖片,GitHub 允許你通過拖拽圖片到文本區來插入圖片。

Figure 6-28. 通過拖拽的方式自動插入圖片
如果你回去查看 [Figure?6-18](#) ,你會發現文本區上有個“Parsed as Markdown”的提示。點擊它你可以了解所有能在 GitHub 上使用的 Markdown 功能。
- 前言
- Scott Chacon 序
- Ben Straub 序
- 獻辭
- 貢獻者
- 引言
- 1. 起步
- 1.1 關于版本控制
- 1.2 Git 簡史
- 1.3 Git 基礎
- 1.4 命令行
- 1.5 安裝 Git
- 1.6 初次運行 Git 前的配置
- 1.7 獲取幫助
- 1.8 總結
- 2. Git 基礎
- 2.1 獲取 Git 倉庫
- 2.2 記錄每次更新到倉庫
- 2.3 查看提交歷史
- 2.4 撤消操作
- 2.5 遠程倉庫的使用
- 2.6 打標簽
- 2.7 Git 別名
- 2.8 總結
- 3. Git 分支
- 3.1 分支簡介
- 3.2 分支的新建與合并
- 3.3 分支管理
- 3.4 分支開發工作流
- 3.5 遠程分支
- 3.6 變基
- 3.7 總結
- 4. 服務器上的 Git
- 4.1 協議
- 4.2 在服務器上搭建 Git
- 4.3 生成 SSH 公鑰
- 4.4 配置服務器
- 4.5 Git 守護進程
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管的選擇
- 4.10 總結
- 5. 分布式 Git
- 5.1 分布式工作流程
- 5.2 向一個項目貢獻
- 5.3 維護項目
- 5.4 總結
- 6. GitHub
- 6.1 賬戶的創建和配置
- 6.2 對項目做出貢獻
- 6.3 維護項目
- 6.4 管理組織
- 6.5 腳本 GitHub
- 6.6 總結
- 7. Git 工具
- 7.1 選擇修訂版本
- 7.2 交互式暫存
- 7.3 儲藏與清理
- 7.4 簽署工作
- 7.5 搜索
- 7.6 重寫歷史
- 7.7 重置揭密
- 7.8 高級合并
- 7.9 Rerere
- 7.10 使用 Git 調試
- 7.11 子模塊
- 7.12 打包
- 7.13 替換
- 7.14 憑證存儲
- 7.15 總結
- 8. 自定義 Git
- 8.1 配置 Git
- 8.2 Git 屬性
- 8.3 Git 鉤子
- 8.4 使用強制策略的一個例子
- 8.5 總結
- 9. Git 與其他系統
- 9.1 作為客戶端的 Git
- 9.2 遷移到 Git
- 9.3 總結
- 10. Git 內部原理
- 10.1 底層命令和高層命令
- 10.2 Git 對象
- 10.3 Git 引用
- 10.4 包文件
- 10.5 引用規格
- 10.6 傳輸協議
- 10.7 維護與數據恢復
- 10.8 環境變量
- 10.9 總結
- A. 其它環境中的 Git
- A1.1 圖形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Eclipse 中的 Git
- A1.4 Bash 中的 Git
- A1.5 Zsh 中的 Git
- A1.6 Powershell 中的 Git
- A1.7 總結
- B. 將 Git 嵌入你的應用
- A2.1 命令行 Git 方式
- A2.2 Libgit2
- A2.3 JGit
- C. Git 命令
- A3.1 設置與配置
- A3.2 獲取與創建項目
- A3.3 快照基礎
- A3.4 分支與合并
- A3.5 項目分享與更新
- A3.6 檢查與比較
- A3.7 調試
- A3.8 補丁
- A3.9 郵件
- A3.10 外部系統
- A3.11 管理
- A3.12 底層命令