首页 › programming › 没存一条差异:Git靠快照骗过所有人

没存一条差异:Git靠快照骗过所有人

导语:你和90%的开发者一样,对Git的理解是错的

如果你是一个写过代码的人,十有八九你的脑子里装着这样一个 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》六误区长文交叉印证。视频是切入点,深度补强来自这些一手资料。

核心内容拆解

要点一:你脑子里的那个 commit,是一张补丁——但它不是

这是整个误会的源头,也是视频最核心的一句话。绝大多数开发者下意识地把"提交"理解成"这次新增了哪些行、删除了哪些行"的一份小记录,像一块砖头叠在之前的砖头上。这种直觉来自几十年前 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 不重复存储,而是"指向"它们已有的版本。但模型是快照,不是差异。这是视频原话,也是一切的起点。

要点二:你看到的每一处 diff,都是临场算出来的(on the fly)

这是理解 Git 行为的关键推论。既然底层存的是快照,那 git diff、git show、git log -p 里那些花花绿绿的增删行是从哪来的?答案是:Git 在显示的瞬间,把两个快照拿来逐字节对比,临时算出来的。差异从来不是"被存储的东西",而是"被计算的结果"。

这条推论能解释一个让初学者困惑的现象:你为什么能对任意两个提交求 diff,哪怕它们隔着 500 次提交、八竿子打不着?因为 Git 不需要顺着一条补丁链一路翻回起点再相减——它只要把那两个时刻的完整快照拿来比一下就行了。O(1) 级别的两状态对比,而不是 O(n) 级别的链式回溯。这就是快照模型的性能红利。

要点三:三个对象撑起整个 Git:blob、tree、commit

要真正相信"Git 是快照机",得钻进它的内部结构。Git 本质上是一个内容寻址的对象数据库(content-addressed object store)——一个把"内容本身"经过哈希运算得到地址(key)的键值仓库。里面主要住着三类对象:

每个对象的"地址"就是它自身内容的 SHA-1(新版本支持 SHA-256)哈希值。内容相同 → 哈希相同 → 只存一份。这是 Git 省空间的真正诀窍,下面细说。

要点四:省空间不靠存差异,靠"内容寻址去重"

很多人会立刻反驳:一万个文件每次提交都存一份全量,那岂不是天文数字的磁盘占用?Git 怎么可能还这么小?

答案是内容寻址带来的天然去重。设想你有一万文件的仓库,改了 main.py 一个文件就提交:

所以这一次提交,Git 实际新增的实体只有:1 个新 blob + 几个新 tree + 1 个新 commit,而不是一万个文件。未变化的文件靠哈希共享同一个对象。再叠加 zlib 压缩,仓库体积就被压得很小。去重的钥匙是"内容即地址",不是"差异存储"——这是视频里没有展开、但理解了就豁然开朗的延伸点。

要点五:一个容易被忽视的反转——packfile 里其实藏着 delta 压缩

这里要补充一个连很多老手都说不清的细节,也是"差异"误解如此顽固的另一半原因。在逻辑模型上,Git 确实是快照。但在物理存储层,Git 会周期性地把零散对象打包进 packfile,而 packfile 内部确实使用了 delta 压缩——它会把某些对象编码成"相对于某个 base 对象的差异"来节省空间。

所以严格来说:Git 在对象数据库层面是快照,在打包压缩层面又用回了 delta。这正是误会顽固的根源——"存差异"并非空穴来风,只是差异出现在压缩优化层,而不是出现在你以为的逻辑模型层。你脑子里应该装的那个模型,仍然是"一串快照"。把它想反了,rebase、cherry-pick 这些操作就永远像玄学。

要点六:换了心智模型,rebase 和 cherry-pick 立刻不吓人了

这是视频和 devdigest 文章共同强调的"实战收益"。人们怕 git rebase 和 git cherry-pick,根子就在于把 commit 当成"可以搬来搬去的补丁块"。一旦你接受 commit = 一张完整快照,这些命令的逻辑就清晰了:

这里有一个漂亮的推论(我的延伸分析):为什么"同样的改动,commit hash 却不同"?因为 commit 对象里塞进了 parent 指针和 tree 指针,parent 一变,commit 的内容就变了,哈希自然就变了。哈希是整段关系的指纹,不是改动的指纹。理解这一点,你就再也不会问"我明明 cherry-pick 了,为什么算成新提交"。

