Git
Chapters ▾ 2nd Edition

6.2 GitHub - 參與一個專案

參與一個專案

現在帳號設定好了,來看看一些關於如何對現有專案做出貢獻的有用小細節吧。

Fork 專案

如果你想要參與一個你沒有推送權限的專案,你可以「fork」一份。這代表說 GitHub 會複製一份這個專案的副本給你,並且你對這副本有全部的權限。這副本會存在於你的帳號下,你可以對它進行推送。

筆記

歷史上,「fork」這件事情在程式開發的領域裡多少帶了點負面意味。因為有些人會透過這途徑將一個開源專案的發展帶往不同方向,甚至是創造出跟原本專案競爭的作品,進而導致貢獻者的分裂。 在 GitHub 上,「fork」就是把一份相同的專案放在你的帳號之下,讓你能夠公開對這專案做變更,做為一個以更開放的方式來參與專案。

透過這方式,專案就不用去煩惱需要把所有協作者加入使用者來讓他們擁有推送的權限。 所有人可以 fork 專案,對 fork 出來的專案推送變更,然後去發出我們等下會提到的 Pull Request,來把這些變更貢獻回原本的專案裡。 這會開立一個能夠作程式碼審閱的討論串,然後擁有者能和貢獻者討論這個變更,直到擁有者覺得可以合併進原始專案裡面。

去到專案頁面,點下右上角的「Fork」鍵,就可以 fork 專案。

「Fork」鈕
圖表 88. 「Fork」鈕

幾秒鐘之後,你就會被帶到你有寫入權限的新專案頁面。

GitHub 流程

GitHub 是基於一個以 Pull Request 為中心的特別合作流程而設計出來的。 這個流程,不論是你在一個緊密連結的團隊裡共同在單一倉儲上合作;或是一個由散布全球的陌生人們構成的合作網路或是公司,透過大量的 fork 專案來對專案做出貢獻,都能運作。 這一切都是基於我們在 使用 Git 分支 這章所講過的 主題分支 的工作流程。

一般情況下就是照著下面的程序運作的:

  1. master 建立一個主題分支。

  2. 加入一些變更來改善這個專案。

  3. 把這個分支推送到你的 GitHub 專案。

  4. 在 GitHub 上建立一個 Pull Request。

  5. 討論,並在需要的時候加入新的變更。

  6. 專案擁有者視情況決定要把這個 Pull Request 合併進原始專案,或是關閉它。

這基本上就是我們在 整合式管理員工作流程 這部分提過的整合式管理流程,不過我們是使用 GitHub 的網頁工具來做溝通或是變更審閱,而非電子郵件。

我們來看看下面的例子來了解如何使用這個流程來對 GitHub 上的專案做出變更吧。

建立一個 Pull Request

Tony 在找能夠在他的 Arduino 可程式化微控制器上運作的程式碼。然後他在 GitHub 的這個專案 https://github.com/schacon/blink 找到了個很棒的程式碼。

他想要做出貢獻的專案
圖表 89. 他想要做出貢獻的專案

唯一的小問題就是閃爍的頻率太高了,我們覺得放慢成 3 秒一次會比原本的 1 秒一次好。 所以我們來改善這個程式並作為變更要求來提交回去吧。

首先,我們要先按下稍早提過的「Fork」鈕來取得這個專案的副本。 我們在這邊使用的使用者名稱是「tonychacon」,所以這個專案的副本會放在 https://github.com/tonychacon/blink,而且我們能編輯這個副本。 我們把它克隆一份到電腦上、建立主題分支、對程式碼作變更,最後推送回 GitHub。

$ git clone https://github.com/tonychacon/blink (1)
Cloning into 'blink'...

$ cd blink
$ git checkout -b slow-blink (2)
Switched to a new branch 'slow-blink'

$ sed -i '' 's/1000/3000/' blink.ino (3)

$ git diff --word-diff (4)
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' (5)
[slow-blink 5ca509d] three seconds is better
 1 file changed, 2 insertions(+), 2 deletions(-)

$ git push origin slow-blink (6)
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
  1. 把我們 fork 的專案克隆一份到本機

  2. 建立名稱有意義的主題分支

  3. 對程式碼作變更

  4. 確認這個變更一切 OK

  5. 把變更加入我們的主題分支

  6. 把我們的新分支推送回 GitHub 的 fork 上

