如何運用GitHub來提高生產效率
這是一篇GitHub的入門級文章,主要針對git的初學者。我們將討論初學者最關心的一些問題,如:為什麽我們要使用GitHub,它的應用有哪些,如何運用它去幫助我們提高工作效率,以及它的基本用法有哪些。
希望看到文中的相關資源鏈接的朋友,可以直接訪問我的中文blog:https://www.terencexie.com 。
在展開討論GitHub之前,其實需要澄清一個在初學者腦中不太清晰的概念:GitHub和git是一回事嗎?
其他人我不知道,但我第一次接觸GitHub的時候,幾乎沒有註意到GitHub和git是兩個東西。更誇張的是,我甚至直接在腦中無理由暴力腦補“嗯,說不定git是GitHub的一個簡稱。”現在想來當然極其可笑,但作為新人不得不面對的一個現實是:新人之所以是新人不是因為他在某一方面很薄弱,而是各方面都千瘡百孔。像這樣的不經過任何調查、也沒有任何官方文檔或者實驗數據作支持,冒然做出一個推論並把它奉為一種真實,是進一步走向混亂的重要因素。
git和GitHub其實是兩個有聯系但卻根本不同的東西。用一種不太嚴格的說法,可以這樣理解它們的關系:GitHub是支持git的remote端(服務器端)的一個服務。它就好比是你的雲端服務,用於保存你本地的代碼版本控制的所有記錄。再介紹了git之後,我們會再一次澄清和展開這個概念。
什麽是git
git是寫出Linux內核的大神Linus,搗鼓出來的代碼版本控制工具。雖然其動機是服務於代碼,但其實對非開發者的普通用戶,也是很有用的工具。
什麽叫做版本控制?
我們可以從比較熟悉的遊戲存檔談起。在玩RPG遊戲的時候,一個非常重要的基礎功能便是能夠存儲當前的遊戲狀態。因為你幾乎不可能在完全不停止的情況下一次性通關。停止你遊戲story展開的因素有很多,例如:
- 你玩遊戲的進度很慢,到了睡覺時間,也就不得不停止,下次再戰。
- 你悲劇地遭遇了電腦的突然中斷,不得不重新進入遊戲。
- 一切都很順利,但你卻不幸被遊戲中的大boss幹掉了。
有了遊戲存檔功能,一方面你可以再一次從你中斷的地方繼續遊戲,另一方面,如果你的遊戲有多條分支線story,你可以從分支的地方繼續開始,選擇另一條路線體驗不同的經歷。
那麽,git幹的其實是同樣的事情。git其實就是為特定的文件目錄下的所有文件,提供一個存檔功能(這個特定的文件目錄,就是運行了git init
命令的文件目錄)。在git的視角之下,每一個時間點的文件目錄狀態,都可以作為一個存儲節點(同遊戲存檔一樣)。而像這樣的可以存儲一個文件的各個時間節點的文件狀態的功能,就叫做版本控制
git
提供的功能就是為這個文件目錄提供一個時間線,將這個文件目錄下發生的所有事情全部記載下來,起到一個類似於遊戲存檔的功能。
例如,假設我們在運行了git init
命令的文件目錄MyDirectory
下有兩個文件File1.txt
和File2.txt
。File1.txt
和File2.txt
這兩個文件各包含兩行文字,如下圖。
圖1.0
作為初級程序員的教程,這裏需要多說幾句,解釋一下為什麽這個例子可以不失一般性,作為其它所有文件的代表。因為所有的文件在其底層的存儲方式都是字節序列。從計算機的角度來看,所有的文件就是一行行的文字而已。所以,如果理解了git
如何去處理一個人類可以識別的文本文件。那麽,對於任何文件來講,其原理也是相同的,不過是處理一行行計算機認識的文字,即一堆0、1序列文字流。
如圖1.0,這裏便可以作為MyDirectory
的一個當前時間點的文件狀態。當MyDirectory
下的任何一個文件有所改動,
圖1.1
那麽此刻的狀態,就好比是遊戲當中的主角在地圖上移動了一個位置,可以作為另一個時間點的文件狀態(圖1.1)來做存儲。
可以發現,到目前為止的討論,都和GitHub無關、和remote端的服務器沒有關系。因為git
的使用可以僅僅限於本地,和網路連接無關。因而,對於普通用戶來說,完全可以使用git
來為你的辦公文件、私人文件做存檔。存檔與備份的不同在於,後者只能夠存儲一個文件狀態,而前者卻可以存儲多個文件狀態,也即是將整個文件的時間線全部存儲下來。有了它,你便能夠去了解這個文件的整個衍化、生長歷史,從而對它有更加深層次的認識和理解。
關於git
的優質教程我會推薦兩個,一個是Udacity的課程How to Use Git and GitHub,另一個是git的官方文檔教程Pro Git,裏面提供了可以下載的多種格式的電子版。
常用的git命令
為文件目錄提供版本控制功能
最開始的命令當然是git init
,用於為一個文件目錄提供版本控制的git
功能。例如,想要為文件目錄/Users/Terence/Documents
提供版本控制的功能,那麽只需要兩步:
- 將命令行切換到目標文件目錄下:
cd /Users/Terence/Documents
。 - 運行
git init
。
此時, /Users/Terence/Documents
目錄便具備了版本控制功能(其實質是在這個目錄下創建了一個.git
隱藏文件夾)。
版本控制下的文件修改和提交
接下來要討論具備了git
監控的文件目錄下,一個文件會呈現的幾個狀態。在git
的體系下,一個文件可以具備四種狀態:
- Untracked
- Unmodified
- Modified
- Staged
不同狀態的同一個文件可以同時彼此獨立存在。下面根據Pro Git 2.2上的一個文件的周期圖來做講解。
Git File Lifecycle
下面以從上到下的順序講解這幾個箭頭相關聯的狀態切換。
首先是文件的untracked
狀態,指的是還未被納入git
存檔體系的文件。雖然這個文件目錄通過git init
具備了存檔的功能,但還未指定哪些文件的時間線可以被存檔。於是這些未被納入存檔的文件的狀態便是untracked
。 通過命令git add <filename>
便可以將它納入待存檔(staged
)的狀態。(這裏可以跳躍一個步驟,提前講解它最後的狀態切換,從通過命令git commit <filename>
讓待存檔(staged
)狀態的文件進入到git
庫。此時就變成了git
庫的unmodified
狀態。)
再來是文件的unmodified
(未修改)狀態。一般在git
界面中將文件名顯示為藍色。一個文件要成為unmodified
狀態,首要的前提是它已經被納入了存檔體系、並且它已經被存檔。也就是說,它一定是從待存檔(staged
)的狀態,通過執行命令git commit <filename>
(提交到git
庫,也就是把當前的這個狀態存儲好)後所處的狀態。
然後是文件的modified
狀態,也就是對一個文件做了修改後的狀態。一般在git
的界面將文件名顯示為紅色。例如,假設圖1.0都所有文件都是unmodified
狀態,我們對File2.txt做了修改成為圖1.1。此時,File2.txt就是modified
狀態。
最後是文件的staged
狀態,也就是待存儲狀態。一般在git
界面中將文件名顯示為綠色。這個staged
狀態是非常微妙的一個狀態,它能夠與modified
狀態同時存在。
舉個例子:現在我們假設在圖1.1的基礎上運行了命令git add File2.txt
,那麽此時的文件狀態就變成了:
圖1.2
也就是Yoga那一行變成了黑色,也就是unmodified
狀態。如果我們對它再做修改,添加文本L1:
圖1.3
也就是說,此時添加了L1的那一行全部變成了紅色。你或許會提出一個疑問,我明明只添加了字符L1,為什麽整行都受到了影響?這是因為,git
是以行為單位來考察文件是否有變化。每一行做相應對比,只要有不同,就把整個一行當作有變動的內容處理。
再運行git add File2.txt
,得到:
圖1.4
可以看到,已處於暫存狀態staged
的做了改動的那一行變成了綠色。
微妙的地方來了,如果此時我們不提交我們這個暫存狀態staged
,而是繼續修改File2.txt
,將剛加入的字符串L1刪去,則我們會得到這個文件的兩個狀態:
- 一個處於
staged
的圖1.4的狀態; - 另一個則是處於
modified
的狀態圖1.5
圖1.5
註意,這裏圖1.5和圖1.1雖然是一樣的,但表針的意思完全不同。圖1.1是從沒有Yoga的狀態,添加了Yoga字符串後形成的。而圖1.5是從狀態Yoga. L1刪除了字符串L1後形成的。
此刻,如果我們再運行命令git commit File2.txt
,我們提交到git
倉庫的代碼將會成為具備Yoga. L1的圖1.6
圖1.6
同時,File2.txt
依舊會保持modified
的狀態圖1.5。
希望這個例子能夠幫你看出這裏的微妙差別。
總結起來,我們這裏涉及到的命令有:
git init
讓文件目錄具備git
存儲功能。git add <filename>
將未被跟蹤的文件(untracked
),或者modified
的文件,添加進暫存狀態(staged
)。git commit <filename>
將暫存狀態的文件(staged
)添加到git
倉庫,成為unmodified
狀態。
什麽是GitHub
GitHub是一家公司,它提供的服務是為用戶提供git
的遠程(雲端)的git
倉儲。其想法就是,你本地有一份文件的版本控制倉庫,裏面保存了所有文件的時間線。但是,如果你更換了一臺電腦,想要再次重現以前那臺機器的版本控制倉庫,就會很麻煩。於是,一個解決方案便是,為何不將我的這個倉庫系統放到雲端。既可以隨時將本地的修改放到雲端,納入正式的代碼庫,也可以在本地保留一份自己的代碼倉庫,做自己的修改。
更具吸引力的是,一旦你的代碼放在了雲端,你就可以同其他人合作,在世界的不同地方推進同一個項目。大家保留各自對代碼的修改,讓雲端的代碼庫成為大家認可的main stream正式存儲倉庫。大家可以在得到彼此的認同後(也就是後面要講到的對Pull Request (PR)
的處理),將自己的這份代碼修改簽入到雲端的倉儲庫,也就是GitHub了。
所以,從這個角度講,GitHub是一個提供了雲端的代碼版本控制的git
倉庫平臺。從這個意義上講,GitHub確實是技術人員的社交平臺,社交平臺中的發朋友圈、發Facebook狀態消息等價於不斷地展現自己對代碼的修改更新。大家是在通過自己的項目狀態,來展現各自的工作進展以及最新的項目進展。仔細研究這些版本控制的歷史,就能夠還原出這個項目、這個工程師的整個成長歷史。
在我看來,使用GitHub的目的無非是兩個:
- 學習他人的代碼,並希望重現這個項目的結果,或者作出自己的貢獻;
- 或者是將自己的工作遷移到GitHub上,使得自己的工作能夠得到一個雲端的備份,或者能夠更方便與在不同地理位置的人合作。
這裏,我想展開談一下這兩點的細節和初學者的疑問點。它們本身並不需要太高深的技術來解決。只是,它們會成為初學者的一道屏障。澄清這些模糊的概念,可以極大地提高初學者對GitHub的掌握效率。
使用GitHub學習他人的代碼
寫代碼如同寫文章,要做出好的工作,需要有大量的高質量閱讀作前提。GitHub上有大量的優秀項目供人閱讀。Programming的各種精髓和技巧,便藏在這一個個優秀的代碼細節中。
通過一般的參考資料,很容易將這些優質的代碼clone
到本地,或者更加簡單粗暴直接download整個項目的壓縮包到你的本地文件目錄。
然後,問題來了:源代碼也到手了,可是,怎麽把這堆代碼變成所需要的目標軟件產品呢?編譯、運行?可是,從哪裏去編譯、運行呢?
這個問題的提出,其實涉及到學校的小項目和真正的工業界的大項目的實踐區別。或許在學校學習C
、Java
、Python
的時候,只需要在命令行或者IDE 界面點擊個運行就可以了。
可是,在真實的復雜項目中,編譯代碼會涉及到更多的細節和步驟。在編譯之前需要更多的環境配置、資源準備以及腳本運行。舉一些例子,當你的項目足夠復雜後:
- 需要編譯的的源代碼本身或者部分,是需要通過某個文本文檔去生成的。而如何從這個文本文檔中提取出需要的信息再生成相應的代碼,又是由一個腳本所控制的。
- 在編譯你的源代碼前,需要你提供一些第三方的代碼庫或者代碼包。比如
Java
的第三方jar
包。而這些第三方的資源文件,是需要你提前準備呢?還是可以通過一個腳本自動獲得呢? - 上面提到的工具腳本,或許是由某個/某幾個特定語言編寫的。你當前環境中,是否提供了支持這些語言的環境呢?
上面提到的這些問題和可能性,遠遠不是僅憑借源代碼就可以推斷出來的。所以,拿到這堆源代碼不知所措,其實是很正常的。
那麽如何解決這個問題呢?
我的回答是,看你的運氣。
如果你恰好遇到的項目是由習慣不大好或者是不喜歡寫文檔的程序員寫的,或者這個項目的貢獻者本身不希望你知道編譯環境,估計你也就只能看著這堆代碼發呆了。
如果你運氣不壞,那麽,真正的嚴肅項目,會在它自己的文檔(通常是這個項目根目錄的README
文件)詳細說明如何部署編譯環境:它包括需要提前準備哪些編譯工具、哪些第三方的代碼庫、如何調用編譯的腳本等等。
所以,當你在GitHub上看到一個項目時,先別急著做download的動作。先仔細閱讀這個項目的文檔,看看它是否足夠優秀。通常,文檔的質量和項目的質量成正相關。用業界俗語來解釋原因就是:寫文檔就是寫代碼,文檔寫不好,代碼通常也是一團糟。
如何在GitHub上工作
如果僅僅是簡單的把GitHub當作自己的一個代碼備份庫,其實實踐操作並不復雜。無非是不斷地在本地修改好代碼,再將自己認可的代碼簽入到遠程端的GitHub代碼庫。
真正有意思的,是在GitHub上與他人合作,一起完成同一個項目。
要能夠一起合作,首先需要在你的項目中添加你合作者的GitHub賬號。這個只需要在GitHub的項目界面中,點擊Settings —> Collaborators
,在裏面添加你的合作者。這樣,你的合作者便獲得了對這個項目的更改權限,才有資格對這個GitHub上的項目倉庫提交代碼。
如果你們同屬於某一個GitHub上的Organization,那麽這個Organization的管理員通常會設定其成員,自動擁有這個Organization下所有項目的更改權限。
下面談談GitHub上的工作流程。作為git
的核心,它是以branch
做核心驅動的。master
branch作為主分支,一定要保證它是可以運行的和經過測試的。而你可以在其它的branch
上做自己的feature開發、實驗和測試,而完全不會影響代碼庫的主分支master
branch。
那麽,在GitHub上是同樣的。當幾個人組成一個團隊合作一個項目時,每個成員不應該輕易地直接提交代碼到master branch
,即便是你有這樣的權限。作為良好的實踐規範,你永遠應該在自己的私人分支上做代碼修改和實驗,然後將這個分支push
到GitHub,在GitHub上對這個分支執行Pull Request
來讓團隊的其他人員review你的代碼。當reviewer認可了你的代碼後,你才能把你的修改分支merge
到master
。也就是說,任何人對master
的代碼提交之前,必須有其他人的code review這一步;沒有其他人的coder review,你不可以向master
分支提交代碼即便是你有這個權限。
而在這個Pull Request
的過程中,審核者和被審核者可以對代碼的每一行做深入的討論。每一行代碼都可以插入相應的評論,供大家去做深入研究。這些討論,是整個代碼設計的精髓,也是代碼功底提升的真正關鍵。再次回到那句老話,寫文檔即寫代碼,它傳達的精神便是:你的思想和看法才是支撐整個代碼的核心。而某種具體編程語言的書寫只是這種思想的一種表達。你只有先整理好了思路、要解決的問題的脈絡,你才可以寫出清晰的代碼。
甚至,在所謂的合作而其實是師傅帶徒弟的過程中,這些code review的價值會遠遠高於push code。因為push code只是一個結果,而code review的comments討論,才是幫助你指正問題、更改壞習慣的核心所在。
而往往初學者又不太明白這一點,誤以為這些comments的討論是配菜,對這些comments的討論極其不耐煩,一心只想把code簽入到代碼庫。所以,作為mentor他其實是有責任向自己的apprentice指出這個差異與優先級的。
讓我們再強調一次,無論作為學徒還是作為平等的合作者,那些review過程中的comments交流才是整個代碼的核心,它們是整個項目的指導思想與精神綱領。只有在review的過程中將問題一步步弄清楚了,你才能夠更加容易和自信地修改代碼,才能夠保證代碼庫的質量水準。任何輕視code review或者review中的comments的行為,都是一種天真的自我欺騙。你需要拿出同寫代碼一樣的嚴肅認真,來對待整個code review,來對待review過程中的comments交流。
近期回顧
《打造讓用戶為自己尖叫的產品》
《2017年10月寫字總結》
《平臺框架-101》
如果你喜歡我的文章或分享,請長按下面的二維碼關註我的微信公眾號,謝謝!
VIP贊賞專區
如何運用GitHub來提高生產效率