我來說說 Git 和 GitHub
本文旨在運用淺顯易懂的文字,解說版本控制背后的理論,以便你能對程序員們如何工作有個全局概念。本文不觸及代碼,不用下載啥東西,按部就班,不關注繁復細節,只要文字和一些不怎樣漂亮的手繪涂鴉。
寫本文的動機
學習任何東西都能在網絡上找到如此之多的指導教程,這不斷令我詫異不已。Git 和 Github 也不例外,網絡上有大量優秀資源,這些資源要么只對其中一個,要么統籌二者引導你開端學習。以下是我特別喜歡的一些資源:
- Treehouse – 寫給設計師的 Git 入門介紹
- Roger Dudler – Git 簡易教程
- Pluralsight – Github:初學者指南
然而,我發現這些教程總是跳過許多理論知識,直接解釋如何通過命令行或 Github 桌面應用程序使用 Git 。坦白說,如果你只是想知道你的開發團隊談論的到底是什么,這些指導教程也綽綽有余了。如上所述,我的目標是對版本控制的整體概念進行簡明扼要地講解,同時希望能讓你了解到版本控制是如此酷。
讓我們從最基礎的開始:版本控制
Image credit: weebletheringskite, WordPress
版本控制(Version control):學習它,愛上它,享受它。顧名思義,版本控制系統是任何能讓你了解到一個文件的歷史,以及它的發展過程的系統。之前作為平面設計師時,我常常會遇到這種文件:
看起來眼熟?盡管上述系統不是一個好用的系統 ,但它確實是一個版本控制系統。更復雜點的例子就像,Google 文檔的 “修訂歷史”或? Photoshop 的“歷史”工具。
?開始 Git
Git?是一種專為處理文本文件而設計的版本控制系統。因為,歸根到底,這就是代碼的本質:一堆堆以某種方式聯合在一起的文本文件。Git 是一個可安裝應用,它允許你對你自己所做的更改進行注釋,用以創建易于導航的系統歷史。
(附: “Git” ?也是工程師取的名字,我們對市場部同仁感到抱歉)
那么,?Git?做了什么,是簡單地保存文件所做不到的呢?從根本上講,文件保存就是一個簡化的版本控制系統,但坦率地說,它并不是一個好用的系統,因為它只能前進。當然,你也許會爭論“撤消”按鈕可以讓你的文件回滾到以前的狀態。但我們都清楚,“撤消”按鈕有其局限性,最明顯示的是,在關閉文件時,文件的過去也隨之丟失。
另外,文件保存是非常個人化的。它不能夠顯示整個系統的歷史,只能夠顯示該文件的。針對這一點,你可能會想,“嗯,我不是一個工程師,我不需要為系統煩惱”。我愿意花些時間來解釋一下,很多事情你認為不是“系統”,而實際上它們就是。
以? Sally為例,她是一個正在寫下一個大冒險奇幻小說系列的作家。Sally 已經寫完該系列小說中的第一本,并把它傳給了她的編輯。此外,由于她才華出眾,在等待編輯的反饋的同時,她還寫了第二本書的前三章。每本書都建立了獨立的 World 文件。
在某個快樂的日子,Sally? 等來了她的編輯關于第一本書的反饋。他擔心年輕的讀者不想讀一系列專寫獸人故事的書,希望她在這個故事中引入一些精靈。關于這點,Sally? 嘆了口氣,但很快意識到,她的精靈新角色將帶來始料未及的沖突和曲折的情節。然后,她做了以下事情:
- 在第一本書中加入新角色,并修改故事情節。
- 完成第一本書之后,對第二本書的故事情節,進行必要的修改。
- 所有的這些修改,導致她需要引入某個地理位置到第一本書中,而不是第二本書。
- 重新編輯第一本書,讓它包含新的地理位置。
終于,她推開了她的鍵盤,確信已經把精靈融入到了她的奇幻世界之中。
你瞧,Sally 實際上在處理一個系統。她的兩本書互相影響。角色、地理位置和故事情節在兩本書中流動交織。然而,遺憾的是,一個月后,她的文件系統里什么都沒有了。Word 的 “文檔歷史”工具,或她曾經粘貼在顯示屏邊緣用于記錄修改過程的便簽紙,將把所有的變化過程都揉合在一起。
這正是Git 大放光芒之處。如果 Sally 一直結合 Git 使用Word,她就能對所有這些相關的變化做一個關于“將精靈引入到系列”的簡潔小結。她可以看到所有穿插在頁、章節、文件,以及每本書中的修改記錄,讓她真正地了解引入精靈對她的奇幻系列產生的影響。這個“簡潔的小結”就是我們在 Git 領域中所講的提交(commit)。
回顧一下。 Git 是一個軟件,它允許你通過提交對一個系統(或一組)文件的歷史進行注釋。這些提交便是在給定時間點對系統做出的差異“快照”。
那么,如果我是Sally,我的提交歷史看起來是這樣子的:
Github
到目前為止,一切都還不錯。但是,如果 Sally 同時用到兩臺電腦工作,將會發生什么呢?問得好。這時,就該用到 Github了。注意,不要和 Git 混淆了。Github 獲取 Git 中的提交歷史,并將其存儲在互聯網上,因此你可以從任一一臺電腦訪問它。你在本機(例如:你當前正在使用的電腦)推送(pushing)提交到 Github,然后,從另一臺新的或不同的電腦上拉取(pulling)這些提交。
讓我們假設上圖為 Sally 的工作流程。她在家里的臺式電腦(左邊,橘黃色的)上開啟她一天的工作。接下來,她完成了幾個章節的寫作,又做了一些編輯工作,等等。整個過程中,她對工作總共進行了三次策略性的“快照”(Git 提交)。
下午,Sally 常常喜歡帶著她的筆記本電腦(上圖中的右側,藍色的)去咖啡館寫作。今天也不例外。因此,在關閉家里的臺式電腦之前,她需要確認當前的Git 提交歷史已推送(push)到了在線Github。一旦被上傳到 Github,這些提交記錄就被存儲在遠程倉庫(remote repository)中。
我們先來分析一下幾個計算機術語:遠程(remote)僅僅意味著聯網(與“本地”的意思相反,和之前我們理解到的意思一樣的,代表當前正在使用的電腦)。而倉庫(repository,經常簡寫為“repo”),就是一個具備 Git 超級權限的文件夾。
因此,Github 就是讓你把工作(通過Git提交進行注解)存儲在了一個指定的在線文件夾(repo)。明白了吧?簡單。
午餐之后,在當地的一家咖啡館中,Sally 拿出了她的筆記本電腦。很明顯,她想接著家里的工作進度繼續。因些,她從 Github 倉庫上獲取到最新進度的工作。“從 Github 上獲取她的工作”,這一過程就叫拉取(pulling)。再看一下上面這幅圖片,你將看到 Sally 拉取了之前她在家時進行的三個提交。
現在,在她的筆記本電腦上,Sally 有整個系統(包含她的幻想系列的所有文本文件)的最新的完整副本,并能夠基于上次的進度,繼續工作。她寫了更多的章節,對工作進行了兩次以上的策略“快照”(提交)。最后,Sally 把這些提交推送(push)到 Github 上,結束了這一天的工作。這樣第二天上午的時候,在家里的臺式電腦上就可以取得這些最新進度的工作。
協同工作
好吧,這一切都能說得通。但是, Sally再如何酷,整個項目也只有她一人而已。工程團隊要如何確保他們的工作不會重疊?
簡而言之,創建分支。將你的 Git 提交歷史想像成一棵樹。樹的主干就是我們談到的主分支。為了讓團隊成員避免彼此牽扯,他們在獨立于他人的隔離區(在一個功能分支)進行工作,然而最終,每個人的工作成果都會被提交到主代碼庫 (主分支)。
現在,回到 Sally 的例子。她加入了奇幻作家協會,在這里每個人都與他人合作完成這本書——《奇幻系列生物辭典》。這本辭典更像一本教材,由多個作者共同完成:Sally、Tom 和 Adam。
讓我們來看看《奇幻系列生物辭典》項目的在線 Github 倉庫,現在的情況是:
如上圖所示,樹的類比完全適用于奇幻作家協會在這個項目上的合作情況,倉庫歷史沿主分支向上移動。常規工作流始于每個作者為完成一個工作任務(例如編寫章節內容,或排版章節)而在主分支上創建分支。只有當更改得到其他合作作家的批準時,分支才會被合并到主分支上(請謹記,主分支上的內容,才是最終要發布的內容)。
當一個分支的內容合并(merged)到主分支時,意味著該分支的內容會覆蓋主分支上的。因此,現有內容的任何更改都將會替代之前的。當然,任何新添加的內容也會添加到主分支。實際上,當分支合并到主分支時,該分支的提交歷史被添加到主分支提交歷史的頂部。
然而,你可能正在思考:人們在本機的工作和之后才推送到 Github 的工作變更是如何連接到一起的呢?
關于這個問題,重點在于:你在 Github 的遠程倉庫是你本機工作項目的一個鏡像。這意味著,你在自己的電腦里存儲了該項目(例如:一個已設置可進行 Git 提交的文件夾)的本地 Git 倉庫。在這個本地的 Git 倉庫(再次,這是一個特定術語,指你的電腦里某個啟用了 Git 功能的文件夾)中,你擁有與該項目相關的所有文件,在本文的例子中,即《奇幻系列生物辭典》。
它的工作原理很像 Dropbox :你在不同的設備(你的家庭電腦、辦公室電腦,等等)上創建本地文件夾,進行工作并更新這些文件。最后,這些操作被同步到網絡上。然而,我們知道,Git/Github 工作流還包含了一些額外的步驟。首先,你必須有意識地對某一時刻的工作執行“快照”(即執行一次提交)。然后,你必須特意地推送這些提交(push) 到 Github。只有這樣,你的工作才被同步到網絡上的位置(Github 版本庫)。
既然如此,為什么不自動化該工作流呢?為什么不讓它像 Dropbox一樣,當你更新本地文件時,同時自動更新 Github 上的文件?有很多理由讓我們不這么做。最主要的理由是——bugs 。同出版界一樣,軟件工程中也不是所有寫過的東西都要保留。有時,你希望實驗一下你的想法,如果實驗失敗,你希望有一種簡單的方式能讓工作快速回滾到之前的正確狀態上。這也是為什么我們提倡這個經驗法則,即在你試圖用不同的方法編輯或實驗之前,先對當前你希望保留的修改進行提交。頻繁地提交小塊工作有益無害,事實上,許多工程師為自己能做到這一點而感到自豪。
現在,回到《奇幻系列生物辭典》。由于? Sally 對獸族有較深的了解,她被挑選為寫獸族章節。但她不想在沒有經過其它合作人員允許的情況下去修改這本書,于是,她創建了一個本地分支,并在該分支上進行寫作和提交。然后,她將本地分支推送到 Github。像往常一樣,Github 的遠程倉庫是本地庫的一個鏡像,最新進展顯示 Sally 已創建了一個包含部分提交的分支(如下圖所示)。
隨著她對本章節的持續寫作,Sally 進行了更多的提交,并將它們推送到 Github 的在線鏡像分支。終于,她準備請 Tom 和 Adam 一起對她的工作進行評審。因此,她在 Github 上發布了一個?Pull Request(發布請求),這是一個 Github 功能,允許她解釋該分支相對于主分支做了哪些修改。Github 還提供了一個簡易平臺,合作人員可以在該平臺上針對分支的修改內容進行討論,并要求 Sally 在分支合并到主分支之前對一些有異議的內容進行修改。
在對部分內容請求修改后,如上圖所示,Tom 和 Adam 對 Sally 的分支內容很滿意,并決定將她的工作成果合并到 Github 的主分支上。此時,他們所要做的就是將 Sally 之前獨立提交的內容,添加到主分支的提交歷史頂部:
此時,Sally可以切換(或“check out”)到本地計算機上的主分支,并將先前在功能分支(獸人章節分支)中的獨立提交拉取下來。現在她又要在新的主分支上重新開始了:以該主分支為基礎為她的下一步工作創建一個新的本地分支,幫助湯姆編輯有關妖精的章節。因此,這一過程又將重復:
- 創建本地分支
- 在本地分支上編輯修改,然后提交
- 推送提交(Push)到 Github
- ?創建發布請求(Pull Request),說明該分支包含了哪些更改
- 合并(Merge)分支內容到主分支
- 將主分支上的最新提交拉取(pull)到本地
- 重復上述步驟
正如你所看到的,這是一個非常流暢的工作流,完美地結合了獨立工作與團隊協作。你本機的 Git 提供了一個絕妙的方法,即通過由你自己控制和策劃的豐富的歷史提交,來創建你工作的各種版本。Github 是一個非常棒的在線版本控制工具,不僅存儲和提供了清晰的可視化歷史記錄,而且還能進行協同工作和質量控制。
總而言之,我希望我已經說服你去嘗試使用 Git ?和 Github 進行任何項目。沒有理由只有工程師能從這個很棒的工具中受益。畢竟,我們也想看到更多有關獸人的故事。
致謝:
非常感謝?Common Craft?對本文的涂鴉和解釋風格的啟發。還要感謝這個視頻《?saving me from the horror of having to explain Twitter to my mum?》。
本文涉及的術語
- Version Control(版本控制):?任何一個能夠讓你了解文件的歷史,以及該文件的發展進程的系統。
- Git:一個版本控制程序,通過對變更進行注釋,以創建一個易于遍歷的系統歷史。
- Commit(提交):在指定時間點對系統差異進行的注釋 “快照”。
- Local(本地):指任意時刻工作時正在使用的電腦。
- Remote(遠程):?指某個聯網的位置。
- Repository (倉庫,簡稱 repo):配置了Git超級權限的特定文件夾,包含了你的項目或系統相關的所有文件。
- Github:獲取本地提交歷史記錄,并進行遠程存儲,以便你可以從任何計算機訪問這些記錄。
- Pushing(推送):取得本地Git提交(以及相關的所有工作),然后將其上傳到在線Github。
- Pulling(拉取):從在線的Github上獲取最新的提交記錄,然后合并到本地電腦上。
- Master (branch):主分支,提交歷史 “樹”的 “樹干”,包含所有已審核的內容/代碼。
- Feature branch(功用分支/特性分支):一個基于主分支的獨立的位置,在再次并入到主分支之前,你能夠在這里平安地寫工作中的新任務。
- Pull Request(發布懇求):一個 Github 工具,允許用戶輕松地查看某功用分支的更改 (the difference或 “diff”),同時允許用戶在該分支兼并到主分支之前對其停止討論和調整。
- Merging(兼并):該操作指獲取功用分支的提交,參加到主分支提交歷史的頂部。
-
Checking out(切換):該操作指從一個分支切換到另一個分支。
馬哥學習交流群
?