Git 底层命令浅析(一)

说明:

  • 由于个人水平有限,文笔一般,难免有些地方表达不准确,还望大家留言提出
  • 文章中的内容,为那些了解一些 git 本地操作,且想了解一下底层命令的Git使用者提供一些参考
  • 由于个人理解也不够深,无法做到面面俱到的讲解

主要内容:

  1. Git 目录结构,Git 数据库
  2. Git 对象:数据对象、树对象、提交对象
一、Git
1. 软件安装
1.1 下载
  • Git 官网地址:https://git-scm.com/

根据自己电脑的操作系统,下载并安装对应的版本。

1.2 安装
  • 请自行搜索安装方式,不再重复写了
1.3 版本
$ git --version
git version 2.26.2.windows.1 # 本文编写时使用的Git的版本
2. 初始化
2.1 代码操作
$ mkdir GitDemo
$ cd GitDemo
$ git init
Initialized empty Git repository in D:/GitDemo/.git/
2.2 操作诠释
  1. 创建名为 GitDemo 的一个文件夹
  2. 进入该文件夹内
  3. 执行初始化命令

注:

  • 初始化后,文件夹内只有一个 .git 的文件夹【该文件夹默认隐藏。没有看到的话,请自行搜索“如何显示隐藏文件夹”】

Null

3. 配置
  • 仅设置当前项目生效的配置,在查看提交历史信息时会看到
# 设置 用户名 和 邮箱
$ git config user.name “test”
$ git config user.email "test@163.com"

# 查看用户名和邮箱
$ git config user.name
test
$ git config user.email
test@163.com
4. 目录结构
  • GitDemo 项目中 .git 文件夹的目录结构

.git 目录结构

  • 注:

index 暂存区非默认目录结构,但在使用Git进行版本控制时,它会被Git创建。这里为笔者添加,并用红色标示

HEAD、objects、refs、index 为Git核心组成部分,接下来着重以这四个部分展开。

二、命令
1. Linux 常用命令
  • 查看指定目录下的文件和文件夹
# 语法:find 目录路径
# 示例:
find .git
  • 查看指定目录下的文件【不含文件夹】
# 语法:find 目录路径 -typf f
# 示例:
find .git/objects/ -type f
  • 清屏
clear
2. Git 底层命令

文中以下内容执行Git命令时,所处目录均为:/d/GitDemo【即:D:\GitDemo】

  • 命令:git hash-object
  • 作用

    接受传给它的数据,返回可以存储在Git仓库中的唯一键

  • 选项

    -w:不仅返回键值对的键,还要将该对象写入Git数据库(.git/objects)中

    –stdin:从标准输入读取内容。若不指定此选项,则须在命令尾部给出待存储文件的路径

  • git cat-file
  • 作用

    从Git 数据库中取回数据

  • 选项

    -p:自动判断内容的类型,取出对应文件中的内容信息

    -t:获取内容信息的类型

三、Git 对象
  • Git对象:数据对象、树对象、提交对象

最初均以单独文件的形式保存在 Git 数据库(.git/objects)目录下

1. 数据对象
1.1 Git 实质
  • Git 是一个内容寻址文件系统

即:Git 的核心部分是一个简单的键值对数据库(key-value data store)。 你可以向 Git 仓库中插入任意类型的内容,它会返回一个唯一的键,通过该键可以在任意时刻再次取回该内容。

1.2 例子-1
  • 示例:Git数据对象的存放、查看、提取【输入流】
# 步骤1:向Git 对象数据库中存数据
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4

# 步骤2:查看 Git 对象数据库汇中的数据
$ find .git/objects/ -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

# 步骤3:从Git数据库中取数据内容
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
  • 诠释

步骤一:
返回值为d670460b4b4aece5915caf5c68d12f560a9fe3e4
这是一个长度为 40 个字符的校验和,是一个SHA-1值【一个将待存储的数据外加一个头部信息(header)一起做 SHA-1 校验运算而得的校验和】

步骤二:
校验和的前两个字符用于命名子目录,余下的 38 个字符则用作文件名。38个字符文件名的文件,其内容是经压缩算法处理后的数据

步骤三:
通过校验和(SHA-1值),从Git对象中取出数据内容

1.3 例子-2
  • 示例:Git数据对象的存放、查看、提取【文件】
