社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Git

[前端漫谈]Git 内部原理 - Git 对象

卖梳子的鲤鱼 • 6 年前 • 614 次点击  
阅读 24

[前端漫谈]Git 内部原理 - Git 对象

导读

这篇文章是对Git Pro 10.2 Git 内部原理 - Git 对象章节的解读和转化,主要介绍两个东西:1)使用 Git 底层命令完成提交,2)尝试使用 NodeJS 解析 Git 对象(文章中提供的是 Ruby)。

0x001 初始化

初始化一个本地仓库:

$ mkdir git-test
$ cd git-test
$ git init
Initialized empty Git repository in ...
复制代码

查看文件结构:

+ git-test
   + .git
       + branches
       - config
       - description
       - HEAD
       + hooks
       + info
       + objects
           + info
           + pack
       + refs
复制代码

其他暂时不关注,只关注objects,此时只有info和pack两个文件夹,我们也不关注它,我们只关注objects下除了info和pack之外的变化。

0x002 hash-object

这个命令用来为一个文件计算对象 ID 并可能创建一个 blob 文件。这里有两层含义:1)计算对象 ID,对象 ID 是什么呢?2)blob 文件是啥?为啥是可能?接下来将给出答案。

执行命令:

$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
复制代码

-w指示hash-object存储数据对象,如果不指定,则返回计算的 objectId。--stdin指示从标准输入读取内容,也就是将test content作为内容计算。

当我们执行这个这个命令的时候,会返回一个 40 个字符长度的 SHA1 哈希值。d670460b4b4aece5915caf5c68d12f560a9fe3e4,由于制定了-w,git 会存储这次的计算,查看objects下的文件:

+ objects
    + d6
        - 70460b4b4aece5915caf5c68d12f560a9fe3e4
复制代码

会发现,多了一个文件夹d6,而d6中有一个文件70460b4b4aece5915caf5c68d12f560a9fe3e4,这两者拼接起来正好是刚刚生成的objectID。

如果我们多次执行这个命令,会发现,这个文件没有发生变化,因为已经存在,这也就是之前说可能生成的原因了。

如果我们改变内容,则会生成一个新的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文件类型一共有三种:

  • blob:hash-object生成,表示一个文件
  • tree:write-tree生成,表示缓存区的文件列表
  • commit:commit-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 的现象级微场景编辑器。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/49898