爲了更近一步的學習和理解Git的理念,這一節介紹一下Git中的一些基本概念。git
Git的版本庫就是一個簡單的數據庫,其中包含全部用來維護和管理項目的修訂版本和歷史的信息。數據庫
Git不只提供版本庫中全部文件的副本,還提供了版本庫自己的副本。數據結構
Git在每一個版本庫裏維護一組配置值,這個咱們在上一篇文章中已經有所說起,好比版本庫的用戶名和email地址。當把一個版本庫進行克隆的時候,會複製文件數據以及其餘的一些版本庫的元數據,可是不會複製配置設置。學習
Git維護兩個主要的數據結構:對象庫和索引。全部這些數據都存放在.git隱藏目錄內。測試
對象庫包含原始數據文件、全部的日誌消息、做者信息、日期、以及其餘用來重建項目任意版本或分支的信息。spa
Git放在對象庫裏的對象只有4種類型:塊(blob)、目錄樹(tree)、提交(commit)、標籤(tag)3d
A、塊:日誌
文件的每個版本被表示爲一個blob。一個blob保存一個文件的數據,但不包含任何關於這個文件的元數據,甚至文件名。code
B、目錄樹(tree):對象
一個目錄樹對象表明一層目錄信息。它記錄了blob標識符、路徑名和在一個目錄裏全部文件的一些元數據。
C、提交(commit):
一個提交對象保存版本庫中每一次變化的元數據:做者、提交者、提交日期、日誌消息。每個提交對象指向一個目錄樹對象,除了根提交,大多數提交都有一個父提交。
D、標籤(tag):一個標籤對象分配一個任意的可讀的名字給一個特定對象(一般是一個提交對象)。
目前來講,感受這個概念不太好解釋。
索引是一個臨時的、動態的二進制文件,它描述整個版本庫的目錄結構。
個人認識是,索引是介於工做區和對象庫之間的一個緩衝區。工做區裏的變化會最先在這裏有所體現,最後由索引將全部的變更提交到對象庫中。
Git對象庫被組織及實現爲一個內容尋址的存儲系統。對象庫中的每個對象都有一個惟一的名稱,這個名稱是向對象的內容應用SHA1獲得的散列值。
首先,Git追蹤的是基於對象內容計算出的散列值,而不關心文件名或目錄名。若是兩個文件的內容相同,那麼無論是否在一樣的目錄,Git在對象庫中只保存一份blob形式的內容副本。
其次,當文件從一個版本變到下一個版本的時候,Git的內部數據庫會有效的存儲每個文件的每一個版本,而不只僅是差別。
固然,你或許已經發現,上面說的這種存儲方式效率很低,由於每個都要保存副本。這個會在後面打包文件的地方進行說明。
Git僅僅記錄每一個文件名,而且確保能經過它的內容精確的重建文件和目錄。
好吧。。。我仍是感受沒法理解做者的意思。
回到上面提出的問題:直接存儲每一個文件每一個版本的完整內容是否過低效了?
Git使用了一種叫作打包文件的存儲機制。要建立一個打包文件,Git首先定位內容很是類似的所有文件,而後爲它們之一存儲整個內容。以後計算類似文件之間的差別而且只存儲差別。
先來張圖壓壓驚。。。經過這張圖看看對象庫中的各個對象是如何協做造成整個系統的。
下面咱們讓對象庫變得複雜一些。保留原來的兩個文件不變,添加一個包含一個文件的新目錄到版本庫中。對象圖以下:
這裏由於頂級目錄被添加的新子目錄改變了,因此頂級樹對象的內容也就跟着改變了。因此Git引入了一棵新的樹cafed00d。
樹cafed00d依舊引用了原來的兩個文件的blob對象,同時新增了一個指向新子目錄的樹對象。
上面的圖是全部Git操做諸如add,commit等所有結束後的對象庫總體圖,但是咱們可能更想知道如下問題:
後面的章節會依次說明上述問題。
咱們新建一個新的版本庫,逐步查看一下每次操做後.git目錄中會發生哪些變化:
首先,咱們建立一個空的版本庫。
咱們能夠看到,除了幾個佔位符外,.git/objects目錄下是空的:
[root@flower1 test]# find .git/objects/ .git/objects/ .git/objects/pack .git/objects/info
如今,咱們建立一個簡單的文件,並添加到對象庫中。
[root@flower1 test]# echo "hello world" > hello.txt [root@flower1 test]# git add hello.txt
接下來,咱們看一下objects目錄下發生的變化:
[root@flower1 test]# find .git/objects/ .git/objects/ .git/objects/3b .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad .git/objects/pack .git/objects/info
從上面的例子裏,咱們看到,當爲hello.txt建立一個對象的時候,Git並不關心hello.txt的文件名。Git只關心文件裏的內容。Git對這個blob執行一些操做,計算它的SHA1散列碼,並散列碼的十六進制表示做爲文件名放進對象庫裏。
其實上述文件的散列碼是3b18e512dba79e4c8300dd08aeb37f8e728b8dad。
Git將散列碼的前2個字符做爲一級目錄名,能夠提升文件系統的效率,至關於將不一樣對象散列到了不一樣的文件夾下,避免某個文件目錄下文件過多。
咱們可使用下面命令使用散列碼將文件的內容提出出來:
[root@flower1 test]# git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad hello world
固然,手動收入長度爲40的散列碼很不切實際,因此Git提供了下面的方式:經過對象的惟一前綴來查找對象的散列值。
[root@flower1 test]# git rev-parse 3b18 3b18e512dba79e4c8300dd08aeb37f8e728b8dad [root@flower1 test]# git rev-parse 3b18e 3b18e512dba79e4c8300dd08aeb37f8e728b8dad [root@flower1 test]# git rev-parse 3b18e5 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
我測試的結果是,前綴最少要達到4位才能夠。
經過上面的演示,咱們看到表明hello world的blob對象已經存放到對象庫中了。那麼它的文件名又發生了什麼事呢?
Git經過另外一種叫作目錄樹的對象來跟蹤文件的路徑名。當使用git add命令時,Git會給添加的每一個文件的內容建立一個對象,但不會立刻爲樹建立一個對象。
此時,索引更新了。索引位於.git/index中,它跟蹤文件的路徑名和相應的blob。每次執行命令時,Git會用心的路徑名和blob信息來更新索引。
任什麼時候候,均可以從當前索引建立一個樹對象,只要經過底層的git write-tree命令來捕獲索引當前信息的快照就能夠了。
當前,該索引只包含一個文件:
[root@flower1 test]# git ls-files -s 100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 hello.txt
從上面的打印信息,能夠看到文件的關聯,hello.txt和3b18e512…的blob。
接下來咱們捕獲索引狀態並把它保存到一個樹對象裏:
[root@flower1 test]# git write-tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60
[root@flower1 test]# find .git/objects/ .git/objects/ .git/objects/3b .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad .git/objects/68 .git/objects/68/aba62e560c0ebc3396e8ae9335232cd93a3f60 .git/objects/pack .git/objects/info
如今有2個對象:3b18e5的hello world對象和68aba6的樹對象。
咱們也能夠看看樹對象裏面的內容究竟是什麼:
[root@flower1 test]# git cat-file -p 68ab 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad hello.txt
該節經過建立一個新的子目錄來看看Git是如何處理多個層次的目錄結構的:
[root@flower1 test]# pwd /root/test [root@flower1 test]# mkdir subdir [root@flower1 test]# cp hello.txt subdir/hello.txt [root@flower1 test]# git add subdir/hello.txt [root@flower1 test]# git write-tree 492413269336d21fac079d4a4672e55d5d2147ac [root@flower1 test]# git cat-file -p 492413 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad hello.txt 040000 tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60 subdir
能夠看到subdir的樹對象和以前的頂級目錄樹對象時徹底同樣的。
以前已經經過git add命令添加了hello.txt文件,並經過git write-tree命令生成了樹對象。
用下面的底層命令建立一個提交對象:git commit-tree SHA1
[root@flower1 test]# echo -n "Commit a file" | git commit-tree 492413269336d21fac079d4a4672e55d5d2147ac 204fa4469c1642cb9190048ebdc8fabc9a4a4af1
提交對象包含如下信息:
有2種基本的標籤類型:輕量級的和帶附註的。
輕量級標籤只是一個提交對象的引用,一般被版本庫視爲是私用的。這些標籤並不在版本庫裏建立永久對象。
帶附註的標籤會建立一個對象。
能夠經過git tag命令來建立一個帶有提交信息、帶附註切未簽名的標籤:
[root@flower1 test]# git tag -m "Tag version 1.0" V1.0 204fa