git clone
完成后,项目根目录除了源码,还多了一个
.git。很多人把它当成黑盒:知道「历史在里面」,但说不清
objects 和 refs
各管什么、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.txt 并
git add、git commit
后,.git 新增关键路径:
| 路径 | 职责 |
|---|---|
.git/objects/ |
对象库:松散对象 xx/yyyy… 与后来的
pack/ |
.git/refs/heads/master |
分支 master 指向的 commit SHA |
.git/logs/HEAD、logs/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
篇。
四、主要路径说明
依据 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 一致):
- 工作区(Working
Tree):目录里直接看到的文件;不在
.git内,但由 checkout 从对象库写出。 - 索引(Index / Staging
Area):
.git/index,git add把路径→blob SHA 写入此处;git commit用索引生成 tree 对象。 - HEAD 树:
HEAD指向的 commit 所引用的 tree,代表已提交快照。
数据流:git add 把工作区内容哈希为 blob
并更新 index;git commit 把 index 固化成 tree +
commit,并移动 refs/heads/* 与
reflog。git checkout 则按目标 commit 的 tree
重写 index 与工作区。
bare 仓库没有工作区和 index(没有检出树的操作),但
objects、refs、config、hooks
等布局与非 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」。
八、本文边界
- 不展开对象 zlib 格式与 pack 字节布局(见第 02、07 篇)。
- 不列举全部钩子语义与
config每个键。 - 命令教程(merge/rebase 用法)见 Pro Git;本系列只关心它们留下的文件。
参考资料
- Git documentation, gitrepository-layout
- Chacon & Straub, Pro Git, 2nd ed., ch.10 Git Internals(B 级)
- 实验环境:
git version 2.54.0,WSL2 Linux
系列索引:Git 内部结构
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Git 内部】Git 内部结构:对象库与磁盘文件格式
从 .git 目录布局、松散对象与 packfile 格式,到 refs、index、reflog、gc/fsck 与 fetch/push 落地——用官方 format 文档与本地实测 dump 系统讲清 Git 把版本历史存在哪、每个命令改写了哪些文件。全 16 篇。
【Git 内部】refs、HEAD 与 packed-refs
分支和标签在磁盘上只是一行 SHA?refs/heads、符号引用 HEAD、packed-refs 文件头与 peeled tag 的格式与生成时机。
【Git 内部】index 暂存区:dircache v2 与扩展节
git add 改的是 .git/index。dircache 魔数、条目 stat/SHA/stage、TREE/REUC 扩展节,对照 xxd 与 git ls-files -s。
【Git 内部】松散对象:zlib 载荷与 SHA-1 路径
blob/tree/commit 在磁盘上如何编码?对象头 type size、zlib 压缩、objects/ab/cdef 路径命名,对照 git hash-object 与手工 SHA-1 验证。