一、什么是Git
答:Git是一個分布式版本控制軟件。另外提一句,它的開發者就是大名鼎鼎的Linux之父Linus。
版本控制,顧名思義,是一種在開發的過程中用於管理我們對文件、目錄或工程等內容的修改歷史,方便查看更改歷史記錄,備份以便恢復以前的版本的技術。(“后悔葯”)
分布式,是一種版本控制的方式,有一個中心的服務器控制最新版本代碼,每個開發者自己還有個本地倉庫,在開發過程中先將代碼提交到本地倉庫再推送到中心服務器上。
也就是說,Git可以幫助我們完成這幾件事:
回到過去
改變歷史
古今對比
並行開發
誰動了我的代碼
除了Git外,常見的版本控制軟件還有SVN等
二、Git基本原理
Git的基本操作有很多,如果不理解其中的原理只是靠死記硬背,那肯定會事倍功半,所以,我建議第一步我們來學習下它的基本原理(親身體會啊/哭,以前學過好幾次Git,但過一段一時間不用就忘得差不多了,回頭想想原來學的總是一知半解)。
我們先來想一個問題:Git為什么能知道我們以前的代碼,為什么能“回到過去”,答案就是因為Git把我們每一次修改提交的代碼以及狀態都保存了下來,而展示給我們的只有當前一個版本。看下面這張圖:

每次提交或保存當前項目狀態,Git都會生成一個當前所有文件狀態的快照,並存儲一個對該快照的引用;而且文件沒有發生變化時,Git不會重復保存快照,而只是鏈接到之前的標識文件。
說到這,可能還點不明白,別急,我們接下來看看快照到底存儲了哪些東西。
快照存儲不是一個個代碼源文件,而是四種對象,幾乎所有的Git操作都是在這四種對象上完成的,這四種對象是:
- “blob”:一個“blob”通常用來存儲文件的內容。一個“blob”對象就是一塊二進制數據,它沒有指向任何東西或有任何其它屬性,甚至沒有文件名。因為“blob”對象內容全部都是數據,所以如若兩個文件在一個目錄樹或是一個版本倉庫中有同樣的數據內容,那么它們將會共享同一個“blob”對象。“blob”對象和其所對應的文件所在路徑、文件名是否改被更改都完全沒有關系。
- “tree”:像一個目錄,管理一些“tree”對象或是“blob”對象。它有一串指向“blob”對象或是其它“tree”對象的指針,一般用來表示內容之間的目錄層次關系(就像文件和子目錄)。
- “commit”:“commit”對象指向一個“tree對象”,並且帶有相關的描述信息,標記項目某一個特定時間點的狀態。它包括一些關於時間點的元數據,如時間戳、最近一次提交的作者、指向上次提交的指針等等。
- “tag”:一個“tag”對象包括一個對象名(SHA1簽名)、對象類型、標簽名、標簽創建人的名字(“tagger”), 還有一條可能包含有簽名(signature)的消息。
這些對象的關系可以用下圖來理解:

所有的對象通過commit對象聯系起來,每一次commit對象中又都會指向上一次提交后的commit對象,所以如果我們什么時候想回退版本,只需要找到相應的commit對象即可。而我們所謂的HEAD對象其實就指向最近一個提交的commit對象,也就是最后一個commit對象。
例如我們有一個項目,如果我們把它提交(commit)到一個Git倉庫中, 在Git中“blob”、“commit”和“tree”對象的關系看起來會如下圖:

可以看到: 每個目錄都創建了“tree”對象, 每個文件都創建了一個對應的“blob”對象。最后有一個“commit”對象來指向根“tree”對象,這樣我們就可以追蹤項目每一項提交內容。
另外,這些對象的命名也很有講究:在Git里隨處可見一種“40個字符”的字符串(例如 6ff87c4664981e4397625791c8ea3bbb5f2279a3)。由於Git中每一個“對象名”都是對“對象”內容做SHA1(SHA1是一種密碼學的哈希算法)哈希計算得來的,所以對於內容不同的對象,會有不同的SHA1哈希值。這樣就意味着兩個不同內容的對象不可能有相同的“對象名”。
這樣,一次快照就是將有關的對象,即一組組由對象名和對象內容組成的的鍵值對存儲下來。所以Git才會有那么好的“記憶力”。
三、Git用戶交互
第二部分已經介紹了Git的基本原理,但只是涉及到Git自己內部存儲,沒說用戶該怎么操作,所以為了能正常輸入和輸出,與用戶交互,Git做了這樣幾件事。
說到這,我又想提一個常見的詞API,API就是Application Programming Interface(應用程序接口),《JavascriptDOM編程藝術》書里提到“簡單地說,API就是一組已經得到有關各方共同認可的基本約定”,說實在地我覺得這說的一點也不簡單,書里這樣說可能是想定義得更加廣泛,但我的理解是從小地說API就像一個封裝完成的函數,你給它一個輸入,它給你一個輸出,從大地說API就是一個程序,也是輸入與輸出,區別就在於規模的不同,結合到本文的內容,Git也像是一個API,輸入命令,操作數據。好了我不說了,感覺自己着迷了,看什么都像API 。\笑哭
回到正文,Git為了與交互,做了什么事呢?
答案在下面的圖里:

在Git,文件可能有三種狀態:已提交(committed),已修改(modified),暫存(staged):
- 已提交(commited),說明數據已經存儲在本地數據庫;
- 已修改(modified),說明數據被修改,但是尚未存儲到本地數據庫;
- 暫存(staged),說明已標記將一個被修改的文件(當前版本)添加到待提交的快照中。
這三種狀態分別對應Git項目的三大區塊:Git目錄,工作目錄,暫存區。
- Git 目錄(repository),即Git存儲項目元數據和對象數據庫的地方,也就是我們克隆(clone)某項目倉庫時拷貝下的內容所在地;
- 工作目錄(working directory),即從項目某版本中檢出的當前所處分支,也就是從Git目錄數據庫中拉取的文件在本地磁盤保存所在地;
- 暫存區(staging area),即一個文件,通常包含在Git目錄中,存儲下一次需提交的內容,有時,它指向我們所說的“index”索引。
所以我們的實際操作流程是:
- 從Git目錄,檢出分支到工作目錄
- 在工作目錄修改文件
- 暫存文件,將其添加到待提交快照
- 提交,將快照持久化提交到Git目錄
這些結合起來,就為我們進行代碼版本控制提供了充足的底層支撐,這次就先寫到這里,下一篇將詳細介紹Git的使用與指令。
