Git内部原理揭秘
Git不仅是工具,更是一个内容寻址文件系统.理解其内部原理,能让你在使用时更加得心应手.
Git的核心数据结构
Git用四种对象存储所有数据:
# Git对象类型: # 1. blob - 文件内容 # 2. tree - 目录结构 # 3. commit - 提交信息 # 4. tag - 标签 # 查看.git目录结构 $ ls -la .git/ # objects/ - 存储所有对象 # refs/ - 存储分支和标签引用 # HEAD - 当前分支指针 # index - 暂存区
Blob对象:存储文件内容
# Git用SHA-1哈希作为文件名存储内容 # 文件内容 → SHA-1哈希 → 对象文件 # 手动创建blob对象 echo "Hello Git" | git hash-object --stdin # 输出:b7aec520dec0a9746479c1e5b3c2f1e4c5a8d9b2 # 查看blob内容 git cat-file -p b7aec520dec0a9746479c1e5b3c2f1e4c5a8d9b2 # 输出:Hello Git
关键特性:相同内容只存一份,天然去重.
Tree对象:存储目录结构
# Tree对象存储文件名,权限,类型和对应的blob哈希
# 查看某次提交的树结构
git cat-file -p HEAD^{tree}
# 输出示例:
# 100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README.md
# 100644 blob 8f94139338f9404f26296befa88755fc2598c289 main.py
# 040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 src
# 含义:
# 100644 = 普通文件权限
# blob = 文件内容对象
# tree = 子目录对象
Commit对象:存储版本历史
# 查看commit对象详情 git cat-file -p HEAD # 输出: # tree 4d5fcadc293a348e88f777dc0920f11e7d5e8e79 # parent 3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b # author Zhang San1712640000 +0800 # committer Zhang San 1712640000 +0800 # # 修复登录bug # 关键字段: # tree - 本次提交对应的目录树 # parent - 父提交(形成链表结构) # author/committer - 作者信息和时间戳
分支的本质:可移动的指针
# 分支只是指向commit的引用 $ cat .git/refs/heads/main # a1b2c3d4e5f6... (40位commit哈希) # HEAD指向当前分支 $ cat .git/HEAD # ref: refs/heads/main # 切换分支就是修改HEAD的指向 git checkout feature # 等价于:echo "ref: refs/heads/feature" > .git/HEAD
图解Git存储结构
工作目录 暂存区(index) 本地仓库(objects) │ │ │ │ git add │ │ │───────────────>│ │ │ │ git commit │ │ │───────────────────>│ │ │ │ │ │ ┌─────────────┐ │ │ │ commit │ │ │ │ ┌────────┐ │ │ │ │ │ tree │─┼──> 根目录树 │ │ │ │ parent │─┼──> 父提交 │ │ │ │ author │ │ │ │ │ │ message│ │ │ │ │ └────────┘ │ │ │ └─────────────┘
Pack文件:压缩存储
Git会定期将松散对象打包,节省空间:
# 手动触发打包 git gc # 查看pack文件 $ ls .git/objects/pack/ # pack-a1b2c3d4e5f6.pack - 打包的数据 # pack-a1b2c3d4e5f6.idx - 索引文件 # pack文件使用增量压缩 # 只存文件差异,而不是完整副本
总结
Git的设计哲学:内容寻址,不可变对象,引用指向.理解这些原理,你就能理解为什么Git分支切换那么快,为什么回滚那么安全.
