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 对象串联成了我们看到的“提交历史”。
三、 从 add 到 commit:底层发生了什么?
让我们用“考古”的视角复盘一次提交过程:
-
执行
git add <file>:- Git 将文件内容计算哈希,压缩后存入
objects/成为一个 Blob。 - 更新
index文件,记录文件名与该 Blob 哈希的对应关系。
- Git 将文件内容计算哈希,压缩后存入
-
执行
git commit:- Git 根据当前
index的状态创建一个 Tree 对象。 - 创建一个 Commit 对象,指向这个 Tree,并记录你的提交信息。
- 更新
refs/heads/master(或当前分支文件),让它指向这个新的 Commit 哈希。
- Git 根据当前
四、 为什么 Git 如此之快?
作为金融级程序员,你可能关心性能。Git 的架构带来了两个极致优势:
- 瞬时分支切换:切换分支只是修改了
HEAD指向的文件内容(一个哈希值),并在工作区恢复对应的 Tree 结构。它不需要像 SVN 那样拷贝整个代码库。 - 无损压缩与去重:由于是内容寻址,相同内容只存一份。此外,Git 还会定期将松散的对象打包成
.pack文件,通过增量压缩进一步减小体积。
💡 总结
理解了 .git 的结构,你就明白为什么 git checkout 某个文件会覆盖本地修改,也明白了为什么只要 .git 还在,即便你删光了工作区代码也能瞬间找回。