如果我們回到我們在 GitHub 上的 fork,我們可以看到 GitHub 發現我們推了新分支上來,並且顯示了一個大大的綠色按鈕讓我們可以檢視我們的變更,並能對原始專案開啟一個 Pull Request。

你也可以去到 在 https://github.com/<user>/<project>/branches 上的「Branches」頁面去找出你的分支並從那邊開啟一個新的 Pull Request。

Pull Request 按鈕
圖表 90. Pull Request 按鈕

如果我們按下綠色按鈕,我們會看到一個畫面,要求我們針對這次的 Pull Request 編寫「標題」和「描述」。 花費一些心思在這上面總是值得的,因為好的說明可以幫助原專案的擁有者去確認「你所嘗試的事情」、「你提案的變更內容是否正確」以及「接受這些變更是否有改善到原專案」。

同時我們也會看到在主分支沒有的所有提交列表(在這個範例中只有一個提交),和一個彙整所有修改的差異資訊,以便讓其他人知道當原作者合併後會有哪些差異。

建立 Pull Request
圖表 91. Pull Request 建立頁面

當你按下畫面中的「Create pull request」按鈕時,你 fork 的來源專案的擁有者會收到一個通知,通知他有人建議一個變動並且會附上連往包含所有資訊的頁面連結。

筆記

雖然在這種公開專案上,Pull Requests 通常都是在貢獻者已經準備好要加入的變更時才會發出;但是它常用於專案「剛開始」時的一些內部專案。基於 Pull Request 在建立「之後」仍然可以持續加入新的變更的特性,因此也常會在初期建立當作一個團隊合作的環境,而非在最後才使用。

重複使用一個 Pull Request

現在呢,專案的擁有者可以閱覽所有建議的變更,然後決定要合併進來、拒絕變更或是對這留下評論。在這邊我們當作他覺得他喜歡這點子好了,但是他覺得燈暗掉的時間要比亮的時間長一點。

這個互動或許會透過電子郵件並依照在 分散式的 Git 提到的工作流程運作;而在 GitHub 上,這是在線上運作的。專案擁有者可以在審閱差異總表時,點一下想要評論的那行內容並留下評論。

Pull Request 行評論
圖表 92. 對 Pull Request 的某行程式碼下評論

當維護者留下評論,建立這個 Pull Request 的人(以及所有關注這個倉儲的人)都會收到通知。我們等等會對這做自訂,不過如果有開啟電子郵件通知,Tony 會這樣的一封信:

郵件通知
圖表 93. 以電子郵件型式寄送的評論

其他人也可以對 Pull Request 留下一般評論。 在 Pull Request 討論頁 裏面我們可以看到專案擁有者對某行程式碼做評論,同時也在討論區塊留了一般評論。你可以看到程式碼評論也會被帶到這個互動之中。

PR 討論頁面
圖表 94. Pull Request 討論頁

現在貢獻者就可以知道他要做哪些處理才能讓擁有者接受這個變更。 幸好這事也很直觀。 如果是透過電子郵件的話你需要把所有的變動重新執行一次然後重新上傳,但是在 GitHub 上你只要對主題分支再次做提交然後推送上去,即可更新該 Pull Request。 在 結束 Pull Request 中,你也可以看到 Pull Request 中舊的程式碼上的評論都被折疊起來,這是因為它所評論的程式碼已經被更新了。

如果是在已發出的 Pull Request 中再加入新的提交並不會觸發通知,所以一旦 Tony 以這種方式推送修正,他需要再留下一個評論以通知專案擁有者:他已完成所要求的修改。

結束 Pull Request
圖表 95. 結束 Pull Request

有個有趣的東西就是如果你點開 Pull Request 的「Files Changed」分頁,你會得到一份「統整過的」差異表 —— 也就是所有當這個主題分支被合併進主要分支時會做的變動。以「git diff」的方式來講,就是自動顯示給你對 Pull Request 指定的主題分支做「git diff master…​<branch>」的結果。看 決定要提到哪些資訊 來了解更多關於這種差異表的事情。

另外一件你會注意到的就是 GitHub 會確認這個 Pull Request 是否能直接合併,並顯示一個能讓你直接在伺服器上做合併的按鈕。這個按鈕只有在你對這個倉儲有寫入權限,而且能簡易的合併時才會出現。當你按下這個按鈕時,GiHub 會做一個「非快速向前」的合併,意味著即使這個合併「能」以快速向前的方式處理,GitHub 還是會建立一個合併的提交。

