天天看點

《Git版本控制管理(第2版)》——第4章 基本的Git概念 4.1基本概念

本節書摘來自異步社群《git版本控制管理(第2版)》一書中的第4章,第4.1節,作者:【美】jon loeliger , matthew mccullough著,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

前一章介紹了git的一個典型應用,并且可能引發了相當多的問題。git是否在每次送出時存儲整個檔案?.git目錄的目的是什麼?為什麼一個送出id像亂碼?我應該注意它嗎?

如果你用過其他vcs,比如svn或者cvs,那麼對最後一章的指令可能會很熟悉。事實上,你對一個現代vcs期望的所有操作和功能,git都能提供。然而,在一些基本的和意想不到的方面,git會有所不同。

本章會通過讨論git的關鍵架構組成和一些重要概念來探讨git的不同之處和原因。這裡注重基礎知識并且示範如何與一個版本庫互動;第12章會介紹如何操作很多關聯的版本庫。追蹤多個版本庫可能看起來是個艱巨的任務,但是你在本章學到的基本原則是一樣适用的。

4.1.1 版本庫

git版本庫(repository)隻是一個簡單的資料庫,其中包含所有用來維護與管理項目的修訂版本和曆史的資訊。在git中,跟大多數版本控制系統一樣,一個版本庫維護項目整個生命周期的完整副本。然而,不同于其他大多數vcs,git版本庫不僅僅提供版本庫中所有檔案的完整副本,還提供版本庫本身的副本。

git在每個版本庫裡維護一組配置值。在前面的章節你已經見過其中的一些了,比如,版本庫的使用者名和email位址。不像檔案資料和其他版本庫的中繼資料,在把一個版本庫克隆(clone)或者複制到另一個版本庫的時候配置設定是不跟着轉移的。相反,git對每個網站、每個使用者和每個版本庫的配置和設定資訊都進行管理與檢查。

在版本庫中,git維護兩個主要的資料結構:對象庫(object store)和索引(index)。所有這些版本庫資料存放在工作目錄根目錄下一個名為.git的隐藏子目錄中。

對象庫在複制操作的時候能進行有效複制,這也是用來支援完全分布式vcs的一種技術。索引是暫時的資訊,對版本庫來說是私有的,并且可以在需要的時候按需求進行建立和修改。

接下來的兩節将對對象庫和索引進行更詳細的描述。

4.1.2 git對象類型

對象庫是git版本庫實作的心髒。它包含你的原始資料檔案和所有日志消息、作者資訊、日期,以及其他用來重建項目任意版本或分支的資訊。

git放在對象庫裡的對象隻有4種類型:塊(blob)、目錄樹(tree)、送出(commit)和标簽(tag)。這4種原子對象構成git高層資料結構的基礎。

塊(blob)

檔案的每一個版本表示為一個塊(blob)。blob是“二進制大對象”(binary large object)的縮寫,是計算機領域的常用術語,用來指代某些可以包含任意資料的變量或檔案,同時其内部結構會被程式忽略。一個blob被視為一個黑盒。一個blob儲存一個檔案的資料,但不包含任何關于這個檔案的中繼資料,甚至連檔案名也沒有。

目錄樹(tree)

一個目錄樹(tree)對象代表一層目錄資訊。它記錄blob辨別符、路徑名和在一個目錄裡所有檔案的一些中繼資料。它也可以遞歸引用其他目錄樹或子樹對象,進而建立一個包含檔案和子目錄的完整層次結構。

送出(commit)

一個送出(commit)對象儲存版本庫中每一次變化的中繼資料,包括作者、送出者、送出日期和日志消息。每一個送出對象指向一個目錄樹對象,這個目錄樹對象在一張完整的快照中捕獲送出時版本庫的狀态。最初的送出或者根送出(root commit)是沒有父送出的。大多數送出都有一個父送出,雖然本書後面(第9章)會介紹一個送出如何引用多個父送出。

标簽(tag)

一個标簽對象配置設定一個任意的且人類可讀的名字給一個特定對象,通常是一個送出對象。雖然9da581d910c9c4ac93557ca4859e767f5caf5169指的是一個确切且定義好的送出,但是一個更熟悉的标簽名(如ver-1.0-alpha)可能會更有意義!

