这篇文章是对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 的现象级微场景编辑器。