你可以基於你的偏好改用這樣的方式:pull 這個分支下來,然後在本機合併進去。如果你把這個分支合併進 master 分支並推送上 GitHub,對應的 Pull Request 會自動關閉。

大部分的 GitHub 專案都使用著這樣的基本流程。建立主題分支,基於這分支建立 Pull Request,針對這個做討論,可能還會在這分支上做更多變更,最後這個要求就被關閉或合併了。

筆記
不只有 Forks

有件很重要的事情是:你也可以對同個倉儲的兩個分支做 Pull Request。如果你跟別人在一個雙方都有寫入權限的專案編寫新功能;你可以推送一個主題分支到倉儲裡,然後以這個分支對同個專案裡的 master 建立一個 Pull Request,藉此來做程式碼審閱以及討論。這不需要 Fork。

Pull Request 的進階用法

現在我們已經講完關於對 GitHub 上的專案做貢獻的基本部份了。來看看一些讓你可以更有效率的使用 Pull Request 的小技巧吧。

把 Pull Request 做成補丁

有件很重要的事情是:很多專案並不會把這些 Pull Request 當成一系列可以乾淨確實的使用的完美補丁,就像許多基於郵件清單運作的專案對系列補丁貢獻的看法。大多數的 GitHub 專案是把 Pull Request 分支用來做多次對期望變更的交流溝通,並將結果集中在一個差異檔,並用其做合併。

這是個很重要的差異,因為通常變動會在程式碼完備之前就被提出,這點跟基於郵件清單的運作模式是天差地遠的。這讓維護者們可以更早做溝通,讓適合的解決方案可以在接受更多社群能量下誕生。當有人使用 Pull Request 提出程式碼,然後維護者或是社群建議了一個變更,雖然這補丁系列不會重來,但相對的會以一個新的提交的形式加入這個分支,並讓討論和背景可以齊頭並進。

舉例來說,你可以回到 結束 Pull Request 這邊看看,你會注意到那個貢獻者並沒有把他的提交重組之後另外開個新的 Pull Request,而是加入新的提交並推送到原本的分支。如果之後你回去看看 Pull Request,你可以看到之前我們為何做了這樣的變動的背景。當按下網站上的「Merge」鈕時,會建立一個參考那個 Pull Request 的合併提交,這樣當有需要時你可以很容易的就找到它並研究當初的交談內容。

跟上上游

如果你的 Pull Request 因為過期或是其他原因導致不能很乾淨的合併,你就會希望去處理這個問題來方便維護者合併它。GitHub 會自動對這點做測試並在頁面最下方告訴你這個 Pull Request 是否能簡易的合併。

Pull Request 合併失敗
圖表 96. 不能乾淨的合併的 Pull Request

如果你看到類似 不能乾淨的合併的 Pull Request 的畫面,你就會希望去對你的分支做修正讓那個標示轉綠,之後維護者就不需要做額外的事。

你有兩個方式可以來處理這個狀況。你可以用變基把你的分支接在目標分支 (通常會是你 fork 的專案的 master 分支) 上,或是把目標分支合併進你的分支。

大部分在 GitHub 上的開發者都會選擇後者,基於上個章節所提的理由:我們看重的是歷史紀錄和最終的合併,所以變基除了給你一個乾淨一點點的歷史之外,你得到的只會是「非常」大的困難並且更容易犯錯。

如果你想要先對目標分支合併使得你的 Pull Request 能被自動合併,你可以把原始倉儲設定成一個新的遠端、從上面擷取資訊、把那個倉儲的主要分支合併進你的主題分支,修正任何可能的問題,最後再推送回你的開 Pull Request 的 主題分支。

舉之前「tonychacon」的例子來說,原始作者做了一個會和 Pull Request 衝突的變更。所以我們來看看解決這個問題的步驟吧。

$ git remote add upstream https://github.com/schacon/blink (1)

$ git fetch upstream (2)
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 (3)
Auto-merging blink.ino
CONFLICT (content): Merge conflict in blink.ino
Automatic merge failed; fix conflicts and then commit the result.

$ vim blink.ino (4)
$ git add blink.ino
$ git commit
[slow-blink 3c8d735] Merge remote-tracking branch 'upstream/master' \
    into slower-blink

