Git 的 .git/objects 里到底藏了什么?
大部分人对 Git 的理解停在
核心只有三种对象。blob 存文件内容,纯原文,连文件名都没有。tree 存目录结构,每条记录指向一个 blob 或子 tree,附带文件名和权限。commit 存快照元信息:一个 tree 指针、零或多个 parent 指针、作者信息和提交消息。所谓分支不过是
值得看的是压缩方式。Git 不存裸文本,而是用 zlib 的
再往深处走一层:packfile。当你有上千次提交时,松散对象泛滥,
下次用
#CS
大部分人对 Git 的理解停在
git add → git commit → git push 这个流水线上,但如果你 ls .git/objects/ 看一眼,会发现一堆两级目录——前两位是 SHA-1 的前缀,剩下的在子目录里。这些就是 Git 的全部家当,所谓 content-addressable storage 的实体。核心只有三种对象。blob 存文件内容,纯原文,连文件名都没有。tree 存目录结构,每条记录指向一个 blob 或子 tree,附带文件名和权限。commit 存快照元信息:一个 tree 指针、零或多个 parent 指针、作者信息和提交消息。所谓分支不过是
.git/refs/heads/main 里的一行四十字符,指向某个 commit 而已。值得看的是压缩方式。Git 不存裸文本,而是用 zlib 的
deflate 压缩。随便找个文件用 python -c "import zlib, sys; sys.stdout.buffer.write(zlib.decompress(open(sys.argv[1],'rb').read()))" .git/objects/xx/yyyy… 就能还原。你会看到开头是一行 header,格式是 类型 长度\0,比如 blob 158\0,然后是原始内容。这就是 Git 的寻址方式——对 header+content 算 SHA-1,前两位做目录名,后 38 位做文件名,内容 zlib 压缩后写入。碰撞概率在可预见的未来可以忽略,所以这个哈希就是对象的唯一身份证。再往深处走一层:packfile。当你有上千次提交时,松散对象泛滥,
git gc 会把它们打包成 .git/objects/pack/ 下的 .pack 和 .idx 文件。pack 内部对相似对象做 delta 压缩——只存与前一版本的差异,解压时链式还原。这也是为什么 git clone 大仓库比想象中快,网络传输的是 pack 格式而不是逐个对象。下次用
git cat-file -p 或 git ls-tree 查看对象时,想想这些命令背后不过是对一个 zlib 压缩文件的解压和解析。整个版本控制系统建立在三个朴素的数据结构和一个哈希函数之上,没有数据库,没有服务器——这个设计判断本身就是最大的工程智慧。#CS