Git
我还在考虑这里要放什么东西。。。
-
在objects中生成Blob对象
-
修改
index文件,记录文件信息。 -
提交变更后,Git会在
objects文件夹中生成一个Commit对象,为每个目录层级生成一个Tree对象。 -
refs\heads目录下的分支文件更新,内容变成新Commit对象的哈希值
- index文件中的一些信息会被更新
本文将按照提交变更的工作流程,说明Git部分底层原理,适合Git初学者,建议跟随本文实际操作,可以更容易理解文章内容。
初始化仓库
让我们从这条命令开始探索Git的世界。
git init git-learning
这条命令会在当前目录新建一个gitlearning文件夹,并在其中构建一个.git目录
cd git-learning/.git
ls
config description HEAD hooks/ info/ objects/ refs/
object/:用于存储对象文件,项目数据都存储在这里
Git有3种对象文件:Blob、Tree、Commit,分别存放文件内容、层次结构、版本历史记录。这是本文的关键内容,下面将逐个进行解释。
info/、pack/:这两个文件夹在Git优化时会使用,一般来说不用管
refs/:指针仓库,包含heads/、remotes/、tags/ ,分别存放本地分支、远程分支、tag。
info/:Git辅助信息目录,其中的exclude文件的作用类似.gitignore。
hooks:存放hook脚本,如pre-commit等,可以在特定的Git事件之前自动执行自定义脚本。
HEAD:当前工作分支的引用。
config:Git配置文件,包含用户信息,远程仓库地址等数据
git add <文件名>
新建一个文件,然后使用这条命令将文件加入暂存区
也可以使用.,表示将所有文件加入暂存区。
git add .
对.git文件夹的影响:
index:这个文件包含了整个目录树,也可以称之为暂存区,只有在首次暂存文件后被创建。
解释一下下面这条命令的输出:文件权限、哈希值、阶段号、文件路径
git ls-files --stage 100644 30d74d258442c7c65512eafab474568dd706c430 0 test.txt
生成Blob:对于add的每一个文件,Git使用SHA-1和文件内容生成哈希值,在objects目录下,使用哈希值的前两位命名子目录,在子目录中生成一个Blob对象,使用剩下38位命名。
不过有的时候,Git不会为一个文件生成Blob,为什么会这样?让我们先了解一下Blob是什么。
Blob
全称是binary large object,是文件内容的完整复制,每个文件都对应一个Blob,是对文件内容进行压缩后的二进制文件,不包含任何元数据,甚至是文件名
这样设计的一个好处就是两个内容完全相同的文件可以复用同一个Blob,无论文件的路径和名称如何变化,这是一个非常高效的去重设计,这也是为什么有时候Git可以不为暂存的文件生成Blob。
既然Blob不存储文件名,你觉得文件名会存储在哪里?
git cat-file <哈希值>:这条命令可以查看git对象的数据,-p参数输出文件内容,-t参数输出对象类型
git cat-file -t 30d74d258442c7c65512eafab474568dd706c430
blob
git cat-file -p 30d74d258442c7c65512eafab474568dd706c430
test
使用命令时,不需要输入完整的哈希值,最少只需使用4位哈希值。
git commit
之前使用了git add将文件暂存,现在可以提交变更,并附加提交说明。
git commit -m "first commit"
[master cdff5ec] first commit
1 file changed, 1 insertion(+)
create mode 100644 test2.txt
这个文件作为指针,可以直接查看,本质上是文本文件,内容是当前Commit的哈希值。
Tree
Tree对象可以存储Blob和其他Tree对象,用来构建文件和子目录的完整层次结构。
git cat-file e5e92b97d6c9add7460cf898ca57afbdb2c8815c -p
100644 Blob f73693a16cdf594532ee4c423a46d32ce3430c4e first.txt
树对象中有文件权限,对象类型、哈希值和文件名
之前我们有一个疑问,文件名称存储在哪里?答案就是Tree对象。
Commit
Commit对象将一个个版本历史节点串在了一起,可以使用之前提交时返回的哈希值,也可以使用git log列出所有的Commit对象。
git log
commit 8b6185ac0d3d8a743521ed08c1227fd881324481 (HEAD -> master)
Author: Jack-S-H <58410600+Jack-S-H@users.noreply.Github.com>
Date: Wed Jul 8 16:03:05 2026 +0800
first commit
git cat-file 8b6185ac0d3d8a743521ed08c1227fd881324481 -p
tree e5e92b97d6c9add7460cf898ca57afbdb2c8815c
author Jack-S-H <58410600+Jack-S-H@users.noreply.Github.com> 1783497785 +0800
committer Jack-S-H <58410600+Jack-S-H@users.noreply.Github.com> 1783497785 +0800
first commit
Commit对象中有作者、提交者、提交日期和提交消息
还有一个Tree对象
第二次及以后的Commit对象还会多一个上次提交的哈希值
parent 8b6185ac0d3d8a743521ed08c1227fd881324481
这些对象类型以哈希值命名,所以非常难记,但是我们可以为Commit对象创建别名——他们本质是指向Commit对象的引用(References)。
分支(Branch)
分支是指向Commit对象的指针,是Commit的引用,每个分支都是一个文件,存储在refs/中,而每个分支的文件内容,就是对应Commit对象的哈希值,代表这个分支的最新一次提交。
cat master
c751429b4da18797552c5b86cebad84a71c23422
分支也有引用——HEAD,这个文件在文章开头也有提到过,保存当前工作分支的路径,意思是指向当前工作的分支的指针。
cat HEAD ref: refs/heads/master
标签
和分支类似,文件存储在refs/tags/中,不同的是Tag文件在创建后无法再修改,也就是说Tag是一个分支的不可变引用
使用以下命令可以创建tag或显示所有tag
git tag first
git tag
first
总结
Blob存储文件内容,Tree存储目录结构,Commit存储版本记录,连接所有内容,非常优秀的功能解耦,非常优雅
Git每次提交都会生成Blob、Commit、Tree,所以Git管理的历史版本记录了非常完整的数据
对象只增不减,只有在gc时才会处理悬空提交,之后对象文件夹中的info和pack文件夹中会出现新文件
运行gc后观察到的文件变化:refs/:heads清空了,logs/:heads被修改了,info/:refs、packed-refs、index、orig_head被修改了
Git是一个很有趣的东西,你不需要Gitee,Gitlab,甚至不要Git,只需要ssh就可以实现一个Git服务器(我之后可能会发布一篇关于这个的文章)