Git(四)底层命令:git对象、树对象、提交对象

目录

    • 一、知识回顾
      • [1.1 Linux 基础命令](#1.1 Linux 基础命令)
      • [1.2 .git 文件夹解析](#1.2 .git 文件夹解析)
    • [二、git 对象(数据对象)](#二、git 对象(数据对象))
      • [2.1 hash-object 存储对象](#2.1 hash-object 存储对象)
      • [2.2 cat-file 查看对象](#2.2 cat-file 查看对象)
    • 三、树对象
      • [3.1 ls-files 查看暂存区](#3.1 ls-files 查看暂存区)
      • [3.2 update-index 创建暂存区](#3.2 update-index 创建暂存区)
      • [3.3 write-tree 生成树对象](#3.3 write-tree 生成树对象)
      • [3.4 更新暂存区,生成第二棵树](#3.4 更新暂存区,生成第二棵树)
      • [3.5 read-tree 树A加入树B,生成第三棵树](#3.5 read-tree 树A加入树B,生成第三棵树)
      • [3.6 查看树对象](#3.6 查看树对象)
    • 四、提交对象
      • [4.1 commit-tree 创建提交对象](#4.1 commit-tree 创建提交对象)
      • [4.2 查看提交对象](#4.2 查看提交对象)
      • [4.3 创建第二次提交对象](#4.3 创建第二次提交对象)
    • 五、总结

一、知识回顾

1.1 Linux 基础命令

在开始学习 Git 的底层命令之前,我们先来回顾一下 Linux 的基础命令:

  • clear:清除屏幕。

  • echo 'test content':将信息输出到控制台。

  • echo 'test content' > test.txt:将信息输出到 test.txt 文件中。

  • ll:查看当前目录下的文件详细信息。

  • find 目录名:查看对应目录下的子孙目录和文件。

  • find 目录名 -type f:查看对应目录下的子孙文件。

  • rm 文件名:删除文件

  • mv 文件名A 文件名B:移动、重命名文件A为文件B。

  • cat 文件名:查看文件内容。

  • vim 文件名:查看、编辑文件。

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

    按 Esc 退出插入模式;

    输入 :q! 强制退出(不保存);

    输入 :wq 写入退出;

    输入 :set nu 查看行号。

1.2 .git 文件夹解析

.git 文件夹内容如下:


二、git 对象(数据对象)

Git 的核心部分是一个简单的 键值对数据库。我们可以向该数据库插入任意类型的内容,它会返回一个键值,通过该键值可以在任意时刻再次取回该内容。我们根据在数据库中存储的数据类型进行了如下分类:

数据类型 对象类型
blob git 对象(数据对象)
tree 树对象
commit 提交对象

2.1 hash-object 存储对象

git hash-object 命令可将任意数据保存于 .git/objects 目录(即 对象数据库),并返回指向该数据对象的唯一键值。

  • -w 选项会指示该命令不要只返回键,还要将该对象写入数据库中。
  • --stdin 选项则指示该命令从标准输入读取内容;若不指定此选项,则在命令尾部给出带存储文件的路径。

示例一:echo 'test content' | git hash-object -w --stdin

示例二:git hash-object -w test.txt

向数据库写入内容,并返回对应键值:

首先,我们需要初始化一个新的 Git 版本库,并确认 .git/objects 目录不包含文件:

shell 复制代码
# 初始化新仓库
$ git init test
Initialized empty Git repository in /tmp/test/.git/
# 进入目录
$ cd test
# 查看目录和文件
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
# 确认 objects 目录中不包含文件
$ find .git/objects -type f

可以看到 Git 对 objects 目录进行了初始化,并创建了 pack 和 info 子目录,但均为空。接着,我们用 git hash-object 创建一个新的数据对象并将它手动存入新 Git 数据库中:

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

在这种最简单的形式中,git hash-object 会接受你传给它的东西,而它只会返回可以存储在 Git 仓库中的唯一键。

git hash-object 命令输出一个长度为 40 个字符的校验和。这是一个 SHA-1 哈希值------一个将带存储的数据外加一个头部信息(header)一起做 SHA-1 校验运算而得的校验和。现在我们查看 Git 是如何存储数据的:

shell 复制代码
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

如果再次查看 objects 目录,那么可以在其中找到一个与新内容对应的文件。这就是开始时 Git 存储内容的方式------一个文件对应一条内容,以改内容加上特定头部信息一起的 SHA-1 校验和为文件命名。校验和的前两个字符用于命名子目录,余下的 38 个字符用作文件名。

2.2 cat-file 查看对象

cat-file 命令可以从 Git 那里取回存储在对象数据库中的内容。

  • -p 选项可指示该命令自动判断内容的类型,并为我们显示大致的内容。
  • -t 选项可显示内部存储的对象类型。
shell 复制代码
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content

至此,我们已经掌握了如何向 Git 中存入内容,以及如何将它们取出。我们同样可以将这些操作应用与文件中的内容。例如,可以对一个文件进行简单的版本控制。首先,创建一个新文件并将其内容存入数据库:

shell 复制代码
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30

接着,向文件里写入新内容,并再次将其存入数据库:

shell 复制代码
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a

对象数据库记录下了该文件的两个不同版本,当然之前我们存入的第一条内容也还在:

shell 复制代码
$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

现在可以再删掉 test.txt 的本地副本,然后用 Git 从对象数据库中取回它的第一个版本:

shell 复制代码
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1

或者取回第二个版本:

shell 复制代码
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2

然而,记住文件的每一个版本所对应的 SHA-1 值并不现实;另一个问题是:再这个简单的版本控制系统中,文件名并没有被保存------我们仅保存了文件的内容。上述类型的对象我们称之为 数据对象(blob object) 。利用 git cat-file -t 命令,我们就可以根据对象的 SHA-1 值获取 Git 内部存储的对象类型。

shell 复制代码
$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob

问题:

  1. 记住文件的每一个版本所对应的 SHA-1 值并不现实。
  2. 在 Git 中,我们仅保存了文件内容,文件名没有被保存。

解决方案:树对象


三、树对象

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

我们可以通过 update-index、write-tree、read-tree 等命令来构建树对象并塞入到暂存区。

在讲构建树对象之前,我们需要先了解一个命令 git ls-files。

3.1 ls-files 查看暂存区

git ls-files -s:查看暂存区当前的样子。

  • -s 选项来显示文件的状态。

3.2 update-index 创建暂存区

首先,清空目录,初始化一个新的仓库,并将 test.txt 的首个版本放入数据库。

shell 复制代码
# 初始化新的仓库
$ git init
Initialized empty Git repository in C:/Users/lenovo/Desktop/test/.git/
# 创建首个版本
$ echo 'test.txt v1' > test.txt
# 放入数据库
$ git hash-object -w test.txt
warning: in the working copy of 'test.txt', LF will be replaced by CRLF the next time Git touches it
560a3d89bf36ea10794402f6664740c284d4ae3b
# 查看暂存区
$ git ls-files -s

我们可以看到,此时暂存区为空。然后我们使用 update-index 命令为 test.txt 的首个版本创建一个暂存区:

shell 复制代码
# 创建暂存区
$ git update-index --add --cacheinfo 100644 560a3d89bf36ea10794402f6664740c284d4ae3b test.txt
  • --add 选项:因为此前该文件并不在暂存区中,首次需要 --add。

  • --cacheinfo 选项:因为将要添加的文件位于 Git 数据库中,而不是位于当前目录下,所以需要 --cache。

  • 文件模式:

    100644,表明这是一个普通文件;

    100755,表示一个可执行文件;

    120000,表示一个符号链接。

再次查看暂存区:

shell 复制代码
$ git ls-files -s
100644 560a3d89bf36ea10794402f6664740c284d4ae3b 0       test.txt

这样我们的 SHA-1 键值和文件名就对应上了。然后,我们再去数据库看一下有没有新增内容:

shell 复制代码
$ find .git/objects/ -type f
.git/objects/56/0a3d89bf36ea10794402f6664740c284d4ae3b

发现没有新增内容,还是只有首个版本的 test.txt,这就说明 update-index 只在暂存区内生成数据,并不会操作数据库。

3.3 write-tree 生成树对象

git write-tree 命令是将暂存区做一个快照。相当于给暂存区拍张照,生成一个树对象,放到数据库里面去。

继续实战,将 3.2 中暂存区中的内容生成一个树对象:

shell 复制代码
$ git write-tree
06e21bb0105e2de6c846725a9a7172f57dd1af96

接下来,我们看一下返回的哈希所对应的数据类型:

shell 复制代码
$ git cat-file -t 06e21bb0105e2de6c846725a9a7172f57dd1af96
tree

我们可以看到,write-tree 命令生成的数据类型为 树对象。然后我们看一下这个树对象在不在我们的版本库(数据库)里面:

shell 复制代码
$ find .git/objects/ -type f
.git/objects/06/e21bb0105e2de6c846725a9a7172f57dd1af96
.git/objects/56/0a3d89bf36ea10794402f6664740c284d4ae3b

可以看到数据库中的对象增加了一个刚放进去的数据对象。

由此可知,我们可以通过多次执行 update-index 命令,在缓存区中存放多个内容,然后通过 write-tree 命令一次性将所有暂存区的内容生成快照,保存为树对象。

总结:

  • git 对象,代表文件的初始版本;
  • 树对象,代表项目的初始版本。

补充:上面执行了 write-tree 生成树对象后,我们可以再去看一下暂存区中还有没有内容:

shell 复制代码
$ git ls-files -s
100644 560a3d89bf36ea10794402f6664740c284d4ae3b 0       test.txt

由执行结果可知,write-tree 命令将暂存区生成快照保存为树对象后,并不会将暂存区清空。

3.4 更新暂存区,生成第二棵树

接下来,我们继续操作:

1)新增 new.txt 到数据库:

shell 复制代码
# 新增一个 new.txt 文件
$ echo "new v1" > new.txt
# 生成 git 对象
$ git hash-object -w new.txt
warning: in the working copy of 'new.txt', LF will be replaced by CRLF the next time Git touches it
eae614245cc5faa121ed130b4eba7f9afbcc7cd9
# 查看数据库
$ find .git/objects/ -type f
.git/objects/06/e21bb0105e2de6c846725a9a7172f57dd1af96
.git/objects/56/0a3d89bf36ea10794402f6664740c284d4ae3b
.git/objects/ea/e614245cc5faa121ed130b4eba7f9afbcc7cd9

2)创建 test.txt 的第二个版本:

shell 复制代码
# 编辑 test.txt
$ vi test.txt
# 查看 test.txt
$ cat test.txt
test.txt v1
test.txt v2
# 生成 git 对象
$ git hash-object -w test.txt
warning: in the working copy of 'test.txt', LF will be replaced by CRLF the next time Git touches it
c31fb1e89d8b6b3ef34cdb5a2f999d6e29b822ba

3)查看数据库:

shell 复制代码
$ find .git/objects/ -type f
.git/objects/06/e21bb0105e2de6c846725a9a7172f57dd1af96  # workspace项目的第一个版本(树对象)
.git/objects/56/0a3d89bf36ea10794402f6664740c284d4ae3b  # test.txt文件的第一个版本(git对象)
.git/objects/c3/1fb1e89d8b6b3ef34cdb5a2f999d6e29b822ba  # test.txt文件的第二个版本(git对象)
.git/objects/ea/e614245cc5faa121ed130b4eba7f9afbcc7cd9  # new.txt文件的第一个版本(git对象)

4)生成项目的第二个版本

数据库中的前两个对象属于项目的第一个版本,我们可以将后面两个对象生成项目的第二个版本:

shell 复制代码
# 更新暂存区 test.txt
$ git update-index --cacheinfo 100644 c31fb1e89d8b6b3ef34cdb5a2f999d6e29b822ba test.txt
# 查看暂存区
$ git ls-files -s
100644 c31fb1e89d8b6b3ef34cdb5a2f999d6e29b822ba 0       test.txt

可以发现由于暂存区中 test.txt 的更新,对应的哈希值也随之更新了。然后我们继续将 new.txt 放入暂存区,并生成项目第二个版本的树对象:

shell 复制代码
# 创建缓存区 new.txt
$ git update-index --add --cacheinfo 100644 eae614245cc5faa121ed130b4eba7f9afbcc7cd9 new.txt
# 查看缓存区
$ git ls-files -s
100644 eae614245cc5faa121ed130b4eba7f9afbcc7cd9 0       new.txt
100644 c31fb1e89d8b6b3ef34cdb5a2f999d6e29b822ba 0       test.txt
# 生成树对象
$ git write-tree
9d74ec4055e0f1edc1921d749c250380ca7b5ebd
# 查看数据库

补充: git update-index 命令后面加 --add 选项,可以直接将文件放到数据库中,然后再为文件创建、更新缓存区。例如:上面 new.txt 文件的 git hash-object 命令和 git update-index 命令可以合并为:

shell 复制代码
$ git update-index --add new.txt

5)查看数据库

shell 复制代码
$ find .git/objects/ -type f
.git/objects/06/e21bb0105e2de6c846725a9a7172f57dd1af96  # workspace项目的第一个版本(树对象)
.git/objects/56/0a3d89bf36ea10794402f6664740c284d4ae3b  # test.txt文件的第一个版本(git对象)
.git/objects/9d/74ec4055e0f1edc1921d749c250380ca7b5ebd  # workpace项目的第二个版本(树对象)
.git/objects/c3/1fb1e89d8b6b3ef34cdb5a2f999d6e29b822ba  # test.txt文件的第二个版本(git对象)
.git/objects/ea/e614245cc5faa121ed130b4eba7f9afbcc7cd9  # new.txt文件的第一个版本(git对象)

此时,数据库中共 5 个对象,项目的两个版本的对象关系如下:

3.5 read-tree 树A加入树B,生成第三棵树

git read-tree 命令是将一棵树读取到暂存区。然后配合 write-tree 命令再将暂存区重新做一个快照,生成一个树对象,放到数据库里面去。

  • --prefix=bak 选项指定了树的前缀为"bak"。

继续实战,将 3.3 中的树对象加入到 3.4 中的树对象:

shell 复制代码
# 从第一棵树读取到暂存区
$ git read-tree --prefix=bak 06e21bb0105e2de6c846725a9a7172f57dd1af96
# 查看暂存区
$ git ls-files -s
100644 560a3d89bf36ea10794402f6664740c284d4ae3b 0       bak/test.txt
100644 eae614245cc5faa121ed130b4eba7f9afbcc7cd9 0       new.txt
100644 c31fb1e89d8b6b3ef34cdb5a2f999d6e29b822ba 0       test.txt

我们可以看到,读取后,暂存区中增加了一个 bak 前缀的 test.txt 文件。我们继续将暂存区生成第三棵树:

shell 复制代码
# 生成新的树对象
$ git write-tree
17d1ee3eac87d38448e7ff2cc92e88ed4e9aa094
# 查看数据库
$ find .git/objects/ -type f
.git/objects/06/e21bb0105e2de6c846725a9a7172f57dd1af96  # workspace项目的第一个版本(树对象)
.git/objects/17/d1ee3eac87d38448e7ff2cc92e88ed4e9aa094  # workspace项目的第三个版本(树对象)
.git/objects/56/0a3d89bf36ea10794402f6664740c284d4ae3b  # test.txt文件的第一个版本(git对象)
.git/objects/9d/74ec4055e0f1edc1921d749c250380ca7b5ebd  # workpace项目的第二个版本(树对象)
.git/objects/c3/1fb1e89d8b6b3ef34cdb5a2f999d6e29b822ba  # test.txt文件的第二个版本(git对象)
.git/objects/ea/e614245cc5faa121ed130b4eba7f9afbcc7cd9  # new.txt文件的第一个版本(git对象)

目前上面的数据结构,我们可以整理为下图:(图中同时包含了三棵树)

补充: read-tree 命令仅作了解,实际过程中,我们并不会将一棵树加入另一棵树中。

3.6 查看树对象

由于树对象和 git 对象一样,都是存储在数据库中,我们使用 git cat-file 命令查看即可:

shell 复制代码
$ git cat-file -p 17d1ee3eac87d38448e7ff2cc92e88ed4e9aa094
040000 tree 06e21bb0105e2de6c846725a9a7172f57dd1af96    bak
100644 blob eae614245cc5faa121ed130b4eba7f9afbcc7cd9    new.txt
100644 blob c31fb1e89d8b6b3ef34cdb5a2f999d6e29b822ba    test.txt

问题:

  1. 树对象的提交只有哈希和文件名,我们看不到文件版本的相关说明。

解决方案:提交对象


四、提交对象

4.1 commit-tree 创建提交对象

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

  • -p 选项可以用于指定父提交对象的 SHA-1 值。

注意: git commit-tree 不但生成提交对象,而且会将对象的快照(树对象)提交到本地库中。

我们先来创建第一棵树的提交对象:

shell 复制代码
# 创建提交对象
$ echo 'first commit' | git commit-tree 06e21bb0105e2de6c846725a9a7172f57dd1af96
92f28813cce27fa514cf3be80a4d4b158afdca27

4.2 查看提交对象

由于提交对象和 git 对象、树对象一样,都是存储在数据库中,我们使用 git cat-file 命令查看即可:

shell 复制代码
# 查看数据类型
$ git cat-file -t 92f28813cce27fa514cf3be80a4d4b158afdca27
commit
# 查看数据内容
$ git cat-file -p 92f28813cce27fa514cf3be80a4d4b158afdca27
tree 06e21bb0105e2de6c846725a9a7172f57dd1af96 # 树对象
author acgkaka <acgkaka@example.com> 1698337169 +0800 # 作者
committer acgkaka <acgkaka@example.com> 1698337169 +0800 # 提交者

first commit # 提交注释

4.3 创建第二次提交对象

接下来,我们可以创建第二次提交对象,然后将第一次提交对象作为父对象:

补充: 这里可以只用树对象和父提交对象 SHA-1 值的前部分(一般6位)即可。

shell 复制代码
# 创建提交对象
$ echo 'second commit' | git commit-tree 9d74ec -p 92f288
1be40a21a3cc7c92e73721967abe30c9ce2a5a51
# 查看提交对象
$ git cat-file -p 1be40a
tree 9d74ec4055e0f1edc1921d749c250380ca7b5ebd # 树对象
parent 92f28813cce27fa514cf3be80a4d4b158afdca27 # 父提交对象
author acgkaka <acgkaka@example.com> 1698338744 +0800 # 
committer acgkaka <acgkaka@example.com> 1698338744 +0800

second commit

第三次提交对象以此类推。三次提交对象创建完毕后,图示如下:

五、总结

  • git对象 里面存储的不是增量,而是一次快照。
  • 树对象 才是真正的一个项目版本的快照;
  • 提交对象 只是对树对象做了一次封装。

补充: 提交对象也说明了 为什么 Git 回滚如此简单,因为每个分支名实际相当于一个指向提交对象的指针,当进行版本回退时,只需要将指针调整到需要的提交对象即可,其他地方均无需改动。

整理完毕,完结撒花~ 🌻

参考地址:

1.深入Git底层原理丨一套掌握git版本控制系统,https://www.bilibili.com/video/BV1Yi4y137eF

相关推荐
nvd118 小时前
Flink 批处理驱动的湖仓 AI 财务研报流水线:从双流广播汇聚到 Agent 智能决策与原生 Iceberg 落盘全景实战
大数据·人工智能·flink
计算机编程指导师10 小时前
【计算机毕设推荐】基于Hadoop+Spark的公共交通运营数据分析与可视化系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习
大数据·hadoop·python·计算机·数据分析·spark·交通
QYR_1111 小时前
机械制造用固体润滑涂层市场分析:2032年全球市场规模预计达8.78亿元,CAGR为6.2%
大数据·人工智能·物联网
数字化顾问11 小时前
(144页PPT)数字化转型汽车企业数智化战略规划(附下载方式)
大数据
zttbee12 小时前
git命令
git
智慧物业老杨13 小时前
数字底座,重构小区治理秩序
大数据
计算机编程指导师13 小时前
【计算机毕设选题推荐】基于Hadoop的宫颈癌变进程光谱数据分析系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习
大数据·hadoop·python·数据分析·spark·毕业设计·宫颈癌变
guhunyihan14 小时前
无代码系统搭建平台,探索恐象 AI 带来的开发新范式
大数据·人工智能
计算机编程指导师14 小时前
【计算机毕设选题推荐】基于Hadoop+Spark的化妆品销售大数据分析系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习
大数据·hadoop·python·spark·毕业设计·课程设计·化妆品
benchmark_cc14 小时前
前复权数据每天可能变化,历史数据库应该覆盖还是保留原始价格?
大数据·python·pandas·量化交易·股票数据·quantdash