[前端漫談]Git 內部原理 - Git 對象

0x000 導讀

這篇文章是對Git Pro 10.2 Git 內部原理 - Git 對象章節的解讀和轉化,主要介紹兩個東西:1)使用 Git 底層命令完成提交,2)嘗試使用 NodeJS 解析 Git 對象(文章中提供的是 Ruby)。html

0x001 初始化

初始化一個本地倉庫: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,此時只有infopack兩個文件夾,咱們也不關注它,咱們只關注objects下除了infopack以外的變化。緩存

0x002 hash-object

這個命令用來爲一個文件計算對象 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,因爲制定了-wgit 會存儲此次的計算,查看objects下的文件:ui

+ objects
    + d6
        - 70460b4b4aece5915caf5c68d12f560a9fe3e4
複製代碼

會發現,多了一個文件夾d6,而d6中有一個文件70460b4b4aece5915caf5c68d12f560a9fe3e4,這二者拼接起來正好是剛剛生成的objectIDspa

若是咱們屢次執行這個命令,會發現,這個文件沒有發生變化,由於已經存在,這也就是以前說可能生成的緣由了。.net

若是咱們改變內容,則會生成一個新的objectID和一個新的blob文件。

0x003 cat-file

咱們已經直到如何存儲文件,那如何讀取呢?可使用cat-file

$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
複製代碼

0x004 文件存儲和版本恢復

接下來咱們使用文件,而不直接使用內容

$ 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
複製代碼

0x005 樹對象 和 write-tree

將文件加入緩存區

$ 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
複製代碼

0x006 提交 commit 和 commit-tree

有了樹對象以後,就能夠提交該樹對象,生成一個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文件

0x007 object文件類型

object文件類型一共有三種:

  • blobhash-object生成,表示一個文件
  • treewrite-tree生成,表示緩存區的文件列表
  • commitcommit-tree生成,表示本次提交的文件列表

他們的關係是:

  • commit包含一個tree對象
  • tree包含多個blob對象和tree對象

0x008 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)
複製代碼

0x009 資源

0x010 帶貨

最近發現一個好玩的庫,做者是個大佬啊--基於 React 的現象級微場景編輯器

相關文章
相關標籤/搜索