如果你是个程序员,你大概率每天都在用 Git——git add、git commit、git push,行云流水。但如果我问你:当你创建一个分支的时候,Git 到底干了什么? 大多数人会卡住。再问一个更扎心的:你每次 commit,Git 存的是"这次改了哪几行",还是"整个项目的完整样子"? 八成的人会答错。
CodeLucky 频道的这支视频《Git Internals: Demystifying Git Objects (Blobs, Trees, Commits, Tags)》做的就是这件事——把 Git 撕开给你看 .git 目录里到底装了什么。看完你会有一种"原来我用了这么多年 Git,从没真正认识它"的恍然大悟。它解决的核心问题不是"怎么用 Git",而是"Git 凭什么这么设计"——一旦你理解了那个底层模型,rebase 令人恐惧、merge 时而冲突、detached HEAD 莫名其妙,这些"玄学"全都会自洽地坍缩成一句话:Git 根本不是版本控制系统,它是一个被哈希串起来的内容寻址文件系统。 这篇文章我带你把这个模型彻底吃透。
| 项目 | 内容 |
|---|---|
| 视频标题 | Git Internals: Demystifying Git Objects (Blobs, Trees, Commits, Tags) |
| 频道 | CodeLucky(@thecodelucky) |
| 视频链接 | https://www.youtube.com/watch?v=1eHwkyOmb-4 |
| 核心主题 | Git 的四类对象(blob / tree / commit / tag)与内容寻址存储模型 |
| 内容定位 | Git 内部原理深度讲解,拆解 .git 目录与对象数据库 |
| 数据说明 | 因 IP 受限,时长与播放量未能精确抓取;属 Git 进阶长视频,干货密度高 |
要理解 Git 为什么长成这样,得先回到 2005 年。那年 Linux 内核社区和商用版本控制工具 BitKeeper 闹翻了——BitKeeper 撤销了对内核项目的免费授权。Linus Torvalds 被逼到墙角,干脆自己写一个。他只用了大约十天就搞出了 Git 的初版,并且放下一句名言:Git 是一个"stupid content tracker"(愚蠢的内容追踪器)。
注意这个词——不是"版本控制系统",是"内容追踪器"。这不是谦虚,是设计宣言。Linus 没有去复刻 CVS、SVN 那一套"以文件为基本单位、记录每次 diff"的思路,而是反过来问:最简单、最暴力、最快的方式是什么? 答案是——把所有内容(文件、目录、提交)统统塞进一个巨大的键值对数据库,键就是内容本身的哈希值。这就是所谓的 content-addressable storage(内容寻址存储)。
这个起点决定了 Git 之后的一切。你越早接受"Git 是个数据库,不是文件管理器",后面所有东西越好懂。
这是视频要纠正的头号常识错误。大多数程序员脑子里有个画面:Git 像个账本,每次 commit 记一笔"第 100 行从 A 改成了 B"。完全不是。
Git 每次 commit,存的是整个项目在那一刻的完整快照(snapshot)。一个 commit 对象里只有几样东西:一个指向根目录树(tree)的指针、若干个父 commit 的指针、作者信息、时间戳和提交信息。它通过那棵 tree,能一路找到当时每一个文件的完整内容。
你可能会问:那每次提交都存一整份,Linux 内核几百万行代码、上百万次提交,仓库岂不是要爆炸?这正是 Git 设计的精妙之处——它不重复存。因为内容是按哈希寻址的,只要某个文件的内容没变,它在对象库里就是同一个条目(同一个哈希),所有 commit 共享这一份。真正发生变化的是"目录结构怎么组织"这层(tree 对象)。再加上 Git 会把零散对象打包成 packfile,用 zlib 压缩、对相似文件做 delta 编码,最终体积才被压下来。但请注意:压缩和 delta 是存储层的优化手段,不是 Git 的逻辑模型。 在逻辑层面,每一次 commit 都是一份完整快照。记住这条,很多困惑会自动消失。
视频把 Git 的对象分成四类,我们一个一个看,最反直觉的是 blob。
注意 blob 和 tree 的分工:内容归 blob 管,名字归 tree 管。这意味着什么?意味着你把 readme.txt 重命名成 README.md,只要文件内容一字未改,底层的 blob 完全不变——它在对象库里还是同一个哈希。变的只是某个 tree 对象里那条记录的名字。Git 之所以能瞬间识别"这是改名而非删除+新增",根源就在这里:它比较的是内容的哈希,而不是文件名。
这还带来一个顺带的好处:自动去重。你仓库里有一百个完全相同的 node_modules 文件副本?只要内容相同,对象库里只有一份。这是任何"以文件名为身份"的系统都做不到的。
现在到了整支视频、也是整篇文章最核心的一句话:
Git 里没有"分支"这个实体。一个分支,就是一个文件,文件里写着一行——某个 commit 的哈希值。
它躺在 .git/refs/heads/ 目录下。比如你的 main 分支,本质上就是 .git/refs/heads/main 这个文件,内容大概长这样:
a1b2c3d4e5f6...(40 个十六进制字符)+ 一个换行
41 个字节,这就是一个分支的全部。创建一个分支(git branch feature)不复制任何代码、不复制任何历史,它只是新建了一个 41 字节的小文件,指向当前所在的 commit。所以 Git 建分支是 O(1) 操作,瞬间完成——这和 SVN 那种"建分支=复制整个目录树"的笨重做法形成天壤之别。Linus 当初要的就是这个:能让上千个开发者同时开上千条分支并行开发,而不被工具拖垮。
那 HEAD 又是什么?它是指针的指针——.git/HEAD 这个文件里写着 ref: refs/heads/main,意思是"我现在站在 main 这条分支上"。当你切分支,Git 干的事就是把 HEAD 改成指向另一个 ref 文件。一旦 HEAD 直接指向一个 commit 而不是分支名(比如你 git checkout 了一个具体的哈希),你就进入了传说中的 detached HEAD(游离头指针) 状态——你接下来做的提交不属于任何分支,像断了线的风筝。理解了"分支=指针文件",detached HEAD 再也不神秘。
Git 用的是 SHA-1,输出 40 位十六进制字符串(160 比特)。它的精髓在于:哈希值就是地址。你想读任何一个对象,告诉 Git 它的哈希就行(甚至可以只写前 7 位,Git 会自动补全),用 git cat-file -p <哈希> 就能看内容。
更关键的是——对象的哈希,是由它的内容算出来的。内容改一个字节,哈希就完全变样。这带来三个直接后果:
这套机制让 Git 既是存储系统,又是一台"完整性校验机"。
很多人怕 git rebase、git commit --amend,觉得它们在"篡改历史"。但理解了对象模型,你会发现 Git 根本无法改写历史——commit 对象是不可变的,造出来就那样了。
rebase 干的事,其实是:以新的基底为起点,重新计算出一连串全新的 commit 对象(新的哈希、新的父指针),然后把分支指针挪到这条新链上。原来的那些旧 commit 还躺在对象库里,只是没有任何分支指着它们了,变成了"不可达"的孤儿。
这恰恰是 rebase 的"危险"所在:它在你的认知里制造了"历史变了"的错觉,实际上旧东西没丢。你随时可以用 git reflog 翻出那些孤儿 commit 的哈希,把它们救回来。reflog 记录了 HEAD 的每一次移动——这是 Git 给你留的后悔药,默认保留 90 天(可达的)或 30 天(不可达的)。
所以记忆口诀是:Git 里没有删除,只有"没人指了"。 真正的物理清除要等 git gc(垃圾回收)把不可达对象打包删除,在那之前,你的"事故现场"基本都能抢救。
.git 目录 = 整个仓库:分布式的根基视频虽然没有大讲分布式,但这层含义值得点透:Git 的全部家当都在项目根目录下那个 .git 文件夹里——对象库、refs、HEAD、配置、reflog,一个不落。
这意味着什么?意味着 git clone 拉下来的,不是一个"工作副本",而是整个版本库的完整拷贝——包括所有历史、所有分支、所有 tag。每一台机器上的仓库都是平等的、完整的、可独立工作的。这就是 Git "fully distributed"(完全分布式)的字面含义,也是它和中心化 VCS 最根本的区别:服务器挂了?无所谓,随便一台开发机的 .git 就是完整备份。
而这个 .git 目录之所以能做到这一切,靠的还是前面那套对象模型——因为对象按哈希命名、内容自描述,任何两个仓库只要哈希对得上,就能无缝对账、互相交换对象,不需要一个"中心权威"来裁决谁对谁错。
最后补一个视频之外的延伸——这套漂亮的模型有一个历史包袱:SHA-1 已经不够安全了。
2017 年,Google 公布了 SHAttered 攻击,用实际可行的算力构造出了两个内容不同、但 SHA-1 哈希完全相同的 PDF 文件,证明 SHA-1 抵抗"碰撞攻击"的能力被击穿。虽然 Git 因为额外校验(检测对象类型和结构)让实际利用难度极高,但这仍是个隐患——毕竟 Git 把哈希当成了对象的"身份证",一旦两人能造出同一哈希的不同内容,整个信任模型就会动摇。
所以 Git 社区多年来一直在推进 从 SHA-1 迁移到 SHA-256 的工作(git 已经支持 SHA-256 仓库,过渡机制也在完善)。理解了"哈希即身份"这层,你就能明白这次迁移为什么如此重要、又如此艰难——它要换的是整个对象数据库的"地基",所有现存哈希都得重新计算。这是 Git 为自己的优雅设计付出的长期代价。
graph TD
A[Git 的本质:内容寻址文件系统<br/>stupid content tracker] --> B[对象数据库 .git/objects]
B --> B1[blob: 文件内容<br/>不存文件名]
B --> B2[tree: 目录结构<br/>存文件名+指向blob/子tree]
B --> B3[commit: 快照+元数据<br/>指向tree+父commit]
B --> B4[tag: 带附加信息的<br/>commit指针]
B3 -->|"哈希=SHA-1(内容)"| H{哈希即地址<br/>内容决定身份}
H --> P1[不可篡改]
H --> P2[Merkle树: 改一处涟漪传到根]
H --> P3[自动去重: 同内容同哈希]
B3 --> R[引用 refs/]
R --> R1[分支 refs/heads/x<br/>= 41字节指针文件]
R --> R2[HEAD = 指针的指针<br/>指向当前分支或commit]
R1 -.->|"rebase/amend<br/>造新commit,旧的变孤儿"| OLD[不可达对象<br/>reflog 可救回 90/30天]
B3 --> DAG[提交组成 DAG<br/>有向无环图]
style A fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style H fill:#fff3e0,stroke:#e65100,stroke-width:2px
style R1 fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
第一,停止把分支当"东西",把它当"便利贴"。 你脑子里"分支"的图像应该是:一张写着某个 commit 哈希的便利贴,贴在历史链条的某个节点上。git branch x = 写一张新便利贴;git checkout x = 把 HEAD 这张"当前在哪"的便利贴挪过去;git merge = 把两条链缝起来后,把便利贴挪到缝合点。一旦切换到这个心智模型,cherry-pick、reset --hard、rebase 这些曾经让你提心吊胆的命令,全都有了清晰的物理对应。
第二,用 git cat-file -p 当 X 光机。 这是视频最该让你记住的"作弊指令"。随手 git cat-file -p HEAD、git cat-file -p <某个tree哈希>、git cat-file -p <blob哈希>,你就能亲眼看到一个 commit 怎么指向 tree、tree 怎么列出文件名和 blob 哈希、blob 怎么吐出文件内容。花十分钟手动走一遍这条链,胜过看一百篇教程。配合 git ls-files --stage 看暂存区、git log --graph --oneline 看 DAG,Git 对你不再是黑盒。
第三,出事故先别慌,翻 reflog。 既然"Git 没有删除,只有没人指了",那么 git reset --hard 误删、rebase 搞砸、强推覆盖——只要还没被 git gc 物理清除,git reflog 里都留着 HEAD 移动的每一步脚印,找到出事前的哈希,git reset --hard <那个哈希> 就能时光倒流。把这条刻进肌肉记忆,你对 Git 的恐惧会少一大半。
第四,理解了"快照而非 diff",代码 review 和合并策略会更清醒。 为什么 merge 有时干净有时一团乱麻?因为 Git 比对的是两棵 tree 的结构差异,而不是逐行 diff。改动越是"动同一个文件的不同区域",三方合并越干净;越是"同一区域各自改",冲突越激烈。这不是 Git 笨,是快照模型比较两个完整状态的必然结果。
hash-object、update-index、write-tree 从零搭一个仓库,是理解对象模型最快的方式。Git 不是版本控制系统,它是一个把所有内容按哈希存进数据库、用指针冒充分支的"愚蠢的内容追踪器"——理解了这棵哈希树,你就再也不会怕 rebase。
本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。 完成摘要(What was done)
下一步计划(What's next)
ducksearch 批量预热候选视频清单,避免现场卡顿。web-access skill)补抓后回填背景卡片。注意事项
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
