首页 › programming › Git不存差异存快照:分支因此近乎免费

Git不存差异存快照:分支因此近乎免费

导语:你用错了十年,是因为从第一天起就理解反了

如果你是一个程序员,你每天大概要敲几十次 git commit、git push、git pull。但要是有人突然问你:"Git 到底把你的代码以什么形式存进去了?"——大部分人会脱口而出:"当然是存差异(diff)啊,每次提交只记录改了哪几行。"

这个直觉,是错的。 而且 GitHub 自己的工程师 Derrick Stolee 都承认,正是这个错误直觉,是"Git 让人困惑的根因"。

LearnThatStack 频道那部播放量破百万的《Git Will Finally Make Sense After This》,干的事情就是一句话捅破这层窗户纸:Git 不存改动,它存整份快照;它甚至不是传统意义上的"版本控制工具",而是一个披着 VCS 外衣的内容寻址键值存储(content-addressable key-value store)。一旦你接受这个事实模型,那些让你头疼多年的 rebase、cherry-pick、reset --hard、detached HEAD,会像被打通任督二脉一样,瞬间全部说通。

这篇文章要做的不只是转述这个视频,而是把"快照模型"这条暗线一路追到底——从 SHA 哈希、默克尔有向无环图,到分支为什么只是一个 41 字节的文件,再到 packfile 里那个"概念是快照、物理却是增量"的精妙妥协。读完它,Git 在你眼里将不再是一堆需要死记的命令,而是一台设计极其优雅的机器。

视频背景卡片

项目 内容
视频标题 Git Will Finally Make Sense After This
频道 LearnThatStack
时长 约 13 分钟
播放量 100 万+(1M views)
发布时间 2025 年 12 月
频道订阅 10.7 万(107K subscribers)
核心主题 Git 真实的数据模型:commit、branch、reset、rebase 背后的快照与指针

核心内容拆解

一、最大的误解:Git 不是"存差异"的

我们先把这个误解钉死。在 Git 之前,老一代版本控制系统——CVS、SVN,更早的 RCS——都是"增量存储"思维:第一次存完整文件,之后每次提交只记录"相对上一版的差异",像一条不断累加的补丁链。这种思维非常符合直觉,于是几乎所有 Git 新手都默认 Git 也是这样工作的。

但 GitHub 官方博客那篇被引用无数次的《Commits are snapshots, not diffs》里,维护者 Derrick Stolee 写得非常直接:用户在 cherry-pick、rebase 这些"重写历史"命令上反复翻车,根因就是把 commit 当成了"可以搬来搬去的差异块"。

真相是:每一次 commit,Git 都对整个项目当时的状态拍一张"全家福"——一个完整快照(snapshot)。 你看到的 git diff 那些花花绿绿的增删行,根本不是被存下来的东西,而是 Git 在你需要的那一刻,把两个快照拿来现场比对、临时算出来的。差异是派生视图(derived view),不是存储内容。这个区分,是理解 Git 的第一块基石。

二、Git 的真身:一个内容寻址的键值存储

Git 官方文档《Pro Git》里有一句被低估的话:"Git 本质上是一个内容寻址文件系统(content-addressable filesystem),上面套了一个 VCS 的用户界面。"

什么叫"内容寻址"?普通数据库是你给它一个"地址"(行号、文件路径、键名),它去那个位置取数据。而内容寻址反过来:你把数据本身(加上一个类型头)做一次哈希,用算出来的哈希值当键,数据本身当值存起来。 同样的内容,喂给同样的哈希函数,永远得到同样的键——这是确定性的、可复现的。

Git 用的是 SHA-1(目前正在向更安全的 SHA-256 迁移)。这意味着三件极其重要的事情自动成立:

  1. 去重是免费的:两个文件内容完全相同,哈希就相同,Git 只存一份。你复制 100 份一模一样的文件进仓库,.git 体积几乎不变。
  2. 完整性是密码学保证的:篡改任何一个字节,哈希就变,整条引用链就断。你不可能"偷偷改了历史"而不被发现。
  3. 寻址不靠位置靠身份:一个对象的名字(哈希)就是它的"指纹",谁能算出同样的指纹,谁就拿到了同样的东西——这正是 Git 能彻底去中心化的根基。

