Git 內部原理之 Git 物件雜湊

2021-09-19 18:32:41 字數 4942 閱讀 8943

git中的資料物件、樹物件和提交物件的hash方法原理是一樣的,可以描述為:

header = "" + content.length + "\0"

hash = sha1(header + content)

上面公式表示,git在計算物件hash時,首先會在物件頭部新增乙個header。這個header由3部分組成:第一部分表示物件的型別,可以取值blobtreecommit以分別表示資料物件、樹物件、提交物件;第二部分是資料的位元組長度;第三部分是乙個空位元組,用來將headercontent分隔開。將header新增到content頭部之後,使用sha1演算法計算出乙個40位的hash值。

在手動計算git物件的hash時,有兩點需要注意:

1.header中第二部分關於資料長度的計算,一定是位元組的長度而不是字串的長度

2.header + content的操作並不是字串級別的拼接,而是二進位制級別的拼接

各種git物件的hash方法相同,不同的在於:

1.頭部型別不同,資料物件是blob,樹物件是tree,提交物件是commit

2.資料內容不同,資料物件的內容可以是任意內容,而樹物件和提交物件的內容有固定的格式。

接下來分別講資料物件、樹物件和提交物件的具體的hash方法。

資料物件的格式如下:

blob
$ echo -n "what is up, doc?" | git hash-object --stdin

bd9dbf5aae1a3862dd1526723246b20206e5fc37

注意,上面在echo後面使用了-n選項,用來阻止自動在字串末尾新增換行符,否則會導致實際傳給git hash-objectwhat is up, doc?\n,而不是我們直觀認為的what is up, doc?

為驗證前面提到的git物件hash方法,我們使用openssl sha1來手動計算what is up, doc?的hash值:

$ echo -n "blob 16\0what is up, doc?" | openssl sha1

bd9dbf5aae1a3862dd1526723246b20206e5fc37

可以發現,手動計算出的hash值與git hash-object計算出來的一模一樣。

在git物件hash方法的注意事項中,提到header中第二部分關於資料長度的計算,一定是位元組的長度而不是字串的長度。由於what is up, doc?只有英文本元,在utf8中恰好字元的長度和位元組的長度都等於16,很容易將這個長度誤解為字元的長度。假設我們以中文來試驗:

$ echo -n "中文" | git hash-object --stdin

efbb13322ba66f682e179ebff5eeb1bd6ef83972

$ echo -n "blob 2\0中文" | openssl sha1

d1dc2c3eed26b05289bddb857713b60b8c23ed29

我們可以看到,git hash-objectopenssl sha1計算出來的hash值根本不一樣。這是因為中文兩個字元作為utf格式儲存後的字元長度不是2,具體是多少呢?可以使用wc來計算:

$ echo -n "中文" | wc -c

6

中文字串的位元組長度是6,重新手動計算發現得出的hash值就能對應上了:

$ echo -n "blob 6\0中文" | openssl sha1

efbb13322ba66f682e179ebff5eeb1bd6ef83972

樹物件

樹物件的內容格式如下:

tree ...
需要注意的是,部分是二進位制形式的sha1碼,而不是十六進製制形式的sha1碼。

$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579

100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt

我們首先使用xxd83baae61804e65cc73a7201a7252750c76066a30轉換成為二進位制形式,並將結果儲存為sha1.txt以方便後面做追加操作:

$ echo -n "83baae61804e65cc73a7201a7252750c76066a30" | xxd -r -p > sha1.txt

$ cat tree-items.txt

���a�ne�s� rru

vj0%

接下來構造content部分,並儲存至檔案content.txt

$ echo -n "100644 test.txt\0" | cat - sha1.txt > content.txt

$ cat content.txt

100644 test.txt���a�ne�s� rru

vj0%

計算content的長度:

$ cat content.txt | wc -c

36

那麼最終該樹物件的內容為:

$ echo -n "tree 36\0" | cat - content.txt

tree 36100644 test.txt���a�ne�s� rru

vj0%

最後使用openssl sha1計算hash值,可以發現和實驗的hash值是一樣的:

$ echo -n "tree 36\0" | cat - content.txt | openssl sha1

d8329fc1cc938780ffdd9f94e0d364e0ea74f579

提交物件

提交物件的格式如下:

commit tree parent [parent if several parents from merges]

author committer

$ echo 'first commit' | git commit-tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579

db1d6f137952f2b24e3c85724ebd7528587a067a

$ git cat-file -p db1d6f137952f2b24e3c85724ebd7528587a067a

tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579

author jingsam 1528022503 +0800

committer jingsam 1528022503 +0800

first commit

這裡需要注意的是,由於echo 'first commit'沒有新增-n選項,因此實際的提交資訊是first commit\n。使用wc計算出提交內容的位元組數:

$ echo -n "tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579

author jingsam 1528022503 +0800

committer jingsam 1528022503 +0800

first commit\n" | wc -c

163

那麼,這個提交物件的header就是commit 163\0,手動把頭部新增到提交內容中:

commit 163\0tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579

author jingsam 1528022503 +0800

committer jingsam 1528022503 +0800

first commit\n

使用openssl sha1計算這個上面內容的hash值:

$ echo -n "commit 163\0tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579

author jingsam 1528022503 +0800

committer jingsam 1528022503 +0800

first commit\n" | openssl sha1

db1d6f137952f2b24e3c85724ebd7528587a067a

可以看見,與實驗的hash值是一樣的。

git內部原理

從根本上來講 git 是乙個內容定址 content addressable 檔案系統,並在此之上提供了乙個版本控制系統的使用者介面。git 的核心部分是乙個簡單的鍵值對資料庫 key value data store 你可以向該資料庫插入任意型別的內容,它會返回乙個鍵值,通過該鍵值可以在任意時刻再...

git內部原理淺見

git物件儲存 git引用 包檔案git gc git是版本管理工具 git是內容定址檔案系統,其核心部分是乙個簡單的鍵值對資料庫 key value index 通過sha 1雜湊值來查詢對應的原資料 git儲存的是快照 每次commit都會儲存整個專案的檔案資料,如果檔案沒有修改則儲存指向原檔案...

git之工作原理簡介

個人感覺,看完git的設計原理之後,對我使用git有挺大的幫助,至少也知道自己乙個命令敲下去是有什麼後果。簡單來說 有了工作區 暫存區 分支這三個概念之後,再來看下git的幾個命令 以下是 git權威指南 對git暫存區的介紹 個人的一點看法 git的工作區就是用來隨便改的,就是一不小心誤刪了損失的...