这一篇在干嘛?
系列收尾篇。前面所有命令背后的机制到底是什么?这一篇掀开
.git目录看底牌:四种对象、引用的本质、refspec 与传输协议。懂了它,Git 的一切「魔法」都变成了「读文件、写文件」。对应《Pro Git》第 10 章。
底层命令与上层命令
平时用的 git add、git commit 是上层命令(porcelain)——人类友好的封装。它们背后是一批底层命令(plumbing),直接操作仓库数据库:
| 上层(人用) | 底层(机器用) |
|---|---|
git add | git hash-object / git update-index |
git commit | git write-tree / git commit-tree |
git branch | git update-ref |
学底层不是为了日常用它,而是为了理解数据模型——之后任何 Git 行为你都能推演出「它动了哪些对象」。
四种对象:Git 的文件系统
在任意仓库试一下(完整复现一本书的经典实验):
$ echo "hello world" | git hash-object --stdin
3b18e512dba79e4c8300dd08aeb37f8e728b8dad这就是 SHA-1 哈希:内容(“hello world”)+ 类型头的哈希值。Git 中一切皆对象,四种:
- blob:文件内容的快照(不含文件名!文件名存在 tree 里)。
- tree:目录快照——一堆条目,每个条目是「文件名 → blob/子 tree 的指针 + 权限」。
- commit:一次提交——指向一个根 tree、指向父提交、作者、信息。
- annotated tag:附注标签对象。
commit a1b2 ───▶ tree (根目录) ───▶ blob "README 的内容"
│ │
▼ ├──▶ blob "main.c 的内容"
父提交 commit 9f8e └──▶ tree (src/) ──▶ blob "util.c 的内容"关键推论:分支里的历史就是 commit 对象的链表,每个 commit 指向一棵完整快照树。相同的文件内容只存一份 blob(哈希相同即内容相同)——这就是「没变的文件不重复存储」的实现。
引用:分支原来就是个文本文件
$ cat .git/refs/heads/main
a1b2c3d4e5f6... # 就是一行 40 位哈希!
$ cat .git/HEAD
ref: refs/heads/main # HEAD 指向分支,也是文本- 分支 = refs/heads/ 下的一个文本文件,内容是它所指提交的哈希。
git branch testing= 写一个 41 字节文件——「瞬间建分支」的底层真相。- 标签在
refs/tags/;远程跟踪分支在refs/remotes/。
git cat-file -p <hash> 可以打印任意对象内容,自己顺着 commit → tree → blob 一路点下去,整个仓库对你透明。
refspec:fetch/push 的映射语法
git fetch origin +refs/heads/*:refs/remotes/origin/* 这类串叫 refspec:<来源>:<目的地>,* 通配。含义是「把远程的 refs/heads/* 拷到我本地 refs/remotes/origin/*」。日常你不用写它——git remote add 已经帮你生成好了——但看懂它能解释很多 push/fetch 的行为。
传输协议速览
fetch/push 时客户端与服务端的对话只有两种形式:
- 哑协议(dumb HTTP):直接按普通 HTTP 下载
.git下的静态文件,效率低,已式微。 - 智能协议(SSH/HTTP/Git):两端先交换各自有什么对象(want/have 列表),只传缺的那部分增量——这是 Git 网络操作快的原因。
包文件(packfile)与维护
blob 逐个 loose 存放会浪费空间(一万次小改动 = 一万个文件)。Git 会定期把松散对象打包成 packfile,用 delta 差异压缩(只存「相对于相似对象的差异」)。git gc 手动触发整理:
git count-objects -v # 查看松散对象/包文件统计
git gc # 打包、压缩、清理数据恢复再强调一次:git reflog + git fsck --lost-found,只要对象进了数据库,几乎都能捞回来。
小结
- 底层命令揭示数据模型;上层命令是友好封装。
- 四种对象:blob(内容)、tree(目录)、commit(快照+父指针)、tag。
- 分支/标签/HEAD 全是文本文件里的哈希——「指针」的物理形态。
- refspec 是
来源:目的地的引用映射语法。 - 智能协议只传增量;gc 打包 delta 压缩;reflog 是最后的后悔药。
学完你会得到:
- 「一切皆对象、一切皆文件」的 Git 世界观
- 亲手在 .git 里读懂一次提交的完整链路
- 系列全部内容串成一张网:对象 → 引用 → 分支 → 协作
自测
自测
- blob 对象里存了文件名吗?文件名存在哪里?
- 为什么「内容相同的文件只存一份」?靠什么机制实现?
- 分支在磁盘上的物理形态是什么?这解释了哪个前面讲过的特性?
git commit在对象层面创建了什么?它最少指向哪几样东西?- 智能协议为什么比哑协议快?
系列完结
十一篇走完,你已经从「复制粘贴改名」进化到理解 Git 的数据模型。接下来最好的学习方式是:给自己建一个真实项目仓库,把这里面的命令全部亲手敲一遍。原始书-sync:完整版请读 Pro Git 中文版。