# 步骤-1:通过命令创建文件
$ echo 'version 1' > test.txt

# 步骤-2:将test.txt存储为Git 数据对象,并返回 SHA-1 值
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30

# 步骤-3:向 test.txt 文件中写入新内容
$ echo 'version 2' > test.txt

# 步骤-4:将test.txt再次存入Git数据库,并返回 SHA-1 值
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a

# 步骤-5:查看Git数据库中的文件
$ find .git/objects/ -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

# 步骤-6:删掉 test.txt 的本地文件
$ rm -f test.txt

# 步骤-7(1):使用Git从Git数据库中取回test.txt 的第1个版本,并查看
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a > test.txt
$ cat test.txt
version 1

# 步骤7(2):使用Git从Git数据库中取回test.txt 的第2个版本,并查看
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2

# 步骤-8:根据SHA-1值,获取其文件内容所属的Git对象的具体类型
$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob
1.4 问题
  1. 记住文件的每一个版本所对应的 SHA-1 值并不现实
  2. 在这个(简单的版本控制)系统中,文件名并没有被保存(仅保存了文件的内容)
2. 暂存区、树对象
2.1 树对象
  • 解决问题:
  1. 它能解决文件名保存的问题
  2. 允许将多个文件组织到一起
  • 诠释:
  1. Git 以一种类似于 UNIX 文件系统的方式存储内容,但作了些许简化。
  2. 所有内容均以树对象和数据对象的形式存储,其中树对象对应了 UNIX 中的目录项,数据对象则大致上对应了 inodes 或文件内容。
  3. 一个树对象包含了一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象的 SHA-1 指针,以及相应的模式、类型、文件名信息。
2.2 例子-3
  • 示例:暂存区创建、树对象创建
# 步骤0:先查看下当前 Git 对象数据库中有的 数据对象
$ find .git/objects/ -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

# 步骤1:将 text.txt 文件的首个版本人为的加入一个新的暂存区【即:index,在目录结构图中已标红】
$ git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt

# 步骤2:将暂存区的状态记录为一个快照,写入树对象
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579

# 步骤3:获取其内容信息
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30    test.txt

# 步骤4:获取其内容类型
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree

# 步骤5:查看 Git数据库中的文件
$ find .git/objects/ -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579

# 步骤6:新建一个文件,以及 test.txt 的第2个版本,加入暂存区
$ echo 'new file' > new.txt
$ git update-index --add --cacheinfo 100655  1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ git update-index --add new.txt

# 步骤7:将当前暂存区的状态记录为一个树对象
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341

# 步骤8:查看该树对象的内容信息
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92    new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a    test.txt
2.3 例子-4
  • 示例:将一个树对象A添加到另一个树对象B,A是B的子树对象
# 步骤1:将第一个树对象加入第二个树对象,
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579

# 步骤2:使其成为新的树对象的一个子目录
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614

# 步骤3:查看新的树对象的内容信息
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579    bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92    new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a    test.txt
2.4 问题
  1. 若想重用这些快照(树对象),你必须记住所有树对象的 SHA-1值

  2. 完全不知道是谁保存了这些快照

  3. 快照在什么时刻保存的

  4. 为什么保存这些快照

3. 提交对象
  • 章节“2.4 问题” 中的4个问题中涉及到的信息,都是提交对象要保存的基本信息

提交对象:为此,需要指定一个树对象的 SHA-1 值,以及该提交对象的父提交对象(如果有的话)

3.1 例子-5
  • 示例:根据已有树对象,创建提交对象
# 步骤0:根据以上的例子,Git数据库中已生成的树对象有以下3个
1、d8329fc1cc938780ffdd9f94e0d364e0ea74f579
2、0155eb4229851634a0f03eb265b69f5a2d56f341 
3、3c4e9cd789d88d8d89c1073707c3585e41b0e614 含子树对象1

# 步骤1:创建第1个树对象的提交对象【注:由于时间和user数据不同,会得到不同的SHA-1值】
$ echo 'first commit' | git commit-tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
10acca35728f4456bc11a4786072a3222bdc7acc

# 步骤2:获取提交对象的信息【可取40字符校验和的前6位作为简短键使用】
$ git cat-file -p 10acca
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author test <test@163.com> 1589787933 +0800
committer test <test@163.com> 1589787933 +0800

