git 内部原理 - git 对象

523 阅读11分钟

image.png

前言

最近在准备学习一些前端的知识,在准备期间首先把巩固git一下,在学习期间掌握了eslint+git+husky是如何把控前端质量,并初步了解git的内部底层运作原理-Git对象

贴出官方文档以便大家进一步学习:git-scm.com/book/zh/v2/

底层概念

还原git高层命令下git底层又做了些什么.

此处用到的linux命令

clear :清除屏幕

find 目录名 -type f :将对应目录下的文件平铺在控制台

cat 文件的 url : 查看对应文件的内容

vim 文件的 url(在英文模式下) 编辑某文件

按 i 进插入模式 进行文件的编辑

按 esc 键&按:键 进行命令的执行

q! 强制退出(不保存)

wq 保存退出

初始化

命令:git init

作用:初始化后,在当前目录下会出现一个名为 .git 的目录,所有 Git 需要 的数据和资源都存放在这个目录中。不过目前,仅仅是按照既有的结构框架初始化 好了里边所有的文件和目录,但我们还没有开始跟踪管理项目中的任何一个文件。

image.png

图为.git文件夹中内容及简介

Git 对象

Git 的核心部分是一个简单的键值对数据库。你可以向该数据库插入任意类型 的内容,它会返回一个键值,通过该键值可以在任意时刻再次检索该内容

下面对一个文件进行简单的版本控制
增 :向数据库写入内容 并返回对应键值信息
 1.创建一个test.md文件,里面写入内容.
 2.输入 git hash-object -w filerNmae  存储返回对应文件的键值.
 3.由图可见我们写入的内容已经被存储在了.git文件夹下objects文件夹当中.

image.png

查 :查看Gi是如何存储数据的
    1. find .git/objects -type f
       返回: .git/objects/df/fdc195af3f4a6c6182c3b6aa306dc8f4671306
       这就是开始时 Git 存储内容的方式:一个文件对应一条内容。校验和的前两个字符用于命名子目录,余下的 38 个字符则用作文件名.
    2. git cat-file -t  dffdc195af3f4a6c6182c3b6aa306dc8f4671306 
       返回:blob
       利用 cat-file -t 命令,可以让 Git 告诉我们其内部存储的任何对象类型
    3. git cat-file -p dffdc195af3f4a6c6182c3b6aa306dc8f4671306 
       返回:1.test
       利用 cat-file -p 命令,可以查看对应对象所存储的内容
    

image.png

注意*
当前的操作都是在对本地数据库进行操作 不涉及暂存区
我理解的是上面操作对应着 git add 中的存储工作区内容的一个步骤
问题
1. 记住文件的每一个版本所对应的 SHA-1 值并不现实
2. 在 Git 中,文件名并没有被保存——我们仅保存了文件的内容
解决方案:树对象

树对象

树对象(tree object),它能解决文件名保存的问题,也允许我们将多个文件 组织到一起。Git 以一种类似于 UNIX 文件系统的方式存储内容。所有内容均以 树对象和数据对象(git 对象)的形式存储,其中树对象对应了 UNIX 中的目录项, 数据对象(git 对象)则大致上对应文件内容。一个树对象包含了一条或多条记录(每条记录含有一个指向 git 对象或者子树对象的 SHA-1 指针,以及相应的模式、类型、文件名信息)。

我们先简单操作得到一个树对像
增 :向数据库生成一个树对象
    1. git update-index --add --cacheinfo 100644 dffdc195af3f4a6c6182c3b6aa306dc8f4671306 test.md
       利用 update-index 命令 为 test.txt 文件的首个版本——创建一个暂存区
       --add 选项: 因为此前该文件并不在暂存区中 首次需要—add 
       --cacheinfo 选项: 因为将要添加的文件位于 Git 数据库中,而不是位于当前目录下 所有需要—cacheinfo
        100644为文件模式,表明这是一个普通文件;100755,表示一个可执行文件; 120000,表示一个符号链接。
        dffdc...为要添加到暂存区的目标HASH
        test.md 为filerName
    2. git status
       此时检查当前文件状态,会发现目标文件已经存储在暂存区内.也就是说.我们用了两个命令.相当于还原了git add的操作.但此时还没有还没有存储在树对象当中.
    3. git write-tree
       通过 write-tree 命令生成树对像
       返回:一个filerName 86c635fe406ae22ae4d0dd63361f1207f112ac6c
       此时树对象已经生成,下面我们来查看一下树对象包含了哪写内容,又放在了哪里