顺带说一句,这套"把内容哈希当指针"的思想,后来被区块链原封不动抄了过去。比特币用默克尔根做交易校验,而 Linus 在 2005 年就用它造了 Git——比中本聪早了整整四年。所以别觉得 Git 老土,它的内核其实是密码学里最时髦的招。

三、四种对象:blob、tree、commit、tag

Git 的世界里只有四种"对象",全部塞在那个内容寻址存储里。理解了它们,你就理解了 Git 的全部:

这里面藏着一个特别精妙的解耦:文件名和文件内容被彻底分开了。文件名住在 tree 里,内容住在 blob 里。于是当你 git mv 给一个文件改名时,那个 blob 的哈希纹丝不动——变动的只是 tree 里那一条映射记录。Git 因此能在 git log --follow 里追踪被改名的文件:内容指纹没变,它就知道"这其实是同一个文件"。

四、默克尔有向无环图:改一个字,哈希全链雪崩

把上面的对象拼起来,你会得到一棵树套树的结构:一个 commit 指向一个根 tree,根 tree 指向若干子 tree 和 blob,每个子 tree 又指向更深一层的子 tree 和 blob……每个父节点都把子节点的哈希"包"进自己里再算哈希。这就是默克尔树(Merkle tree),而 commit 之间通过 parent 指针连成的,是一条有向无环图(DAG)。

这套结构的直接后果叫"哈希雪崩":你只改了某个文件里的一个字符,那个 blob 的哈希就变了;它的哈希一变,包含它的 tree 哈希就变了;tree 一变,上一层 tree 也变……一路传导到根 tree,最后这个 commit 的哈希也变了。 任何一个下游节点动了,所有上游节点的哈希都必须重新计算。

这不是缺陷,这是特性。它让 Git 历史天然防篡改:你没法在不破坏整条哈希链的前提下悄悄改动任何一个历史提交。整个 Git 的完整性校验(git fsck)就是建立在这个机制上——扫一遍对象图,重算哈希,对不上就是坏了。

五、分支只是一个 41 字节的文件指针

这大概是 Git 里最反直觉、也最解放思维的一个事实。在很多人的想象里,"分支"是一个容器,里面装着属于这个分支的一堆 commit,创建分支是一项"大工程"。

真相是:一个分支(Git 叫 ref)只是 .git/refs/heads/ 目录下的一个小文件,里面写着它当前所指的那个 commit 的 40 位 SHA-1(加一个换行符,正好 41 字节)。 创建一个分支,本质上就是往磁盘上写 41 个字节。这就是为什么 Git 的分支创建是 O(1) 的、几乎零成本——你大可以开几百个分支随便玩,而 SVN 时代分支昂贵得没人敢多用。

而 HEAD 呢?它本身又只是一个指针,指向"你当前在哪个分支"。所以真实的指针链是这样的:HEAD → 当前分支(refs/heads/main)→ 某个 commit → 根 tree → …… → blobs。一整条"当前状态"的引用,是从几个轻量指针一路牵出来的。

一旦你看清"commit 是不可变的快照节点,分支和 HEAD 只是几根会动的指针",所有 Git 操作的本质就变成了同一道题:移动指针,或生成新快照。

六、为什么 rebase、reset 这么绕?因为它们在"重算差异"

既然 commit 是快照不是差异,那 git diff、git show 里的差异从哪来?答案是——Git 每次都拿两个 commit 的根 tree 做现场比对,临时算出来。它从不存储"提交之间的差异"。

这个事实,恰好解释了几个最让人抓狂的命令为什么是那个样子:

七、概念是快照,物理是增量:packfile 的精妙妥协

如果你够敏锐,此刻应该有个疑问:每次 commit 都存一份完整快照?那一个几 GB 的大仓库,提交几万次,.git 岂不是要爆炸?

并不会——而这就是 Git 设计里最容易被误读的一层妥协。

答案是 packfile。Git 平时(松散对象,loose objects)确实每个对象单独存。但当你执行 git gc、git repack,或者在 push/pull 传输时,Git 会把大量对象"打包"。打包时,它确实会做增量压缩(delta compression):把某些对象表示成"相对某个基础对象的差异",从而大幅省空间。

