这一篇在干嘛?

系列收尾篇。前面所有命令背后的机制到底是什么?这一篇掀开 .git 目录看底牌:四种对象、引用的本质、refspec 与传输协议。懂了它,Git 的一切「魔法」都变成了「读文件、写文件」。对应《Pro Git》第 10 章。

底层命令与上层命令

平时用的 git addgit commit上层命令(porcelain)——人类友好的封装。它们背后是一批底层命令(plumbing),直接操作仓库数据库:

上层(人用)底层(机器用)
git addgit hash-object / git update-index
git commitgit write-tree / git commit-tree
git branchgit 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 里读懂一次提交的完整链路
  • 系列全部内容串成一张网:对象 → 引用 → 分支 → 协作

自测

系列完结

十一篇走完,你已经从「复制粘贴改名」进化到理解 Git 的数据模型。接下来最好的学习方式是:给自己建一个真实项目仓库,把这里面的命令全部亲手敲一遍。原始书-sync:完整版请读 Pro Git 中文版