first commit
# 步骤3:创建第2个树对象的提交对象
$ echo 'second commit' | git commit-tree 0155eb -p 10acca
d66c810f80445ce7ba1f8d3e5c0c41204e1d4da9

# 步骤4:创建第3个树对象的提交对象
$ echo 'third commit' | git commit-tree 3c4e9c -p d66c81
75708b7e9d3a651d745b701f1e1c20ce64a14a39
3.2 例子-6
  • 示例:根据 SHA-1 值查看提交记录

例子-5 的几个步骤,创建了分别指向3个树对象快照的提交对象

  1. 10acca35728f4456bc11a4786072a3222bdc7acc
  2. d66c810f80445ce7ba1f8d3e5c0c41204e1d4da9
  3. 75708b7e9d3a651d745b701f1e1c20ce64a14a39
# 步骤1:对最后一个提交对象的SHA-1运行 git log 命令,获取提交历史
$ git log --stat 75708b
commit 75708b7e9d3a651d745b701f1e1c20ce64a14a39
Author: test <test@163.com>
Date:   Mon May 18 15:52:04 2020 +0800

    third commit

 bak/test.txt | 1 +
 1 file changed, 1 insertion(+)

commit d66c810f80445ce7ba1f8d3e5c0c41204e1d4da9
Author: test <test@163.com>
Date:   Mon May 18 15:51:05 2020 +0800

    second commit

 new.txt  | 1 +
 test.txt | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

commit 10acca35728f4456bc11a4786072a3222bdc7acc
Author: test <test@163.com>
Date:   Mon May 18 15:45:33 2020 +0800

    first commit

 test.txt | 1 +
 1 file changed, 1 insertion(+)
:...skipping...
commit 75708b7e9d3a651d745b701f1e1c20ce64a14a39
Author: test <test@163.com>
Date:   Mon May 18 15:52:04 2020 +0800

    third commit

 bak/test.txt | 1 +
 1 file changed, 1 insertion(+)

commit d66c810f80445ce7ba1f8d3e5c0c41204e1d4da9
Author: test <test@163.com>
Date:   Mon May 18 15:51:05 2020 +0800
second commit

 new.txt  | 1 +
 test.txt | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

commit 10acca35728f4456bc11a4786072a3222bdc7acc
Author: test <test@163.com>
Date:   Mon May 18 15:45:33 2020 +0800

    first commit

 test.txt | 1 +
 1 file changed, 1 insertion(+)
3.3 提交记录
  • Git 数据库已有的Git 对象
  • 数据对象

1:83baae61804e65cc73a7201a7252750c76066a30 83baae version 1
2:1f7a7a472abf3dd9643fd615f6da379c4acb3e3a 1f7a7a version 2
3:fa49b077972391ad58037050f2a75f74e3671e92 fa49b0 new file

  • 树对象

1:d8329fc1cc938780ffdd9f94e0d364e0ea74f579 d8329f -b[83baae]
2:0155eb4229851634a0f03eb265b69f5a2d56f341 0155eb -b[1f7a7a,fa49b0]
3:3c4e9cd789d88d8d89c1073707c3585e41b0e614 3c4e9c -b[1f7a7a,fa49b0,d8329f] 含树对象1

  • 提交对象

1:10acca35728f4456bc11a4786072a3222bdc7acc 10acca -t[d8329f]
2:d66c810f80445ce7ba1f8d3e5c0c41204e1d4da9 d66c81 -t[0155eb] -p[10acca]
3:75708b7e9d3a651d745b701f1e1c20ce64a14a39 75708b -t[3c4e9c ] -p[d66c81]

  • 将以上的各个对象,按照其组织关系,整理成以下的示意图
    提交记录图解
四、总结

git add 和 git commit 命令执行时,Git所做的工作实质:

  1. 将被改写的文件保存为数据对象
  2. 更新暂存区
  3. 记录树对象
  4. 最后创建一个指明了顶层树对象和父提交对象的提交对象

参考文献:

Pro Git:Git 内部原理 网址:https://git-scm.com/book/zh/v2
如若想深入探究Git,请阅读该书(免费,开源)

本文属于原创,转载请注明出处

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值