image.png

查 :查看树对象是怎样存储数据的
    1. find .git/objects -type f
       返回: .git/objects/86/c635fe406ae22ae4d0dd63361f1207f112ac6c
             .git/objects/df/fdc195af3f4a6c6182c3b6aa306dc8f4671306
       其中 86为刚才存储的树对象的文件夹路径,c635...为存储的树对象文件路径.
   2.  git ls-files -s
       返回:100644 dffdc195af3f4a6c6182c3b6aa306dc8f4671306 0       test.md
       查看当前暂存区存储状态,因为刚才执行了update-index命令对暂存区(.git下的index)文件进行了更新.
   3. git cat-file -t  86c635fe406ae22ae4d0dd63361f1207f112ac6c 
       返回:tree
       利用 cat-file -t 命令,可以让 Git 告诉我们其内部存储的任何对象类型
   4. git cat-file -p 86c635fe406ae22ae4d0dd63361f1207f112ac6c  
       返回:100644 blob dffdc195af3f4a6c6182c3b6aa306dc8f4671306    test.md
       利用 cat-file -p treeHASH 命令,可以查看对应对象所存储的内容会发现里面存储的是当前暂存区对应的每一个文件。我们会发现与步骤'2'中的数据是对应的.
    

image.png

结论:当前树结构为下,我们发现此时的树结构已经存储了一个blob数据类型的文件.如果我们用cat-file -p blob也是可以查看对应内容
graph TD
tree --> blob1
改 :如果我们修改了文件内容,树结构会发生怎样的变化呢.让我们一起往下看
此时我们业务场景为新增了一个文件test2.md并修改过了内容.操作如下
    1.git hash-object -w test.md.
    2.git update-index --add --cacheinfo 100644 blobHash filerNmae
    3.git write-tree 此时会出现一个新的tree
    4.git read-tree  --prefix=bak 'old-treeHASH' 该命令会把老的树与新的树合并.参数为老的树的hash
    5.git write-tree 再次生成新的树.
    6.git cat-file -p new-treeHASH 查看新的树的HASH会发现内涵 两个文件一个为老的 tree 结构的文件 
      另一个为新的blob文件 如下图

image.png

解析树对象
此时我们的树结构已经法生了微妙的变化,我们发现此时的树结构存储了一个tree数据类型的文件
和一个blob数据类型的文件,如果我们用cat-file -p HASH也是可以查看对应内容.
Git 根据某一时刻暂存区(即 index 区域)所表示的状态创建并记录一个对应的树对象,
如此重复便可依次记录(某个时间段内)一系列的树对象。其实树对象是对暂存区内操作的抽象,这颗树对象 相对于就是快照。
当我们的工作区有任何更改同步到暂存区时。
便会调用 write-tree 命令通过 write-tree 命令向暂存区内容写入一个树对象。
它会根据当前暂存区状 态自动创建一个新的树对象,即每一次同步都产生一颗树对象。
且该命令会返回一个 hash 指向树对象.
在 Git 中每一个文件(数据)都对应一个 hash(类型 blob),每一个树对象都对应一个 hash(类型 tree)
graph TD
tree --> blob
newTree --> blob2
newTree --> oldTree --> blob1

结论
我们可以认为树对象就是我们项目的快照.
存在问题
现在有三个树对象(执行了三次 write-tree).
分别代表了我们想要跟踪的不同项目快照.然而问题依旧,若想重用这些快照,你必须记住所有三个HASH值.
并且,你也完全不知道是谁保存了这些快照,在什么时刻保存的,以及为什么保存这些快照.
而以上这些,正是提交对象(commit object)能为你保存的基本信息