这里有两点必须分清,否则你就会在"快照派"和"差异派"的争论里迷失:

  1. 概念模型永远是快照。 你的大脑、你的操作直觉,都应该基于"每次提交是一张完整快照"。这是 Git 暴露给你思考的真相。
  2. 物理存储格式可以是增量。 packfile 里用 delta 编码,纯粹是底层为了省磁盘、省带宽的透明优化,对你不可见,也不该影响你的心智模型。

更聪明的是,Git 会根据访问模式选择增量的方向:通常把较新、较常访问的版本存成完整对象,把旧的、不常碰的版本存成"相对新版本的逆向 delta"。这样最近的历史(你最常查的)解压快,代价摊到冷数据上。这就是为什么 git gc 之后仓库往往小一大圈,却又几乎不拖慢日常操作。

一句话收尾这层:Git 在语义层是"快照主义者",在物理层是"实用主义的增量压缩者"。两层各司其职,别把它们混为一谈。

八、为什么 Linus 要这样设计:速度、完整性、去中心化的三合一

回到 2005 年。Linux 内核团队原本用一款叫 BitKeeper 的商业版本控制工具,免费许可被收回后,Linus 一怒之下,花了大约十天,自己写了 Git。他要同时满足三个在当时看似矛盾的目标:

  1. 极快的分支与合并——内核每天有海量的合并操作;
  2. 数据完整性——绝不能出现"悄无声息被篡改的代码";
  3. 彻底去中心化——每一个克隆都是完整备份,断网也能干活。

而"内容寻址 + 快照 + 指针"这套设计,竟然一次性把三者全解决了:内容寻址给出密码学完整性;快照让分支只是一根指针(O(1) 创建);对象自包含让任何克隆都能从 .git 这个自洽数据库里重建一切。这是少有的"第一性原理设计"案例——Linus 没有去改良现有的 VCS,而是从"如何用哈希管理有版本的内容"这个最底层问题重新出发。

理解了这个动机,你也就理解了 .git 目录的本质:它不是一个配置文件夹,而是一个完整的、自包含的、密码学保护的数据库。 删掉远端,你的本地克隆依然握有全部历史——这就是去中心化的底气。

知识结构图

graph TD
    HEAD["HEAD<br/>(指针: 当前在哪个分支)"] --> BR["refs/heads/main<br/>(41字节文件: 指向某commit)"]
    BR --> C2["commit B<br/>SHA: c9d0…<br/>(快照 + 元数据)"]
    C2 -.->|"parent"| C1["commit A<br/>SHA: a1b2…"]
    C2 --> RT2["根 tree B<br/>(目录映射表)"]
    C1 --> RT1["根 tree A"]

    RT2 --> TS["tree: src/<br/>哈希未变 → 复用"]
    RT2 --> B3["blob: README v2<br/>(新内容→新哈希)"]
    RT2 --> BL["blob: logo.png<br/>(未变→复用)"]

    RT1 --> TS
    RT1 --> B2["blob: README v1"]
    RT1 --> BL

    TS --> MAIN["blob: main.py"]

    style C2 fill:#b3e5fc
    style B3 fill:#fff3e0
    style HEAD fill:#c8e6c9
    style BR fill:#c8e6c9

读图要点:实线箭头是 commit/tree 的"内容指针"(哈希引用);虚线是 commit 之间的 parent 链(构成 DAG)。绿色的是会动的轻量指针(HEAD、分支),蓝色 commit 是不可变快照,橙色 blob 是这次提交里唯一真正变化的对象。注意 tree: src/、blob: logo.png 被 commit A 和 B 共享——这就是内容寻址带来的免费去重:没变的东西,哈希相同,只存一份。

实战启示:这套模型能帮你做什么

第一,别再害怕分支。 既然分支只是 41 字节的指针,那就尽情开。功能分支、实验分支、临时分支,开完删掉,对仓库几乎零负担。Git 工作流(Git Flow、Trunk-based、GitHub Flow)的差异,本质上就是"什么时候、往哪根指针上挪 commit"的策略差异,底层完全一样。

