土法炼钢兴趣小组的算法知识备份

【Git 内部】.git 目录全景:三棵树与仓库布局

文章导航

分类入口
opensource
标签入口
#git#git-internals#gitrepository-layout#objects#refs#index

源码下载

本文相关源码已整理,共 1 个文件。

打开下载目录 →

目录

git clone 完成后,项目根目录除了源码,还多了一个 .git。很多人把它当成黑盒:知道「历史在里面」,但说不清 objectsrefs 各管什么、index 是不是可有可无、bare 仓库为什么连工作区都没有。后续要读 pack 格式、排查 fsck 报错、写直连对象库的工具,都需要先有一张磁盘级地图

本文对照 Git 官方 gitrepository-layout,用 git init 与一次最小提交的实测文件清单,把 non-bare 与 bare 仓库的布局、三棵树心智模型和主要路径职责钉住。对象字节格式见 松散对象格式


一、常见误区

误区一:Git 保存的是文件 diff。 Git 的核心持久化单元是内容寻址对象(blob、tree、commit、tag),工作区文件是 checkout 时从对象库展开出来的快照,不是逐版本 diff 补丁。

误区二:分支是目录里的副本。 分支只是 refs/heads/<name> 里一行 commit 对象 ID;同一份 blob 可被多个 tree 引用,不会被「每个分支复制一份」。

误区三:.git 里只有 objects 重要。 没有 refs 就不知道 commit 的「名字」;没有 index 就无法高效暂存;没有 logs/ 就没有 reflog。缺任何一环,命令层语义都无法落地到磁盘。


二、git init 后的默认文件

在空目录执行 git init(Git 2.54.0,WSL2 Linux),尚未有任何提交时,.git普通文件如下(以下输出经删减,仅列路径):

.git/HEAD
.git/config
.git/description
.git/hooks/*.sample
.git/info/exclude

此时还没有 objects/ 下的数据对象、refs/heads/*index(首次 git add 前可能不存在)和 logs/HEAD 内容为符号引用:

ref: refs/heads/master

表示当前分支名指向 refs/heads/master,而该文件要在第一次提交后才创建。

复现:

mktemp -d && cd $_ && git init -q && find .git -type f | sort

也可运行系列脚本 reproduce/init-list.sh


三、一次提交后的布局

创建 hello.txtgit addgit commit 后,.git 新增关键路径:

路径 职责
.git/objects/ 对象库:松散对象 xx/yyyy… 与后来的 pack/
.git/refs/heads/master 分支 master 指向的 commit SHA
.git/logs/HEADlogs/refs/heads/master reflog 追加日志
.git/index 暂存区(dircache),记录路径→blob SHA 映射

实测 find .git/objects -type f(一次提交、尚未 repack):

.git/objects/2b/24b26a117ef5cfdbf88040e282ec86085e6b1f
.git/objects/aa/a96ced2d9a1c8e72c56b253a0e2fe78393feb7
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a

分别对应 commit、tree、blob 三个松散对象。git cat-file -p HEAD 可见 commit 指向 tree,tree 条目指向 blob——对象图细节留到 第 03 篇

.git 目录树:HEAD、objects、refs 等路径职责
三棵树数据流:工作区、index、HEAD tree 与 git add/commit/checkout

四、主要路径说明

依据 gitrepository-layout 与日常仓库观察,下表覆盖最常碰到的条目。

路径 类型 说明
HEAD 文件 当前检出位置:符号引用 ref: refs/heads/… 或直接写 detached SHA
config 文件 仓库级配置(remote、branch.、core.
description 文件 仅供 GitWeb 等使用的一行描述
index 文件 暂存区;merge 冲突时出现 stage 2/3(第 06 篇
objects/ 目录 对象库根;info/packs 清单、commit-graph
objects/pack/ 目录 *.pack 数据 + *.idx 索引(第 07 篇
refs/heads/ 目录 分支 → commit SHA
refs/tags/ 目录 标签 → tag 或 commit SHA
refs/remotes/ 目录 远程跟踪分支,fetch 后更新
packed-refs 文件 git pack-refs 后合并的只读引用快照
logs/ 目录 reflog(第 05 篇
hooks/ 目录 客户端钩子脚本(sample 后缀为示例)
info/exclude 文件 仅本仓库生效的忽略规则,不提交
FETCH_HEAD 文件 最近一次 fetch 的引用与 SHA 列表
MERGE_HEAD 文件 合并/变基进行中的临时指针(第 13 篇
shallow 文件 浅克隆边界 commit 列表(第 14 篇
worktrees/ 目录 链接工作树元数据(第 15 篇

五、三棵树:工作区、索引、HEAD

Pro Git 用「三棵树」描述 Git 的数据分层(B 级辅助,机制与官方 layout 一致):

  1. 工作区(Working Tree):目录里直接看到的文件;不在 .git 内,但由 checkout 从对象库写出。
  2. 索引(Index / Staging Area).git/indexgit add 把路径→blob SHA 写入此处;git commit 用索引生成 tree 对象。
  3. HEAD 树HEAD 指向的 commit 所引用的 tree,代表已提交快照。

数据流:git add 把工作区内容哈希为 blob 并更新 index;git commit 把 index 固化成 tree + commit,并移动 refs/heads/* 与 reflog。git checkout 则按目标 commit 的 tree 重写 index 与工作区。

bare 仓库没有工作区和 index(没有检出树的操作),但 objectsrefsconfighooks 等布局与非 bare 相同,常用于服务端 git clone --bare


六、bare 与 non-bare 对照

本地 git clone --bare 得到的目录本身就是 .git 等价物(没有外层工作区):

HEAD
config
description
hooks/
info/
objects/
packed-refs
refs/

non-bare 克隆则在项目根下保留工作区,.git 为子目录。两者对象与引用语义一致,差别仅在于是否维护工作区与 index。


七、与小文件问题的联系

每个 blob、commit 最初常以松散文件形式落在 objects/xx/…,大量小提交会产生海量小文件,inode 与目录项压力与 存储工程:小文件问题 描述的现象同型。Git 用 git gc / repack 把松散对象打进 pack(第 10 篇)缓解这一问题——理解 .git 布局,也就理解为何「仓库越大越要 gc」。


八、本文边界


参考资料

系列索引Git 内部结构

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-07-05 · opensource

【Git 内部】Git 内部结构:对象库与磁盘文件格式

从 .git 目录布局、松散对象与 packfile 格式,到 refs、index、reflog、gc/fsck 与 fetch/push 落地——用官方 format 文档与本地实测 dump 系统讲清 Git 把版本历史存在哪、每个命令改写了哪些文件。全 16 篇。


By .