随着時間的推移,所有資訊在對象庫中會變化和增長,項目的編輯、添加和删除都會被跟蹤和模組化。為了有效地利用磁盤空間和網絡帶寬,git把對象壓縮并存儲在打封包件(pack file)裡,這些檔案也在對象庫裡。

4.1.3 索引

索引是一個臨時的、動态的二進制檔案,它描述整個版本庫的目錄結構。更具體地說,索引捕獲項目在某個時刻的整體結構的一個版本。項目的狀态可以用一個送出和一棵目錄樹表示,它可以來自項目曆史中的任意時刻,或者它可以是你正在開發的未來狀态。

git的關鍵特色之一就是它允許你用有條理的、定義好的步驟來改變索引的内容。索引使得開發的推進與送出的變更之間能夠分離開來。

下面是它的工作原理。作為開發人員,你通過執行git指令在索引中暫存(stage)變更。變更通常是添加、删除或者編輯某個檔案或某些檔案。索引會記錄和儲存那些變更,保障它們的安全直到你準備好送出了。還可以删除或替換索引中的變更。是以,索引支援一個由你主導的從複雜的版本庫狀态到一個可推測的更好狀态的逐漸過渡。

在第9章中,你會看到索引在合并(merge),允許管理、檢查和同時操作同一個檔案的多個版本中起到的重要作用。

4.1.4 可尋址内容名稱

git對象庫被組織及實作成一個内容尋址的存儲系統。具體而言,對象庫中的每個對象都有一個唯一的名稱,這個名稱是向對象的内容應用sha1得到的sha1散列值。因為一個對象的完整内容決定了這個散列值,并且認為這個散列值能有效并唯一地對應特定的内容,是以sha1散列值用來做對象資料庫中對象的名字和索引是完全充分的。檔案的任何微小變化都會導緻sha1散列值的改變,使得檔案的新版本被單獨編入索引。

sha1的值是一個160位的數,通常表示為一個40位的十六進制數,比如,9da581d910c9c4ac93557ca4859e767f5caf5169。有時候,在顯示期間,sha1值被簡化成一個較小的、唯一的字首。git使用者所說的sha1、散列碼和對象id都是指同一個東西。

全局唯一辨別符

sha散列計算的一個重要特性是不管内容在哪裡,它對同樣的内容始終産生同樣的id。換言之,在不同目錄裡甚至不同機器中的相同檔案内容産生的sha1哈希id是完全相同的。是以,檔案的sha1散列id是一種有效的全局唯一辨別符。

這裡有一個強大的推論,在網際網路上,檔案或者任意大小的blob都可以通過僅比較它們的sha1辨別符來判斷是否相同。

4.1.5 git追蹤内容

了解git不僅僅是一個vcs是很重要的,git同時還是一個内容追蹤系統(content tracking system)。這種差別盡管很微小,但是指導了git的很多設計,并且也許這就是處理内部資料操作相對容易的關鍵原因。然而,因為這也可能是對新手來講最難把握的概念之一,是以做一些論述是值得的。

git的内容追蹤主要表現為兩種關鍵的方式,這兩種方式與大多數其他①修訂版本控制系統都不一樣。

首先,git的對象庫基于其對象内容的散列計算的值,而不是基于使用者原始檔案布局的檔案名或目錄名設定。是以,當git放置一個檔案到對象庫中的時候,它基于資料的散列值而不是檔案名。事實上,git并不追蹤那些與檔案次相關的檔案名或者目錄名。再次強調,git追蹤的是内容而不是檔案。

如果兩個檔案的内容完全一樣,無論是否在相同的目錄,git在對象庫裡隻儲存一份blob形式的内容副本。git僅根據檔案内容來計算每一個檔案的散列碼,如果檔案有相同的sha1值,它們的内容就是相同的,然後将這個blob對象放到對象庫裡,并以sha1值作為索引。項目中的這兩個檔案,不管它們在使用者的目錄結構中處于什麼位置,都使用那個相同的對象指代其内容。