$ git push origin slow-blink (5)
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
  1. 將原本的倉儲新增為遠端,並取名為「upstream」

  2. 從這個遠端擷取最新的內容

  3. 把主要分支合併進你的主題分支

  4. 修正產生的衝突

  5. 再推送回同一個主題分支

當你做完上述步驟,Pull Request 會自動更新並檢查是否能乾淨的合併。

已修正的 Pull Request
圖表 97. Pull Request 現在能乾淨的合併了

Git 偉大的事情之一就是你可以一直重複這個過程。當你有個長期運作的專案時,你可以很簡單的重複對目標分支做合併,而你只要對最近一次的合併產生的衝突做處理即可,讓這個程序易於管理。

如果你一定要對分支做變基,你還是可以這樣做,不過強烈建議你不要強制對已經開了 Pull Request 分支做推送。如果其他人已經 pull 下來而且做了些變動,你會遇到所有在 使用衍和的危險 描述的問題。相對的,應該要把變基過的分支推送到 GitHub 上的新分支,然後建立一個參考至舊的 PR 的新 Pull Request,之後關閉原本的 PR。

參考

你的下一個問題可能是「我要怎麼對舊的 Pull Request 做參考連結?」。這有非常非常多的方法可以對其他東西做參考連結,幾乎所有你在 GitHub 上能撰寫訊息的地方都可以做到。

先從怎麼在 Pull Request 或是議題互相做參考開始吧。所有的 Pull Request 在專案裡都會被賦與一個獨一無二的編號。舉例來說你不能同時擁有 Pull Request #3 和議題 #3。如果你要在 Pull Request 裡參考其他的 Pull Request 和議題,你只要在評論或描述打下 #<num> 即可。你也可以指定參考不在同個專案裡的專案;如果是在同一個倉儲的 fork 裡你可以用 username#<num> 指定,或是 username/repo#<num> 來指定別人的其他倉儲裡的專案。

來看個範例吧。假設我們們在上個範例選擇使用變基處理分支,並為此開了新的 Pull Request,之後我們想要在新的 PR 裡放個參考連結到舊的。而且我們也想要參考一個在這個倉儲的 fork 裡的議題,還有一個在完全不同的專案裡的議題。我們的描述就可以用 Pull Request 裡的跨倉儲參考 裡的寫法。

PR 參考
圖表 98. Pull Request 裡的跨倉儲參考

當我們送出這個 pull request,我們可以看到內容被渲染成 在 Pull Request 中被渲染後的跨倉儲參考。 裡的型式。

渲染過的 Pull Request
圖表 99. 在 Pull Request 中被渲染後的跨倉儲參考。

可以注意到那完整的 GitHub 網址被簡化了,只留下必需的資訊。

如果 Tony 現在去關閉原本的 Pull Request,當我們在新的 PR 標記它時會得知這件事情,因為 GitHub 會自動在 PR 的時間線上對這件事做反向追蹤。這意味著所有造訪舊的 PR 頁面的人會知道這個 PR 已經被一個新的 PR 取代了,並且能簡單的透過連結造訪新的 PR 頁面。這連結看起來就是 在 Pull Request 中被渲染後的跨倉儲參考。 這樣。

已關閉的 PR
圖表 100. 在 Pull Request 中被渲染後的跨倉儲參考。

除了議題編號之外,你也可以用 SHA-1 對一個提交做參考。你必須完整的標出 40 字元的 SHA-1,然後 GitHub 在評論裡看到那個 SHA-1 時就會產生該提交的超連結。而且你也可以用和議題一樣的方式,對其他 fork 甚至是其他倉儲的提交做參考。

Markdown

連結其他議題,對於你在 GitHub 大多數的文字方塊裡能做的有趣事情而言,只是個開始。在議題、PR 描述、評論、程式碼評論,以及其他更多的地方,你都可以使用「GitHub 風格的 Markdown」。Markdown 能以純文字方式編輯,但能渲染出豐富的內容。

看看 一個顯示出 Markdwon 撰寫型式和渲染結果的範例 的範例來看看文字和評論能怎樣撰寫,並接著以 Markdwon 的方式渲染。

Markdown 範例
圖表 101. 一個顯示出 Markdwon 撰寫型式和渲染結果的範例

GitHub Flavored Markdown

GitHub 風格的 Markdown 增加了許多你在基本 Markdown 語法做不到的事。這些在你要建立有用的 Pull Request、議題評論、描述時,會非常的有用。

