這篇文章是對Git Pro 10.2 Git 內部原理 - Git 對象章節的解讀和轉化,主要介紹兩個東西:1)使用 Git 底層命令完成提交,2)嘗試使用 NodeJS 解析 Git 對象(文章中提供的是 Ruby)。html
初始化一個本地倉庫:git
$ mkdir git-test
$ cd git-test
$ git init
Initialized empty Git repository in ...
複製代碼
查看文件結構:github
+ git-test
+ .git
+ branches
- config
- description
- HEAD
+ hooks
+ info
+ objects
+ info
+ pack
+ refs
複製代碼
其餘暫時不關注,只關注objects
,此時只有info
和pack
兩個文件夾,咱們也不關注它,咱們只關注objects
下除了info
和pack
以外的變化。緩存
這個命令用來爲一個文件計算對象 ID 並可能建立一個 blob 文件。這裏有兩層含義:1)計算對象 ID,對象 ID 是什麼呢?2)blob 文件是啥?爲啥是可能?接下來將給出答案。bash
執行命令:編輯器
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
複製代碼
-w
指示hash-object
存儲數據對象,若是不指定,則返回計算的 objectId。--stdin
指示從標準輸入讀取內容,也就是將test content
做爲內容計算。post
當咱們執行這個這個命令的時候,會返回一個 40 個字符長度的 SHA1 哈希值。d670460b4b4aece5915caf5c68d12f560a9fe3e4
,因爲制定了-w
,git
會存儲此次的計算,查看objects
下的文件:ui
+ objects
+ d6
- 70460b4b4aece5915caf5c68d12f560a9fe3e4
複製代碼
會發現,多了一個文件夾d6
,而d6
中有一個文件70460b4b4aece5915caf5c68d12f560a9fe3e4
,這二者拼接起來正好是剛剛生成的objectID
。spa
若是咱們屢次執行這個命令,會發現,這個文件沒有發生變化,由於已經存在,這也就是以前說可能生成的緣由了。.net
若是咱們改變內容,則會生成一個新的objectID
和一個新的blob
文件。
咱們已經直到如何存儲文件,那如何讀取呢?可使用cat-file
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
複製代碼
接下來咱們使用文件,而不直接使用內容
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30
複製代碼
而後更新這個文件並存儲
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
複製代碼
此時的objects
+ objects
+ 1f
- 7a7a472abf3dd9643fd615f6da379c4acb3e3a
+ 83
- baae61804e65cc73a7201a7252750c76066a30
+ d6
- 70460b4b4aece5915caf5c68d12f560a9fe3e4
複製代碼
而後吧文件內容恢復到第一個版本
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1
複製代碼
或者第二個版本
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2
複製代碼
將文件加入緩存區
$ git update-index --add --cacheinfo 100644 \
83baae61804e65cc73a7201a7252750c76066a30 test.txt
複製代碼
將緩存區內容寫入樹對象
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
複製代碼
新建一個新的數對象,包含test.txt
的第二個版本和一個新的文件:
$ echo 'new file' > new.txt
$ git update-index --cacheinfo 100644 \
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ git update-index --add new.txt
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
複製代碼
有了樹對象以後,就能夠提交該樹對象,生成一個commit
$ echo 'first commit' | git commit-tree d8329f
b51096bf62fa145c0b95ce18dc3020daa1f2556e
複製代碼
查看這個commit
$ git cat-file -p b51096bf62fa145c0b95ce18dc3020daa1f2556e
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700
first commit
複製代碼
接着提交第二個樹對象,並使用-p
指定這個提交的上一個commit
$ echo 'second commit' | git commit-tree 0155eb4229851634a0f03eb265b69f5a2d56f341 -p b51096bf62fa145c0b95ce18dc3020daa1f2556e
bf41fa3700a67914b3b45eefced02fffcdaf4464
複製代碼
使用git log
查看記錄
commit bf41fa3700a67914b3b45eefced02fffcdaf4464
Author: lyxxxx <lyxxxx@yeah.net>
Date: Sun Nov 17 22:14:36 2019 +0800
second commit
new.txt | 1 +
test.txt | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
commit b51096bf62fa145c0b95ce18dc3020daa1f2556e
Author: lyxxxx <lyxxxx@yeah.net>
Date: Sun Nov 17 22:07:01 2019 +0800
first commit
test.txt | 1 +
1 file changed, 1 insertion(+)
複製代碼
以上,即是使用底層命令建立 Git 提交歷史的過程,主要涉及 5 個命令:
hash-object
:計算objectID
,建立blob
文件cat-file
:讀取生產的object
文件update-index
:更新暫存區文件write-tree
:將暫存區文件寫入tree
文件commit-tree
:提交tree
文件object
文件類型object
文件類型一共有三種:
blob
:hash-object
生成,表示一個文件tree
:write-tree
生成,表示緩存區的文件列表commit
:commit-tree
生成,表示本次提交的文件列表他們的關係是:
commit
包含一個tree
對象tree
包含多個blob
對象和tree
對象objectID
如何生成接下來使用NodeJS
演示如何生成objectID
,
假設咱們要存儲的內容是what is up, doc?
const content = 'what is up, doc?'
const type = 'blob'
複製代碼
object
對象的存儲的格式是:
const store = `${type} ${content.length}\0${content}`
複製代碼
而後計算sh1 值
:
const crypto = require('crypto');
const hash = crypto.createHash('sha1');
hash.update(store);
const objectID = hash.digest('hex');
複製代碼
最後計算的結果是:
bd9dbf5aae1a3862dd1526723246b20206e5fc37
複製代碼
接着是存儲,在存儲的時候,會執行壓縮以後再存儲:
const zlib = require('zlib');
const result = zlib.deflateSync(Buffer.from(store))
複製代碼
而後按照objectID
分割存儲到objects
文件夾下就好了:
+ objects
+ bd
- 9dbf5aae1a3862dd1526723246b20206e5fc37
複製代碼
完整源碼:
const zlib = require('zlib');
const fs = require('fs');
const Buffer = require('buffer').Buffer
const crypto = require('crypto');
const type = 'blob'
const content = process.argv[2]
const store = `${type} ${content.length}\0${content}`
const hash = crypto.createHash('sha1');
hash.update(store)
const objectID = hash.digest('hex')
const result = zlib.deflateSync(Buffer.from(store))
const path = '.git/objects'
const [a, b, ...file] = objectID
const dirPath = `${path}/${a}${b}`
const filePath = `${dirPath}/${file.join('')}`
fs.mkdirSync(dirPath)
fs.writeFileSync(filePath)
複製代碼
最近發現一個好玩的庫,做者是個大佬啊--基於 React 的現象級微場景編輯器。