美文网首页
10.2 Git 内部原理 - 《Git 对象》

10.2 Git 内部原理 - 《Git 对象》

作者: 金樽明月 | 来源:发表于2021-03-03 10:32 被阅读0次

    Git 的核心部分是一个简单的键值对数据库(key-value data store)。 你可以向 Git 仓库中插入任意类型的内容,它会返回一个唯一的键,通过该键可以在任意时刻再次取回该内容。
    可以通过底层命令 git hash-object 来演示上述效果——该命令可将任意数据保存于 .git/objects 目录(即 对象数据库),并返回指向该数据对象的唯一的键。

    数据对象(blob)

    1. 将 test content 写入 git 数据库
     echo 'test content' | git hash-object -w --stdin
    

    此命令输出一个长度为 40 个字符的校验和。 这是一个 SHA-1 哈希值——一个将待存储的数据外加一个头部信息(header)一起做 SHA-1 校验运算而得的校验和

    1. 查看 git 存储的数据的位置
    find .git/objects -type f
    
    1. git 显示存储的大致内容
     git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
    
    1. git 显示数据类型
    git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
    
    1. 模拟git提交与检出
    echo 'version1' > test.txt
    git hash-object -w test.txt
    echo 'version 2' > test.txt
    git hash-object -w test.txt
    find .git/objects -type f
    git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
    

    树对象(tree object)

    它能解决文件名保存的问题,也允许我们将多个文件组织到一起。
    一个树对象包含了一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象的 SHA-1 指针,以及相应的模式、类型、文件名信息。

    例如,某项目当前对应的最新树对象可能是这样的:

    $ git cat-file -p master^{tree}
    100644 blob a906cb2a4a904a152e80877d4088654daad0c859      README   
    100644 blob 8f94139338f9404f26296befa88755fc2598c289      Rakefile
    040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0      lib
    
    $ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
    100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b      simplegit.rb
    

    通常,Git 根据某一时刻暂存区(即 index 区域,下同)所表示的状态创建并记录一个对应的树对象, 如此重复便可依次记录(某个时间段内)一系列的树对象。

    1. 为 test.txt 的首个版本,创建缓存区
    $ git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt
    

    必须为上述命令指定 --add 选项,因为此前该文件并不在暂存区中(我们甚至都还没来得及创建一个暂存区呢); 同样必需的还有 --cacheinfo 选项,因为将要添加的文件位于 Git 数据库中,而不是位于当前目录下。 同时,需要指定文件模式、SHA-1 与文件名

    1. 将暂存区内容写入一个树对象
    git write-tree
    

    此处无需指定 -w 选项——如果某个树对象此前并不存在的话,当调用此命令时, 它会根据当前暂存区状态自动创建一个新的树对象

    1. 创建一个新的树对象
    $ git write-tree
    d8329fc1cc938780ffdd9f94e0d364e0ea74f579
    $ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
    100644 blob 83baae61804e65cc73a7201a7252750c76066a30        test.txt
    
    $ git write-tree
    0155eb4229851634a0f03eb265b69f5a2d56f341
    $ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
    100644 blob fa49b077972391ad58037050f2a75f74e3671e92        new.txt
    100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt
    
    1. 将第一个树对象加入第二个树对象,使其成为新的树对象的一个子目录
    $ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
    $ git write-tree
    3c4e9cd789d88d8d89c1073707c3585e41b0e614
    $ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
    040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579      bak
    100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
    100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt
    

    提交对象(commit object)

    1. 创建一个提交对象
    $ echo 'first commit' | git commit-tree d8329f
    fdf4fc3344e67ab068f836878b6c4951e3b15f3d
    

    由于创建时间和作者数据不同,你现在会得到一个不同的散列值。 请将本章后续内容中的提交和标签的散列值替换为你自己的校验和。

    1. 查看提交对象
    $ git cat-file -p fdf4fc3
    tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
    author Scott Chacon <schacon@gmail.com> 1243040974 -0700
    committer Scott Chacon <schacon@gmail.com> 1243040974 -0700
    
    first commit
    
    1. 创建另外两个提交,各自引用上一个提交
    $ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
    cac0cab538b970a37ea1e769cbbde608743bc96d
    $ echo 'third commit'  | git commit-tree 3c4e9c -p cac0cab
    1a410efbd13591db07496601ebc7a059dd55cfe9
    
    1. 查看提交历史
    $ git log --stat 1a410e
    

    每次我们运行 git add 和 git commit 命令时,Git 所做的工作实质就是将被改写的文件保存为数据对象, 更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象。 这三种主要的 Git 对象——数据对象、树对象、提交对象——最初均以单独文件的形式保存在 .git/objects 目录下。

    对象的头信息 header

    Git 首先会以识别出的对象的类型作为开头来构造一个头部信息,本例中是一个“blob”字符串。 接着 Git 会在头部的第一部分添加一个空格,随后是数据内容的字节数,最后是一个空字节(null byte)

    $ irb
    >> content = "what is up, doc?"
    => "what is up, doc?"
    >> header = "blob #{content.length}\0"
    => "blob 16\u0000"
    

    Git 会将上述头部信息和原始数据拼接起来,并计算出这条新内容的 SHA-1 校验和。
    Git 会通过 zlib 压缩这条新内容,最后,需要将这条经由 zlib 压缩的内容写入磁盘上的某个对象。 要先确定待写入对象的路径(SHA-1 值的前两个字符作为子目录名称,后 38 个字符则作为子目录内文件的名称)。
    所有的 Git 对象均以这种方式存储,区别仅在于类型标识——另两种对象类型的头部信息以字符串“commit”或“tree”开头,而不是“blob”。

    相关文章

      网友评论

          本文标题:10.2 Git 内部原理 - 《Git 对象》

          本文链接:https://www.haomeiwen.com/subject/zfcqqltx.html