工作清單

第一個專屬於 GitHub 的 Markdown 功能,特別是在 Pull Request 上,就是工作清單。工作清單就是一系列對應到你想要完成的事情的核取方塊。把這放在議題或是 Pull Request 裡時,通常表明了你想要完成的事項。

你可以建立這樣的工作清單:

- [X] 撰寫程式碼
- [ ] 撰寫所有的測試項
- [ ] 為程式碼做文件

如果我們在 Pull Request 的描述裡或是議題裡加入這個,我們就能看到他被渲染成像 Markdwon 評論裡渲染後的工作清單。 這樣。

工作清單範例
圖表 102. Markdwon 評論裡渲染後的工作清單。

這個功能在 Pull Request 裏面,常被用來聲明在合併之前,你想要在這個分支裡完成的事情。最酷的地方就是你只要點下核取方塊就能更新你的評論-你不需要為了標記工作完成而得修改 Markdown。

除此之外,GitHub 還會把議題和 Pull Request 裡面所有的工作清單整理起來,把它們作為後設資料顯示在 Pull Request 的清單頁面。舉例來說,如果你的 Pull Rewquest 裡面有工作清單,你可以在所有 Pull Request 的總覽頁面上看到進度。這讓人們得以把一個 Pull Request 分解成數個小工作,同時也便於其他人追蹤這個分支上的進度。你可以在 在 Pull Request 清單裡的工作清單統整。 看到關於這個功能的範例。

工作清單範例
圖表 103. 在 Pull Request 清單裡的工作清單統整。

當你在實作一個功能的開始就開了 Pull Request,並使用工作清單追蹤進度時,這個功能會驚人的好用。

程式碼摘錄

你也可以在評論裡摘錄某段程式碼。當你想要展示某段還沒提交到分支的變更時,這會非常的有用。這在展示無法正常運作或是這個 Pull Request 可以實作的的程式碼時也會用到。

你摘錄的程式碼需要用反引號「包」起來。

```java
for(int i=0 ; i < 5 ; i++)
{
   System.out.println("i is : " + i);
}
```

如果你在上面那段代碼的 java 的位置放上其他的程式語言名稱,GitHub 也會試著做語法上色。如果以我們上面的範例來說,上面那段代碼最後會被渲染成 被渲染過的嵌入程式碼片斷 的樣子。

渲染過的嵌入代碼
圖表 104. 被渲染過的嵌入程式碼片斷
引文

如果你要對一長段評論的一部份做回應,你只要複製你需要的片斷,然後在前面加上 > 即可。事實上,因為這個功能因為太實用也太常用到,所以有一個專用的快捷鍵可用。如果你把評論你要回應的文字反白起來,並按下 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?

經過渲染之後評論就會變成 渲染過的引文範例 這個樣子。

渲染過的引文
圖表 105. 渲染過的引文範例
表情符號

最後就是你可以在評論裡使用表情符號。這個東西很常出現在許多 GitHub 議題和 Pull Request 的評論裏面。GitHub 上甚至有表情符號工具。如果你在評論裡用了 : 當作開頭,自動完成會協助你找出你想要的表情。

表情符號自動完成器
圖表 106. 表情符號的自動完成提示

你可以在評論的任何地方打出 :<name>: 來使用表情符號。舉例來說,你可以寫出這樣的東西:

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:

經過渲染之後會變成這樣 使用大量表情符號的評論

表情符號
圖表 107. 使用大量表情符號的評論

雖然不能說它是是個非常實用的功能,但它能在這種不方便表達情緒的媒介裡,加入了由趣味和心情構成的元素。

筆記

事實上現在有不少的網路服務可以在上面使用表符字元。這邊有張非常好用的大抄可以讓你很快到找到能表達你現在情緒的符號:

圖片

技術上來說,雖然這並不是 GitHub 風格的 Markdown,但是還是非常的實用。如果不想用 Markdown 圖片語法這種很難知道是什麼圖片的方法之外,GitHub 允許你以把圖片拖曳至文字方塊的方式來嵌入圖片。

圖片拖放
圖表 108. 以拖曳的方式來上傳並自動嵌入圖片

如果你回到 Pull Request 裡的跨倉儲參考,你會在文字區塊上看到一個小小的「Parsed as Markdown」提示。點一下那個提示,他就會提供你包含所有在 GitHub 上可以用 Markdown 做的事的小抄。