揭开 .git 的黑盒:Git 是如何工作的?

3 阅读3分钟

Git 不仅仅是一个版本控制工具,它本质上是一个内容寻址文件系统(Content-Addressable File System) ,外加一个构建在它之上的 VCS 用户界面。

要揭开 .git 的黑盒,我们先要在脑子里建立一个核心模型:Git 并不存储“文件差分(Diff)”,它存储的是“快照(Snapshot)”。


一、 .git 文件夹:Git 的“心脏”

当你执行 git init 时,那个神秘的 .git 隐藏文件夹就诞生了。它几乎包含了 Git 所有的秘密:

  • objects/ :Git 的“数据库”,存储所有的文件内容和提交记录。
  • refs/ :存储指向提交对象的指针(分支、标签等)。
  • HEAD:一个文件,内容通常是 ref: refs/heads/master,告诉 Git 你现在在哪。
  • index:暂存区(Staging Area),一个二进制文件,记录了下次提交时快照的样子。

二、 三位一体:Git 的对象模型

Git 将一切都抽象为对象(Object) 。每个对象通过对内容进行 SHA-1 哈希计算,得到一个 40 位的唯一 ID。

1. Blob(Binary Large Object)

  • 角色:存储文件内容
  • 特点:它不记录文件名,只管内容。这意味着如果你有两个内容完全一样的文件,Git 只会存一个 Blob,从而实现天然的数据去重。

2. Tree

  • 角色:存储目录结构
  • 内容:关联 Blob 对象或其他 Tree 对象,并记录文件名、权限等元数据。
  • 比喻:如果 Blob 是文件,Tree 就是文件夹。

3. Commit

  • 角色:存储快照元数据
  • 内容:指向根 Tree 对象(项目的顶层快照),记录作者、提交者、时间戳以及**父提交(Parent)**的哈希值。
  • 意义:通过 Parent 指针,Commit 对象串联成了我们看到的“提交历史”。

三、 从 addcommit:底层发生了什么?

让我们用“考古”的视角复盘一次提交过程:

  1. 执行 git add <file>

    • Git 将文件内容计算哈希,压缩后存入 objects/ 成为一个 Blob
    • 更新 index 文件,记录文件名与该 Blob 哈希的对应关系。
  2. 执行 git commit

    • Git 根据当前 index 的状态创建一个 Tree 对象。
    • 创建一个 Commit 对象,指向这个 Tree,并记录你的提交信息。
    • 更新 refs/heads/master(或当前分支文件),让它指向这个新的 Commit 哈希。

四、 为什么 Git 如此之快?

作为金融级程序员,你可能关心性能。Git 的架构带来了两个极致优势:

  • 瞬时分支切换:切换分支只是修改了 HEAD 指向的文件内容(一个哈希值),并在工作区恢复对应的 Tree 结构。它不需要像 SVN 那样拷贝整个代码库。
  • 无损压缩与去重:由于是内容寻址,相同内容只存一份。此外,Git 还会定期将松散的对象打包成 .pack 文件,通过增量压缩进一步减小体积。

💡 总结

理解了 .git 的结构,你就明白为什么 git checkout 某个文件会覆盖本地修改,也明白了为什么只要 .git 还在,即便你删光了工作区代码也能瞬间找回。