第二,.git 目录就是你的保险箱。 把一个项目的整个 .git 拷贝到 U 盘里,你就拥有了它的全部历史、全部分支、全部标签——一个自包含的完整备份。这比任何"备份方案"都更彻底,因为它是密码学可验证的。养成定期对重要仓库的 .git 做 tar 打包冷备的习惯。

第三,用对象思维 debug,而不是背命令。 下次遇到 detached HEAD,别慌——它只是说 HEAD 此刻直接指着一个 commit 而不是某个分支名;想保留当前状态,就创建一根新指针指过去(git branch <name> / git switch -c)即可。遇到 rebase 冲突,记住 Git 在"把补丁打到新基底",冲突是因为上下文对不上,手动解决后 --continue。一旦你用"指针 + 快照"看问题,就没有学不会的 Git 命令。

第四,理解 packfile,理解 git gc。 大仓库变慢、.git 膨胀,往往是因为松散对象太多或 packfile 老化。定期 git gc(或 git repack -ad)能重算增量、压缩体积、加速日常操作。对 CI/镜像构建场景,浅克隆(--depth)和部分克隆(--filter=blob:none)也是基于这套对象模型做的取舍——少拉一些 blob,换取速度。

第五,内容寻址是一种可迁移的设计哲学。 Git、Docker 镜像层(每层按内容哈希)、Nix/Guix 包管理器、IPFS、区块链——它们共享同一个内核:用内容的哈希当它的唯一身份。 看懂 Git,你就半只脚踏进了现代分布式存储和可复现构建(reproducible builds)的世界。

延伸学习路径

  1. 《Pro Git》第 10 章 "Git Internals"(git-scm.com 官方,免费)——把对象模型、packfile、refspec 讲到骨子里,是这篇解读的事实底座。
  2. GitHub Engineering 博客:《Commits are snapshots, not diffs》(Derrick Stolee)——由 Git 核心维护者亲自澄清那个最大的误解,权威性无可辩驳。
  3. 动手实验:git cat-file -p <hash> 与 git hash-object——亲手用底层命令把 blob、tree、commit 哈希出来、读回去,比读十篇文章都管用。试着改一个字符,观察哈希雪崩。
  4. Julia Evans 的 Git 漫画《Oh shit, git!》及 zine——以极其直观的图示讲清 reset/rebase/reflog,配上本文的快照模型食用效果最佳。
  5. Linus Torvalds 2007 年 Google Talk:《Tech Talk: Linus Torvalds on git》——听设计者本人解释为什么 Git 必须是分布式的、为什么他鄙视 SVN 的分支模型,能让你从源头理解这套设计的动机。

一句话总结

Git 不是存差异的版本工具,而是一台用"内容哈希 + 完整快照 + 轻量指针"搭起来的、密码学自洽的去中心化数据库——一旦你从快照而非差异去看它,所有的困惑都会自动归位。

参考来源

  1. Git Will Finally Make Sense After This — LearnThatStack — 本文分析的核心视频(1M+ views)
  2. Commits are snapshots, not diffs — The GitHub Blog (Derrick Stolee) — 官方维护者对"快照 vs 差异"误解的权威澄清
  3. Git Internals: Git Objects — Pro Git (git-scm.com) — 官方文档对 blob/tree/commit 对象模型与内容寻址的原始定义
  4. Git Commit: Snapshot vs Delta Storage Mechanics Explained — sqlpey.com — 澄清"概念快照、物理 packfile 增量"两层模型的技术细节
  5. Git: The Elegant Machinery of Content-Addressable Storage — decoupled.dev — 从 Linus 第一性原理设计角度解读默克尔树与 DAG

    本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。

完成摘要

标题核验:「Git不存差异存快照:分支因此近乎免费」= 19 字(≤20),domain=programming,video 链接完整,三行元信息格式合规。

注意事项:视频时长 13:25 略低于 15 分钟建议线,但百万播放量与频道质量已充分保证内容深度与热度;核心论点全部基于 GitHub 官方维护者文章与 Git 官方文档,技术准确性有据可查。

[!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学通信知识星球海报-扫码加入