如果這些檔案中的一個發生了變化,git會為它計算一個新的sha1值,識别出它現在是一個不同的blob對象,然後把這個新的blob加到對象庫裡。原來的blob在對象庫裡保持不變,為沒有變化的檔案所使用。

其次,當檔案從一個版本變到下一個版本的時候,git的内部資料庫有效地存儲每個檔案的每個版本,而不是它們的差異。因為git使用一個檔案的全部内容的散列值作為檔案名,是以它必須對每個檔案的完整副本進行操作。git不能将工作或者對象庫條目建立在檔案内容的一部分或者檔案的兩個版本之間的差異上。

檔案擁有修訂版本和從一個版本到另一個版本的步進,使用者的典型看法是這種檔案簡直是個工藝品。git用不同散列值的blob之間的差別來計算這個曆史,而不是直接存儲一個檔案名和一系列差異。這似乎有些奇怪,但這個特性讓git在執行某些任務的時候非常輕松。

4.1.6 路徑名與内容

跟很多其他vcs一樣,git需要維護一個明确的檔案清單來組成版本庫的内容。然而,這個需求并不需要git的清單基于檔案名。實際上,git把檔案名視為一段差別于檔案内容的資料。這樣,git就把索引從傳統資料庫的資料中分離出來了。看看表4-1會很有幫助,它粗略地比較了git和其他類似的系統。

《Git版本控制管理(第2版)》——第4章 基本的Git概念 4.1基本概念

檔案名和目錄名來自底層的檔案系統,但是git并不真正關心這些名字。git僅僅記錄每個路徑名,并且確定能通過它的内容精确地重建檔案和目錄,這些是由散列值來索引的。

git的實體資料布局并不模仿使用者的檔案目錄結構。相反,它有一個完全不同的結構卻可以重建使用者的原始布局。在考慮其自身的内部操作和存儲方面,git的内部結構是一種更高效的資料結構。

當git需要建立一個工作目錄時,它對檔案系統說:“嘿!我這有這樣大的一個blob資料,應該放在路徑名為path/to/directory/file的地方。你能了解嗎?”檔案系統回複說:“啊,是啊,我認出那個字元串是一組子目錄名,并且我知道把你的blob資料放在哪裡!謝謝!”

4.1.7 打封包件

一個聰明的讀者也許已經有了關于git的資料模型及其單獨檔案存儲的揮之不去的問題:直接存儲每個檔案每個版本的完整内容是否太低效率了?即使它是壓縮的,把相同檔案的不同版本的全部内容都存儲的效率是否太低了?如果你隻添加一行到檔案裡,git是不是要存儲兩個版本的全部内容?

幸運的是,答案是“不是,不完全是!”

相反,git使用了一種叫做 打封包件(pack file) 的更有效的存儲機制。要建立一個打封包件,git首先定位内容非常相似的全部檔案,然後為它們之一存儲整個内容。之後計算相似檔案之間的差異并且隻存儲差異。例如,如果你隻是更改或者添加檔案中的一行,git可能會存儲新版本的全部内容,然後記錄那一行更改作為差異,并存儲在包裡。

存儲一個檔案的整個版本并存儲用來構造其他版本的相似檔案的差異并不是一個新伎倆。這個機制已經被其他vcs(如rcs)用了好幾十年了,它們的方法本質上是相同的。

然而,git檔案打包得非常巧妙。因為git是由内容驅動的,是以它并不真正關心它計算出來的兩個檔案之間的差異是否屬于同一個檔案的兩個版本。這就是說,git可以在版本庫裡的任何地方取出兩個檔案并計算差異,隻要它認為它們足夠相似來産生良好的資料壓縮。是以,git有一套相當複雜的算法來定位和比對版本庫中潛在的全局候選差異。此外,git可以構造一系列差異檔案,從一個檔案的一個版本到第二個,第三個,等等。

git還維護打封包件表示中每個完整檔案(包括完整内容的檔案和通過差異重建出來的檔案)的原始blob的sha1值。這給定位包内對象的索引機制提供了基礎。

打封包件跟對象庫中其他對象存儲在一起。它們也用于網絡中版本庫的高效資料傳輸。