说明:
- 由于个人水平有限,文笔一般,难免有些地方表达不准确,还望大家留言提出
- 文章中的内容,为那些了解一些 git 本地操作,且想了解一下底层命令的Git使用者提供一些参考
- 由于个人理解也不够深,无法做到面面俱到的讲解
主要内容:
- Git 目录结构,Git 数据库
- 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 操作诠释
- 创建名为 GitDemo 的一个文件夹
- 进入该文件夹内
- 执行初始化命令
注:
- 初始化后,文件夹内只有一个 .git 的文件夹【该文件夹默认隐藏。没有看到的话,请自行搜索“如何显示隐藏文件夹”】
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
文件夹的目录结构
- 注:
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 问题
- 记住文件的每一个版本所对应的 SHA-1 值并不现实
- 在这个(简单的版本控制)系统中,文件名并没有被保存(仅保存了文件的内容)
2. 暂存区、树对象
2.1 树对象
- 解决问题:
- 它能解决文件名保存的问题
- 允许将多个文件组织到一起
- 诠释:
- Git 以一种类似于 UNIX 文件系统的方式存储内容,但作了些许简化。
- 所有内容均以树对象和数据对象的形式存储,其中树对象对应了 UNIX 中的目录项,数据对象则大致上对应了 inodes 或文件内容。
- 一个树对象包含了一条或多条树对象记录(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 问题
若想重用这些快照(树对象),你必须记住所有树对象的 SHA-1值
完全不知道是谁保存了这些快照
快照在什么时刻保存的
为什么保存这些快照
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个树对象快照的提交对象
- 10acca35728f4456bc11a4786072a3222bdc7acc
- d66c810f80445ce7ba1f8d3e5c0c41204e1d4da9
- 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所做的工作实质:
- 将被改写的文件保存为数据对象
- 更新暂存区
- 记录树对象
- 最后创建一个指明了顶层树对象和父提交对象的提交对象
参考文献:
Pro Git:Git 内部原理 网址:https://git-scm.com/book/zh/v2
如若想深入探究Git,请阅读该书(免费,开源)
本文属于原创,转载请注明出处