头像

Git基础

pubDate: 2026-07-09

lastModified: 2026-07-24

Written by: Jack-S-H

本文将按照提交变更的工作流程,说明Git部分底层原理,适合Git初学者,建议跟随本文实际操作,可以更容易理解文章内容。

Git

learning

初级

初始化仓库

让我们从这条命令开始探索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文件夹的影响:

  1. 在objects中生成Blob对象

  2. 修改index文件,记录文件信息。

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
  1. 提交变更后,Git会在objects文件夹中生成一个Commit对象,为每个目录层级生成一个Tree对象。

  2. refs\heads目录下的分支文件更新,内容变成新Commit对象的哈希值

这个文件作为指针,可以直接查看,本质上是文本文件,内容是当前Commit的哈希值。

  1. index文件中的一些信息会被更新

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服务器(我之后可能会发布一篇关于这个的文章)

参考:

How Git Internally Works

Book