Git 簡介之工作原理雜談
自從認識Git開始,就一直非常佩服這個軟件,一直想寫點東西來把自己所體會到的記錄下來。 Git是由Linux Kernal的創始人Linus設計并發布的一個版本控制軟件,乍一看是愚蠢的設計,實際上是天才的杰作,真可謂是大智若愚。我們都知道傳統的版本控制軟件有CVS,SVN等,但Git跟同源軟件相比能脫穎而出并青出于藍,完全得意于它“大智若愚”的模型設計。
首先想說一下Git模型設計的一些基本概念:Git設計了幾種對象模型,在每種對象模型中,主要包含了對象的size,type和content。Git中包含的對象非常簡單,主要為以下三種:
- blob: is used to store file data - it is generally a file
- tree: is basically like a directory - it references a bunch of other trees and/or blobs
commit: points to a single tree, marking it as what the project looked like at a certain point in time
(optional)4. tag: The tag object is a way to mark a specific commit as special in some way.
以我們常用的源碼管理為例子,blob對象即項目中的所有實體文件,包括源代碼、圖片資源、xml配置信息等等等等的內容,特別強調它記錄的僅僅是文件內容,而關于此文件所在目錄、名字大小等信息統統記錄在關聯它的tree對象上。我們每次提交,都會產生一個commit對象,并更新有改動的文件所關聯的所有tree對象,tree除了管理blob還可以管理tree本身。所以,眾多tree對象一起記錄了包含整個項目所有blob對象的信息,并形成了一個個的DAG(有向無環圖),以至于在任何時間點任何情況下,通過commit對象關聯的唯一根節點tree,都可以遍歷找出整個項目在這次commit 狀態下的全部文件。
引用一段Git權威書籍的原文:
</span>Git design split file name and content
- file name saved in tree
- file content saved in blob
so the same blob can stands for multi files with multi names
Git objects are immutable, that is, they cannot ever be changed.
- There are references which also stored in Git.
- Unlike the objects, references can constantly change. </pre>
</div>
那么Git為什么能夠成為同類軟件中的佼佼者?只是因為這簡潔的幾種object定義么?
筆者理解是:傳統版本控制軟件CVS,SVN,在文件被修改之后提交,提交之后再修改再提交,它們只記錄文件之間的差異狀態,也就是為一個文件從創建之初就只有一個副本,以后所有的改變都是通過記錄的差異從原始的副本計算得來。而Git的設計理念是,任何文件,只要有任何改動,哪怕是一個字節也好,都會重新創建一個副本(即之前提到的blob)對象,若一個文件被修改了4次就會有4個副本,每一個都是獨立的,都與每次提交產生commit對象所管理。乍一看Git的這種設計非常消耗硬盤,確實是這樣,貌似非常愚鈍!但當今的計算機時代,硬盤的低廉和容量的飛速擴大,讓這磁盤空間的消耗變得越來越微不足道。 Git的設計者Linus就充分利用這一點,犧牲了磁盤空間,換取 了無限控制上的靈活和管理的高效。這就是筆者之前提到的“大智若愚”
下面通過一個實際的例子來介紹Git的工作原理:
假設我們的項目里目錄結構是這樣子的:
- src
- java
- Hello.java
- resource.xml
- java
- lib
- rt.jar
- run.bat </pre>
1.當我們使用git init創建repository并第一次提交整個項目之后,會形成4個blog對象分別存儲Hello.java,resource.xml, rt.jar和run.bat。并形成一個commit對象和四個tree對象(分別代表src、java和lib和整個項目根目錄root)。
此時Git里的缺省HEAD即指向最近一次提交的tree對象。通過checkout HEAD,可以把最近一次提交的所有文件都找出來。(實際上這所有的對象都存儲在隱藏目錄.git/objects里面)
2.現在我們唯獨只修改Hello.java,并進行第二次提交,那么Git會生成一個新的blob對象記錄修改后的Hello.java,并生成一個新的commit對象。由于blob只記錄文件內容,其他文件信息、目錄結構等都由tree對象記錄,所以Hello.java改變導致代表java目錄的 tree發生了改變,父目錄src代表的tree對象也發生了改變,根目錄同理,所以這次提交還會生成三個新的tree對象(代表新的src和新的 java和新的項目根目錄root)
3.當有另外一個開發人員希望得到項目的第一次提交狀態的話,只需要提供第一提交的commit對象的key,它記錄的僅僅是第一提交的root tree,這個root tree會找到舊的src tree對象,java tree對象和并非改變過的lib tree對象,并通過它們找回它們所管理的所有blog對象。至此,第一次提交時的整個項目就被checkout 出來了。我們可以給某次commit對象起便于記憶的別名,這也就形成了我們所熟悉的tag和branch的概念。下次checkout提供別名就可以了。
這樣設計的優勢是什么呢?筆者的理解是,對于大型項目來說,創建分支是很常見的。Git的整套模型設計賦予了開發人員最大的靈活性來任意創建分支并在自己的分支上開發。到一定時間需要merge到主干的時候,除非是對同一個文件內容的修改需要處理沖突(合并兩個blob對象)之外,其余部分只是在 merge兩棵tree,把有向無環圖tree中對blob的指針和少量文件基本信息更新,形成一棵新的tree,如此而已!在實際項目開發中,畢竟創建分支的不同開發都是在分支上開發少量的新功能,大部分內容與主干并無區別,所以merge成新的tree的時候,對毫無改變的blob對象,merge前后的tree都依然指向它們,對于各自分支的修改文件,分別merge到主干上也只是更新了少量的tree和blob而已。(如果對數據結構還有些基礎的話,不妨試著畫一畫,你會發現把兩個有向無環圖合并是如此之簡單高效)
最后附上一些常用的Git命令供實驗和參考:(推薦一本介紹Git的書《Pro Git》,在學習Git的過程中,其實也是在學習如何根據衍化設計出高效實用的軟件的過程)
本文由用戶 openkk 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!