Git 提交对象

我们可以通过调用 commit-tree 命令创建一个提交对象,为此需要指定一个树 对象的 SHA-1 值,以及该提交的父提交对象(如果有的话 第一次将暂存区做快 照就没有父对象)

增 :创建提交对象
    1. echo 'first commit' | git commit-tree treeHASH (要提交的树对象HASH)
       返回:e71c5d4acbff486237b0cda2fa438a5b8bb3315f(新创建的提交对象的HASH)
       我们可以通过调用 commit-tree 命令创建一个提交对象,为此需要指定一个树
       对象的 SHA-1 值,以及该提交的父提交对象(如果有的话 第一次将暂存区做快
       照就没有父对象)
    2.git cat-file -p e71c5d4acbff486237b0cda2fa438a5b8bb3315f
      查看提交对象内容
      它先指定一个顶层树对象,代表当前项目快照;然后是作者/提交者信息(依
      据你的 user.name 和 user.email 配置来设定,外加一个时间戳)
   

image.png

改 :新增提交对象(此时我们在test.md文件中修改一些内容,然后在做操作新增提交对象)
    1. git hash-object -w test.md 新增文件快照.
    2. git update-index --cacheinfo 100644   93d63e74a1b991638e48bc289ba7237f1c15b36c test.md
       文件快照加入暂存区
    3. git write-tree 创建新的树对象.
    4. git read-tree --prefix=bak 86c635fe406ae22ae4d0dd63361f1207f112ac6c 将老的树对象加入到新的树对象中.
    5. git write-tree 创建新的树对象.
       返回:6420f8cdd2497dec2cd60f0b8989ce77750d1136
       此时新的树对象内容为 old tree 与 new blob .
    6. echo 'second commit' | git commit-tree  newTreeObj -p  oldCommitObj 创建新的提交对象.
    
       此时该提交对象中比之前没有父节点的提交对象多了一个 parent => commit 提交对象
    7. git cat-file -t  newCommitObjHASH  查看该文件类型格式
       返回:commit 
    8. git cat-file -p newCommitObjHASH  查看该文件内容
       返回:tree 新增的树对象快照.
            parent 父节点提交对象
            后面就是提交用户信息

image.png

image.png

现在,如果对最后一个提交的 SHA-1 值运行 git log 命令,会出乎意料的发现,
你已有一个货真价实的、可由 git log 查看的 Git 提交历史

image.png

结论
至此,我们没有借助任何上层命令,仅凭几个底层操作便完成了一个 Git 提交历史的创建.
这就是每次我们运行 git add 和 git commit 命令时,git做的工作实质就是将被改写的文件保存为数据对象,新暂存区,记录树对象.
最后创建一个指明了顶层树对象和父提交的提交对象.
这三种主要的 Git 对象——数据对象、树对象、提交对象——最初均以单独文件的形式保存在 .git/objects 目录下

image.png

如果跟踪所有的内部指针,将得到一个类似下面的对象关系图:

image.png

graph TD


结尾

便大家更进一步理解.我把命令对比贴出来给大家加深下印象.
    git add ./
        git hash-ovject -w 文件名(修改了多少工作目录中的文件,次命令就要被执行多少次)
        git update-index ... 更新index缓存区中的数据
    git commit -m '注释内容'
        git write-tree  写入树当中
        git commit-tree 写入提交树当中
    git log 
        git log commitHASH 查看提交信息
##### 作用
    git对象:可以向数据库写入内容,存储文件快照. 缺点内容过多,记住每一个文件所对应的HASH并不显示,并且文件名没有被保存-
             仅保存了文件内容.
    tree对象:它解决了文件名保存的问题,也运行我们将多个文件组织到一起,问题是如果你想重用这些快照,就必需记住当前所用的几个tree 哈希值,
             且也不知道是谁保存的.没有相关信息.
    commit对象:它解决了把所有要准备发布的内容组织到了一起,并给予相关信息备注.用户信息等.