要点七:分支只有 41 字节,HEAD 是个游标

继续顺着快照模型往下走,两个常被神化的概念会瞬间祛魅:

要点八:reflog——你以为删掉的 commit,其实还在

误解清单里最让人胆战心惊的一条是:"一次 git reset --hard、一次误删分支、一次搞砸的 rebase,我的代码就永远没了。"这也是为什么那么多人对 Git 战战兢兢、不敢碰历史。

真相是:几乎什么都还没真正丢掉。Git 维护着一份叫 reflog(引用日志) 的日记,记录 HEAD 的每一次移动——每一次 commit、checkout、reset、rebase 都会留下一条。即使某个 commit 已经没有任何分支引用它,它仍然作为对象躺在数据库里:可达的提交保留约 90 天,不可达的保留约 30 天,之后才被垃圾回收真正清除。

这恰恰是快照模型的安全性所在——删除分支只是摘掉了一个 41 字节的指针,快照本身原地不动,直到 GC 才清理。所以 git reflog + git reset --hard <那个旧 hash>,就能把"误删"的工作救回来。

要点九(延伸):Linus 当年为什么选了"反直觉"的快照方案

最后补一段视频未展开的设计哲学。Git 是 Linus Torvalds 在 2005 年为管理 Linux 内核源码而造的——内核有数万个文件、数千名贡献者、极其疯狂的速度和完整性要求。Linus 选择"快照 + 内容寻址"而非传统的"差异链",是刻意为之的权衡:

  1. 完整性(integrity):每个对象的地址就是它内容的哈希。篡改任何一处,哈希就变,引用链立刻断裂、立刻被发现。Git 天然防篡改,这在分布式协作里是命根子。
  2. 速度:checkout、diff、log 全是本地磁盘读取,不需要联网、不需要重建 delta 链。
  3. 分布式优先:每个 clone 都是完整仓库,断网也能工作,因为没有"中心服务器存着唯一真相"。

存储效率被 Linus 排在了后面,用"哈希去重 + packfile 压缩"补足。也就是说,Git 是用"看起来费空间"的快照,换来了完整性和速度,再回头用工程手段把空间省回来。这是一个值得反复品味的设计抉择。

知识结构图: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),快照模型就从抽象变成肌肉记忆。

延伸学习路径

  1. 《Pro Git》官方免费书 · "What is Git?" 与 "Git Internals" 两章 —— 快照模型与对象数据库的权威出处,可在线全文阅读,配合 git cat-file 边读边验。
  2. GitHub 官方博客《Commits are snapshots, not diffs》(Derrick Stolee, 2020) —— 把"为什么 rebase/cherry-pick 难懂"归结为心智模型错位,本文核心论点的一手来源。
  3. Julia Evans(jvns.ca)《Do we think of git commits as diffs, snapshots, or histories?》 —— 从"人到底怎么理解 commit"切入,配她标志性的手绘 zine,极其适合建立直觉。
  4. devdigest《Git Isn't About Diffs: Fix Your Mental Model in 6 Steps》(2026-06) —— 本文重点引用的六误区清单,覆盖 commit / rebase / pull / branch / HEAD / reflog,最贴近当下。
  5. 动手实验:建一个空仓库,连做两次只改一个文件的提交,用 git cat-file -p 和 git ls-tree 打印每次 commit 的 tree 与 blob 哈希——你会亲眼看到没变的文件共享同一个 blob 对象。

一句话总结

Git 从没存过一条差异——它存的是一串完整快照,靠内容寻址去重和 packfile 压缩把空间省回来;一旦你把"差异链"换成"快照流",rebase、cherry-pick、分支、HEAD、reflog 就全部不再神秘。

参考来源

  1. Git Stores Snapshots, Not File Differences(YouTube) — 本文分析的核心视频
  2. Commits are snapshots, not diffs — The GitHub Blog(Derrick Stolee, 2020) — 快照模型的官方权威论证
  3. Git Isn't About Diffs: Fix Your Mental Model in 6 Steps — Devdigest(2026-06-29) — 六大 Git 误区清单
  4. Do we think of git commits as diffs, snapshots, or histories? — Julia Evans / jvns.ca — 心智模型视角的技术拆解
  5. Pro Git Book · "What is Git?" 与 "Git Internals" — "series of snapshots of a miniature filesystem" 原话出处

本文由「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 等场景全覆盖。

51学通信知识星球海报-扫码加入