如果你是一个写过代码的人,十有八九你的脑子里装着这样一个 Git 的画面:每次 git commit,Git 就把"这次改了哪几行"记成一个小补丁(patch),像一摞便利贴一样叠在上一张上面。需要看历史?把这一摞补丁从头翻到尾,一点点还原。需要看差异?翻出对应的两张补丁对比。听起来天衣无缝,对吧?
但这是错的,而且错得相当彻底。
Git 根本不存储差异。每一次 commit,Git 都在硬盘上存下整个项目的完整快照(snapshot)——你仓库里的一万个文件,哪怕你只改了其中一个标点符号,Git 也会把这"一万个文件此刻的状态"整个拍一张照存下来。你在 git diff、git show、git log -p 里看到的那些 + 和 -,全是临时算出来的,从来没有任何一个差异被写进过 Git 的数据库。
这个视频(以及 GitHub 官方博客、Pro Git 一书、jvns.ca 等一众权威来源)要纠正的就是这个普遍到几乎成为"行业共识"的误解。它不是吹毛求疵的概念洁癖——一旦你把心智模型从"差异链"换成"快照流",Git 里那一堆让你头大的命令:rebase 为什么会"改写历史"、cherry-pick 为什么会产生新的 commit hash、分支为什么"几乎不要钱"、git reset 删掉的东西为什么还能救回来——全部会瞬间变得顺理成章。这篇文章带你把这件事彻底想通。
| 项目 | 内容 |
|---|---|
| 视频标题 | Git Stores Snapshots, Not File Differences |
| 视频链接 | youtube.com/watch?v=_pjnLSUFEqQ |
| 频道定位 | 版本控制 / 开发者工具科普类 |
| 时长 | 约15–20分钟(概念系统讲解) |
| 受众 | 用了多年 Git 但仍被 rebase/merge 折腾的开发者 |
| 核心主题 | 拆穿"Git 存差异"的集体误解,建立"快照 + 内容寻址"的心智模型 |
| 一句话概括 | 你以为 commit 是补丁,其实它是整盘快照 |
说明:本文的核心论点同时由 GitHub 官方博客《Commits are snapshots, not diffs》(Derrick Stolee, 2020)、Pro Git 官方手册、Julia Evans(jvns.ca)的技术拆解,以及 2026 年 6 月新发表的《Git Isn't About Diffs》六误区长文交叉印证。视频是切入点,深度补强来自这些一手资料。
这是整个误会的源头,也是视频最核心的一句话。绝大多数开发者下意识地把"提交"理解成"这次新增了哪些行、删除了哪些行"的一份小记录,像一块砖头叠在之前的砖头上。这种直觉来自几十年前 CVS、SVN、RCS 那一代版本控制系统——它们确实是这么干的:把每个文件存成一条反向增量链(reverse delta),最新版本是完整的,旧版本靠"减去差异"一点点倒推回去。
Git 反着来。Pro Git 手册里有一句被反复引用的原话(视频和所有权威文章都引它):
"Git thinks of its data more like a series of snapshots of a miniature filesystem."(Git 把自己的数据看作一个微型文件系统的一系列快照。)
也就是说,每一次 git commit,Git 都会对"此刻整个项目长什么样"拍一张全息照片存下来。你改了一个文件,Git 就为这个文件存一个新的版本对象;你没碰的那 9999 个文件,Git 不重复存储,而是"指向"它们已有的版本。但模型是快照,不是差异。这是视频原话,也是一切的起点。
这是理解 Git 行为的关键推论。既然底层存的是快照,那 git diff、git show、git log -p 里那些花花绿绿的增删行是从哪来的?答案是:Git 在显示的瞬间,把两个快照拿来逐字节对比,临时算出来的。差异从来不是"被存储的东西",而是"被计算的结果"。
这条推论能解释一个让初学者困惑的现象:你为什么能对任意两个提交求 diff,哪怕它们隔着 500 次提交、八竿子打不着?因为 Git 不需要顺着一条补丁链一路翻回起点再相减——它只要把那两个时刻的完整快照拿来比一下就行了。O(1) 级别的两状态对比,而不是 O(n) 级别的链式回溯。这就是快照模型的性能红利。
要真正相信"Git 是快照机",得钻进它的内部结构。Git 本质上是一个内容寻址的对象数据库(content-addressed object store)——一个把"内容本身"经过哈希运算得到地址(key)的键值仓库。里面主要住着三类对象:
每个对象的"地址"就是它自身内容的 SHA-1(新版本支持 SHA-256)哈希值。内容相同 → 哈希相同 → 只存一份。这是 Git 省空间的真正诀窍,下面细说。
很多人会立刻反驳:一万个文件每次提交都存一份全量,那岂不是天文数字的磁盘占用?Git 怎么可能还这么小?
答案是内容寻址带来的天然去重。设想你有一万文件的仓库,改了 main.py 一个文件就提交:
main.py 内容变了 → 它算出一个新的 blob,存一份。所以这一次提交,Git 实际新增的实体只有:1 个新 blob + 几个新 tree + 1 个新 commit,而不是一万个文件。未变化的文件靠哈希共享同一个对象。再叠加 zlib 压缩,仓库体积就被压得很小。去重的钥匙是"内容即地址",不是"差异存储"——这是视频里没有展开、但理解了就豁然开朗的延伸点。
这里要补充一个连很多老手都说不清的细节,也是"差异"误解如此顽固的另一半原因。在逻辑模型上,Git 确实是快照。但在物理存储层,Git 会周期性地把零散对象打包进 packfile,而 packfile 内部确实使用了 delta 压缩——它会把某些对象编码成"相对于某个 base 对象的差异"来节省空间。
所以严格来说:Git 在对象数据库层面是快照,在打包压缩层面又用回了 delta。这正是误会顽固的根源——"存差异"并非空穴来风,只是差异出现在压缩优化层,而不是出现在你以为的逻辑模型层。你脑子里应该装的那个模型,仍然是"一串快照"。把它想反了,rebase、cherry-pick 这些操作就永远像玄学。
这是视频和 devdigest 文章共同强调的"实战收益"。人们怕 git rebase 和 git cherry-pick,根子就在于把 commit 当成"可以搬来搬去的补丁块"。一旦你接受 commit = 一张完整快照,这些命令的逻辑就清晰了:
cherry-pick:Git 先当场算出目标 commit 隐含的差异(拿它和它自己的 parent 比一下),把这个差异应用到你的当前状态上,然后记录一张全新的快照,于是诞生了一个全新的 commit——这就是为什么 cherry-pick 出来的提交 hash 和原来不一样。rebase:把你的若干 commit 逐个"重放"到新基点之上——每次都是"算差异 → 应用 → 记新快照 → 得到新 hash"。所谓"改写历史",改的其实是这一串新快照。这里有一个漂亮的推论(我的延伸分析):为什么"同样的改动,commit hash 却不同"?因为 commit 对象里塞进了 parent 指针和 tree 指针,parent 一变,commit 的内容就变了,哈希自然就变了。哈希是整段关系的指纹,不是改动的指纹。理解这一点,你就再也不会问"我明明 cherry-pick 了,为什么算成新提交"。
继续顺着快照模型往下走,两个常被神化的概念会瞬间祛魅:
HEAD~1 是"从我现在的地方往回退一格",git reset --hard HEAD~3 是"把当前分支指针往后挪三格"。"detached HEAD(游离头指针)"也不再是吓人的报错——它只是说 HEAD 直接指到了某个 commit 上、没挂在任何分支名下而已。误解清单里最让人胆战心惊的一条是:"一次 git reset --hard、一次误删分支、一次搞砸的 rebase,我的代码就永远没了。"这也是为什么那么多人对 Git 战战兢兢、不敢碰历史。
真相是:几乎什么都还没真正丢掉。Git 维护着一份叫 reflog(引用日志) 的日记,记录 HEAD 的每一次移动——每一次 commit、checkout、reset、rebase 都会留下一条。即使某个 commit 已经没有任何分支引用它,它仍然作为对象躺在数据库里:可达的提交保留约 90 天,不可达的保留约 30 天,之后才被垃圾回收真正清除。
这恰恰是快照模型的安全性所在——删除分支只是摘掉了一个 41 字节的指针,快照本身原地不动,直到 GC 才清理。所以 git reflog + git reset --hard <那个旧 hash>,就能把"误删"的工作救回来。
最后补一段视频未展开的设计哲学。Git 是 Linus Torvalds 在 2005 年为管理 Linux 内核源码而造的——内核有数万个文件、数千名贡献者、极其疯狂的速度和完整性要求。Linus 选择"快照 + 内容寻址"而非传统的"差异链",是刻意为之的权衡:
存储效率被 Linus 排在了后面,用"哈希去重 + packfile 压缩"补足。也就是说,Git 是用"看起来费空间"的快照,换来了完整性和速度,再回头用工程手段把空间省回来。这是一个值得反复品味的设计抉择。
graph TD
subgraph 提交层 commit
C1["commit 7b2e…<br/>第一次提交"]
C2["commit a3f9…<br/>第二次提交"]
C2 -->|parent| C1
end
subgraph 快照层 tree
T1["tree 9d4a<br/>项目快照①"]
T2["tree f1c0<br/>项目快照②"]
C1 -->|指向顶层tree| T1
C2 -->|指向顶层tree| T2
end
subgraph 数据层 blob 内容寻址
BO["blob r5t7<br/>main.py 旧版本"]
BN["blob x1z2<br/>main.py 新版本"]
BU["blob p9q8<br/>utils.py"]
BR["blob m3n6<br/>README.md"]
end
T1 --> BO
T1 --> BU
T1 --> BR
T2 --> BN
T2 --> BU
T2 --> BR
BU -.->|"内容没变→同一对象<br/>(哈希去重)"| BU
BR -.->|"内容没变→同一对象<br/>(哈希去重)"| BR
style BU fill:#cfe8d6,stroke:#2e7d32
style BR fill:#cfe8d6,stroke:#2e7d32
style BN fill:#fde2c8,stroke:#e65100
看图说话:两次提交之间,utils.py 和 README.md 没有改动,所以 tree① 和 tree② 指向同一个 blob(绿色,去重);只有改了的 main.py 生成了一个新 blob(橙色)。没有一处存的是"差异",存的是"指针 + 快照"。 你在 git log -p 看到的差异,是 Git 临场把两个快照比出来的。
第一,敢于频繁开分支、频繁提交。 既然分支只是 41 字节、commit 只是记快照,那"开分支很贵""提交太多很乱"的旧恐惧就该丢掉。正确习惯是反过来的:每个小想法一个分支,每写完一小段就 commit。错了大不了删指针,快照还在 reflog 里候着。
第二,不再害怕 rebase。 记住 rebase 唯一的真正风险:重写那些别人已经拉取(pull)过的共享历史,会逼别人做痛苦的对账。你自己机器上没推送过的本地分支,rebase、squash、改顺序、改信息随便玩——你改的是只存在于你本机的历史快照,没人受影响,搞砸了还有 reflog 兜底。"别 rebase 共享分支"是好建议,"别 rebase"是坏建议,后者只是让你白怕很多年。
第三,把 git pull 拆成两步。 git pull 是"穿着一件外套的两条命令"——git fetch + git merge(或 rebase)。那步隐藏的 merge 才是惊喜来源:可能造出你没要的 merge commit,或者撞上未提交改动卡在半路。改成先 git fetch(只下载、绝不碰你的工作区,永远安全)→ 看一眼进来了什么(git log HEAD..@{u})→ 再主动决定 merge 还是 rebase。把决定权从命令的"默认行为"手里拿回到你自己手里。
第四,调试 Git 问题时,永远可以打开 .git 目录用 plumbing 命令验证。 git cat-file -p <hash> 能打印任意对象的原始内容,git ls-tree <tree> 能列出某个快照的文件清单。一旦你亲手"看见"一个 commit 长什么样(指向 tree + parent + message),快照模型就从抽象变成肌肉记忆。
git cat-file 边读边验。git cat-file -p 和 git ls-tree 打印每次 commit 的 tree 与 blob 哈希——你会亲眼看到没变的文件共享同一个 blob 对象。Git 从没存过一条差异——它存的是一串完整快照,靠内容寻址去重和 packfile 压缩把空间省回来;一旦你把"差异链"换成"快照流",rebase、cherry-pick、分支、HEAD、reflog 就全部不再神秘。
本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。
[!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 等场景全覆盖。
