1. 程式人生 > >Git原始碼學習(一)

Git原始碼學習(一)

趁著最近工作清閒,學習一下Git的原始碼。搜了一下網上沒有這方面的資料,只能自己慢慢的看。為了降低難度,並且更好的理解Git的發展歷程,我決定從最初版的Git開始看起,跟著Git學Git。

選擇Git v0.99開始學習。從https://github.com/git/git/tree/v0.99/處下載原始碼,這個版本的Git非常簡單,功能也很少,可以從中看到初期Git設計的初衷。Clone下來之後,reset到第一次commit,開始原始碼的閱讀之旅。

第一次Commit的檔案很少,目錄結構如下所示:


編譯

依賴包:libssl-dev、zlib

修改編譯選項:Makefile中LIBS= -lcrypto -lz

編譯完成之後在~/bin/下會產生以下命令:cat-filecommit-treeinit-dbread-treeshow-diffupdate-cachewrite-tree

用法

1、使用init-db初始化工作目錄,類似於git init的作用。

2、專案編寫,增刪改各種檔案等等。

3、使用update-cache [file-path],儲存更改至快取中。這會生成一個index檔案,改檔案用於儲存當前的cache

4、使用write-tree提交快取中的更改。這會生成一個tree檔案,當前的cache中的檔案會寫入到tree檔案中去。命令結果會返回tree檔案的sha1

值。

5、使用commit-tree <tree-sha1> [-p parent-sha1]* < changelog提交這次更改。如果沒有parent的資訊,就會當做是第一次提交。有的話就表示改次提交是在parent基礎上提交的。這裡也只是一個資訊的記錄,其實並不會檢查是否存在child-parent的關係。

6、工具命令show-diff,用來比較當前工作目錄下的檔案和cache中(即index檔案)記錄的檔案的區別。

7、工具命令cat-file <SHA1>,檢視某個sha1檔案。會生成一個TEMP檔案用來儲存改SHA1檔案中的內容。


概念

1、The Object Database(SHA1_FILE_DIRECTORY)

這是一個用於儲存SHI1檔案的檔案資料庫,其實本質上只是一個資料夾,用於存放所提交的檔案。檔案包含Metadata資訊和Blob內容,經由Zlib壓縮後算出SHA1,該SHA1的前2位作為子資料夾名,後38位作為檔名。Directory如下圖所示。


這裡存放的檔案包括三種:treeblobcommit

Treetree檔案中存放的是所提交的檔案列表,每一行描述所記錄的一個檔案,包括:檔案的許可權、路徑名、SHA1值。這個就能夠用於儲存每一次提交的具體內容,通過查詢tree檔案,可以知道該次提交時所含有的所有檔案,然後根據每一個檔案的SHA1,可以在object database中搜索出該檔案。這樣就達到了儲存每一次提交的具體內容的目的。

BLOBblob檔案是指具體的檔案內容,即我們所提交的檔案。Blob檔案會被壓縮,然後計算SHA1值,所以如果檔案的內容沒有發生變化,那麼就不會產生新的Blob檔案。因為它們算出的SHA1是相同的,而SHA1值就是它們實際的存放路徑。

Commitcommit檔案是用於記錄每一次提交的檔案。包含的內容有:treeparentsauthorcommitterchangelog。其中tree是指用於儲存此次提交的tree檔案。Parents是指此次提交的父分支是哪些,也是對應的tree檔案。Authorcommitterchangelog是提交的記錄資訊。

一個commit檔案的例子:


原始碼分析

init-db.c

該檔案的工作很簡單,就是在工作目錄下建立一個.dircache的目錄,並在其中建立objects目錄作為database的主目錄,在objects中,建立了從00ff一共255個子目錄。這些子目錄將被用來儲存SHA1檔案。如計算出sha1值為b7140ba6976a2a2bf3cb31bf3aa2bd3e3619d521的檔案,將被儲存為.dircache/objects/b7/140ba6976a2a2bf3cb31bf3aa2bd3e3619d521

Update-cache.c

該檔案實現了update-cache命令,主要流程為:從index中讀取cache,根據傳入的file,新增或者替換cache,講新的cache寫回index檔案。

下面分幾個主要部分記錄主要的程式碼工作。

1、index檔案的更新。

這裡使用的方式是先更新記憶體中的cache內容,然後create一個index.lock檔案,將新的cache寫入index.lock檔案中,然後將index.lock改名為index

2、Cache的更新。

Cache在記憶體中使用一個數組儲存,本地使用index檔案儲存。通常是從index檔案中讀取到陣列中。Cache中的條目根據cache->name有序排放,更新cache時,會通過二分查詢檢查該條目是否在cache中,如果已存在則進行替換,否則直接加入到cache中(注意有序排放)。

Show-diff.c

這裡需要解釋的是,實際的工作是通過diff命令來實現的。為了避免所有的檔案都會呼叫diff,先通過比較檔案的屬性來判斷檔案是否發生了改變。如果已經能夠判斷出檔案未發生改變,就無需再呼叫diff來比較新舊檔案。此外,沒有加入到cache中的檔案不會被比較,也就是說對於新增和刪除檔案來說,這裡是不能看到diff的結果的。並且如果是刪除檔案的話,使用show-diff還會報segment fault.

程式碼的大致邏輯:從index中讀取cache到陣列中,根據cache_entry->name找到工作目錄下的檔案(即新的檔案),根據cache_entry->sha1objects中讀取舊的檔案,使用cache_entry中存放的舊檔案的屬性和新檔案的stat比較,如果不同則呼叫diff來顯示兩者的差異。

總結:

從這個最初版的git中可以看到我們日常使用的git的一點影子。它能夠跟蹤專案的版本,但是還沒有實現控制的功能。使用檔案database的方式儲存歷史檔案,並且利用sha1來簡單的實現檔案的複用,即相同的blob只需要儲存一份。目前能夠檢視歷史版本的檔案,能夠手動的記錄提交的資訊,手動的跟蹤版本更新的資訊。從今天的角度來看,但是這些功能都有待優化,優化的初衷就是更加易用。期待著它一步一步變成今天強大的Git!

附上Linus在這個版本README中對Git的描述:

"git" can mean anything, depending on your mood.


 - random three-letter combination that is pronounceable, and not
   actually used by any common UNIX command.  The fact that it is a
   mispronounciation of "get" may or may not be relevant.
 - stupid. contemptible and despicable. simple. Take your pick from the
   dictionary of slang.
 - "global information tracker": you're in a good mood, and it actually
   works for you. Angels sing, and a light suddenly fills the room. 
 - "goddamn idiotic truckload of sh*t": when it breaks