Git 原理解析

Git 原理解析

目录


概述

Git 不仅仅是一个版本控制系统,它的核心是一个内容寻址的文件系统。理解 Git 的内部机制,能够帮助我们更好地使用 Git,并在遇到问题时知道如何解决。

核心特点

  • 内容寻址:通过内容计算哈希值,相同内容产生相同哈希
  • 不可变性:对象一旦创建,内容不可修改
  • 完整性:通过哈希值验证数据完整性
  • 高效性:相同内容只存储一份,通过引用共享

Git 的核心设计

内容寻址的文件系统

Git 的核心是一个键值对数据库 (key-value store),其工作流程如下:

复制代码
┌─────────────────────────────────────┐
│      Git 内容寻址流程                │
├─────────────────────────────────────┤
│  1. 计算哈希 (SHA-1)                │
│  2. 存储内容 (.git/objects/)        │
│  3. 通过哈希寻址                    │
└─────────────────────────────────────┘

工作流程

  1. 计算哈希:对一段内容(如文件、目录结构、提交信息)附加一个头部信息,计算出一个唯一的 SHA-1 哈希值(40位十六进制字符串)

  2. 存储内容 :将内容压缩后,以其 SHA-1 值的前两位为目录名、后38位为文件名,存储在 .git/objects/ 目录下

  3. 通过哈希寻址:之后,Git 便通过这个 SHA-1 哈希值来读取或引用该内容

存储结构

复制代码
.git/objects/
├── ab/
│   └── c123def456...  (哈希值: abc123def456...)
├── cd/
│   └── e789f012...    (哈希值: cde789f012...)
└── ...

键值对数据库

Git 本质上是一个键值对数据库:

  • 键 (Key):SHA-1 哈希值
  • 值 (Value):对象内容(blob、tree、commit、tag)

特点

  • ✅ 相同内容产生相同哈希,只存储一份
  • ✅ 内容一旦存储,不可修改(不可变性)
  • ✅ 通过哈希值可以验证内容完整性
  • ✅ 高效的内容去重

Git 的四种核心对象

Git 的对象主要分为四类:blobtreecommittag。其中前三者构成了版本控制的基础。

Blob:文件内容

作用 :仅存储文件内容,不包含文件名、权限等任何元数据。

特点

  • 内容相同,SHA-1 值就相同
  • Git 只存储一份副本(内容去重)
  • 不包含文件名信息

示例

bash 复制代码
# 将 "hello" 存入对象库,返回其 SHA-1 值
echo 'hello' | git hash-object -w --stdin
# 输出: ce013625030ba8dba906f756967f9e9ca394464a

# 查看对象类型
git cat-file -t ce013625030ba8dba906f756967f9e9ca394464a
# 输出: blob

# 查看对象内容
git cat-file -p ce013625030ba8dba906f756967f9e9ca394464a
# 输出: hello

存储位置

复制代码
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a

内容去重示例

bash 复制代码
# 创建两个内容相同的文件
echo "hello" > file1.txt
echo "hello" > file2.txt

# 添加到 Git
git add file1.txt file2.txt

# 查看对象,发现只存储了一份
git cat-file --batch-check --batch-all-objects | grep blob
# 两个文件指向同一个 blob 对象

Tree:目录结构

作用 :代表项目中的目录,记录了该目录下所有文件和子目录的列表。

内容:每条记录包含:

  • 文件模式(如 100644 普通文件,100755 可执行文件,040000 目录)
  • 对象类型(blobtree
  • SHA-1 值
  • 文件名

示例 :一个 tree 对象的内容可能如下:

复制代码
100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README
100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib

这代表一个包含 READMERakefile 文件和 lib 子目录的目录。

查看 tree 对象

bash 复制代码
# 查看根目录的 tree
git cat-file -p HEAD^{tree}

# 查看指定 tree 对象
git cat-file -p <tree-hash>

# 查看 tree 的类型
git cat-file -t <tree-hash>

文件模式说明

模式 说明 示例
100644 普通文件 README.md
100755 可执行文件 script.sh
040000 目录 src/
120000 符号链接 link

Commit:版本快照

作用 :代表项目在某个时间点的完整快照,并记录了提交元信息。

内容

  • 指向根目录 tree 对象的 SHA-1 值
  • 一个或多个父 commit 的 SHA-1 值(首次提交无父提交,合并提交有多个父提交)
  • 作者、提交者信息及时间戳
  • 提交信息(commit message)

示例 :一个 commit 对象的内容可能如下:

复制代码
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
parent fdf4fc3344e67ab068f836878b6c4951e3b15f3d
author Alice <alice@example.com> 1617187200 +0800
committer Alice <alice@example.com> 1617187200 +0800

First commit

查看 commit 对象

bash 复制代码
# 查看 commit 对象
git cat-file -p <commit-hash>

# 查看 commit 的类型
git cat-file -t <commit-hash>

# 查看最新提交
git cat-file -p HEAD

提交链

所有 commit 通过 parent 指针串联起来,形成一条可追溯的历史链:

复制代码
C1 ← C2 ← C3 ← C4

合并提交

合并提交有多个父提交:

复制代码
     C2 ← C3
    /       \
C1           C4 (合并提交)
    \       /
     C5 ← C6

Tag:里程碑标签

作用 :为某个特定的 commit 对象(或其他对象)创建一个永久的、人类可读的别名 ,通常用于标记发布版本(如 v1.0.0)。

类型

  1. 轻量标签 (Lightweight Tag)

    • 直接指向 commit 的指针
    • 不包含额外信息
  2. 附注标签 (Annotated Tag)

    • 一个独立的 tag 对象
    • 包含打标签者信息、日期和消息

创建标签

bash 复制代码
# 创建轻量标签
git tag v1.0.0

# 创建附注标签
git tag -a v1.0.0 -m "Release version 1.0.0"

# 查看标签
git show v1.0.0

查看 tag 对象

bash 复制代码
# 查看 tag 对象内容
git cat-file -p v1.0.0

# 查看 tag 类型
git cat-file -t v1.0.0

对象关系图

一次 git commit 后,这几个对象的关系如下:

复制代码
                    [Commit]
                       |
                       v
              [Tree] (根目录)
              /  |  \
             v   v   v
    [Blob] ... [Tree] ... (子目录和文件)
    (文件内容)

关系说明

  1. Commit 指向项目根目录tree
  2. Tree 指向其包含的文件 blob 和子目录 tree
  3. Blob 仅存储文件内容

完整示例

复制代码
Commit: abc123...
  |
  v
Tree: def456... (根目录)
  |
  +-- 100644 blob ghi789... README.md
  |
  +-- 100644 blob jkl012... main.py
  |
  +-- 040000 tree mno345... src/
      |
      +-- 100644 blob pqr678... utils.py

从 git add 到 git commit 的底层流程

1. git add:生成 Blob & 更新暂存区

流程

  1. 读取工作区文件内容
  2. 生成 blob 对象并存入 .git/objects
  3. 更新暂存区 (Index) ,使其指向这个新生成的 blob 对象

暂存区 (Index)

  • 位置:.git/index
  • 格式:二进制文件
  • 内容:记录了下一次提交的内容快照
  • 作用:暂存待提交的文件

查看暂存区

bash 复制代码
# 查看暂存区内容
git ls-files --stage

# 查看暂存区的 tree
git write-tree
git cat-file -p $(git write-tree)

2. git commit:生成 Tree & Commit

流程

  1. 生成 Tree :根据暂存区的内容,为当前目录及所有子目录生成新的 tree 对象

  2. 生成 Commit :创建一个 commit 对象,它指向根目录的 tree,并指向上一个 commit(形成历史链)

  3. 更新分支 :将当前分支(如 main)的引用更新为这个新 commit 的 SHA-1 值

详细步骤

bash 复制代码
# 1. 生成 tree 对象(基于暂存区)
tree_hash=$(git write-tree)

# 2. 创建 commit 对象
commit_hash=$(echo "First commit" | git commit-tree $tree_hash)

# 3. 更新分支引用
git update-ref refs/heads/main $commit_hash

.git 目录核心结构

.git 目录是 Git 仓库的核心,包含所有版本控制信息:

复制代码
.git/
├── objects/          # 对象存储目录
│   ├── ab/          # 哈希值前两位作为目录名
│   │   └── c123...  # 哈希值后38位作为文件名
│   └── ...
├── refs/            # 引用目录
│   ├── heads/       # 分支引用
│   │   ├── main     # main 分支指向的 commit
│   │   └── dev      # dev 分支指向的 commit
│   └── tags/        # 标签引用
│       └── v1.0.0   # 标签指向的 commit
├── HEAD             # 当前分支的符号引用
├── index            # 暂存区(二进制文件)
├── config           # 仓库配置
├── hooks/           # Git 钩子脚本
└── ...

objects/ 目录

存放所有 blobtreecommittag 对象。

存储方式

  • 使用 SHA-1 哈希值的前两位作为目录名
  • 后38位作为文件名
  • 内容经过压缩(zlib)

查看对象

bash 复制代码
# 列出所有对象
find .git/objects -type f

# 查看对象类型和内容
git cat-file -t <hash>  # 类型
git cat-file -p <hash>  # 内容
git cat-file -s <hash>  # 大小

refs/ 目录

存放各种引用(分支、标签等),如 refs/heads/main 指向 main 分支的最新 commit

分支引用

复制代码
.git/refs/heads/main
内容: abc123def456... (commit 的 SHA-1 值)

标签引用

复制代码
.git/refs/tags/v1.0.0
内容: abc123def456... (commit 的 SHA-1 值)

查看引用

bash 复制代码
# 查看分支引用
cat .git/refs/heads/main

# 查看所有引用
git show-ref

# 查看分支指向的 commit
git rev-parse main

HEAD 文件

一个符号引用,指向当前检出的分支(如 ref: refs/heads/main)。

查看 HEAD

bash 复制代码
# 查看 HEAD 内容
cat .git/HEAD
# 输出: ref: refs/heads/main

# 查看 HEAD 指向的 commit
git rev-parse HEAD

分离 HEAD

当检出到某个具体的 commit 时,HEAD 直接指向 commit:

bash 复制代码
git checkout abc123
cat .git/HEAD
# 输出: abc123def456...

index 文件

二进制文件,即暂存区,记录了下一次提交的内容快照。

查看暂存区

bash 复制代码
# 查看暂存区内容
git ls-files --stage

# 查看暂存区的统计信息
git diff --cached --stat

分支的本质

Git 的分支本质上是一个指向某个 commit 的轻量级指针文件

分支的存储

例如,refs/heads/main 文件里存储的就是 main 分支最新 commit 的 SHA-1 值:

bash 复制代码
cat .git/refs/heads/main
# 输出: abc123def456789...

分支操作的本质

新建分支

  • 创建一个新文件(如 refs/heads/dev
  • 内容指向当前 commit

提交

  • 在当前分支上提交时,Git 更新该分支文件
  • 使其指向新的 commit

切换分支

  • HEAD 文件的内容被修改,指向新的分支引用
  • 工作区文件随之更新

分支关系图

复制代码
        C1 ← C2 ← C3
              ↑    ↑
            main  dev

分支的优势

由于 commit 对象之间通过 parent 指针相连,分支的创建、合并、切换都非常高效,因为它们只是在移动指针。

创建分支:只需创建一个文件,指向当前 commit(几乎瞬间完成)

切换分支:只需修改 HEAD 和工作区文件(非常快速)

合并分支:只需创建一个新的 commit,指向两个父 commit

引用和符号引用

引用 (Reference)

引用是一个指向 commit 的指针,存储在 .git/refs/ 目录下。

类型

  • 分支引用:refs/heads/<branch>
  • 标签引用:refs/tags/<tag>
  • 远程引用:refs/remotes/<remote>/<branch>

查看引用

bash 复制代码
# 查看所有引用
git show-ref

# 查看分支引用
git show-ref --heads

# 查看标签引用
git show-ref --tags

符号引用 (Symbolic Reference)

符号引用是一个指向另一个引用的引用,如 HEAD

HEAD 的两种状态

  1. 指向分支(正常状态):

    复制代码
    HEAD → refs/heads/main → commit
  2. 直接指向 commit(分离 HEAD):

    复制代码
    HEAD → commit

查看符号引用

bash 复制代码
# 查看 HEAD 指向
git symbolic-ref HEAD

# 查看所有符号引用
find .git -type f -name "HEAD" -o -name "*HEAD*"

Git 对象存储机制

对象压缩

Git 使用 zlib 压缩算法存储对象,节省存储空间。

压缩效果

  • 文本文件:通常压缩率 50-70%
  • 二进制文件:压缩率较低

对象打包

为了进一步提高效率,Git 会将多个对象打包成 pack 文件。

打包机制

  • 位置:.git/objects/pack/
  • 格式:.pack 文件(打包数据)+ .idx 文件(索引)
  • 触发:git gc(垃圾回收)时自动打包

查看打包文件

bash 复制代码
# 查看 pack 文件
ls .git/objects/pack/

# 查看 pack 文件信息
git verify-pack -v .git/objects/pack/*.idx

对象去重

相同内容的文件只存储一份,通过引用共享。

去重示例

bash 复制代码
# 创建两个内容相同的文件
echo "hello" > file1.txt
echo "hello" > file2.txt

# 添加到 Git
git add file1.txt file2.txt

# 查看对象,发现只存储了一份
git cat-file --batch-check --batch-all-objects | grep blob

哈希算法与完整性

SHA-1 哈希

Git 使用 SHA-1 算法计算对象的哈希值。

特点

  • 40 位十六进制字符串
  • 相同内容产生相同哈希
  • 内容稍有改动,哈希值完全不同
  • 不可逆(无法从哈希值反推内容)

计算示例

bash 复制代码
# 计算内容的哈希值
echo "hello" | git hash-object --stdin
# 输出: ce013625030ba8dba906f756967f9e9ca394464a

完整性验证

Git 通过哈希值验证对象完整性:

bash 复制代码
# 验证对象完整性
git fsck

# 验证特定对象
git cat-file -t <hash>
git cat-file -p <hash>

损坏检测

如果对象文件损坏,Git 会检测到哈希值不匹配,报告错误。

实际示例演示

示例 1:手动创建对象

bash 复制代码
# 1. 创建 blob 对象
echo "Hello, Git!" | git hash-object -w --stdin
# 输出: a5c19667710254f4f5137305c3b1092e62643261

# 2. 查看 blob 对象
git cat-file -p a5c19667710254f4f5137305c3b1092e62643261
# 输出: Hello, Git!

# 3. 创建 tree 对象(需要先有 blob)
git update-index --add --cacheinfo 100644 \
  a5c19667710254f4f5137305c3b1092e62643261 hello.txt

# 4. 写入 tree 对象
tree_hash=$(git write-tree)
echo $tree_hash

# 5. 创建 commit 对象
commit_hash=$(echo "Initial commit" | \
  git commit-tree $tree_hash)
echo $commit_hash

# 6. 更新分支引用
git update-ref refs/heads/main $commit_hash

示例 2:查看对象关系

bash 复制代码
# 查看最新提交
git cat-file -p HEAD

# 查看提交指向的 tree
git cat-file -p HEAD^{tree}

# 查看 tree 中的文件
git ls-tree HEAD

# 查看文件的 blob
git cat-file -p HEAD:README.md

示例 3:探索对象存储

bash 复制代码
# 查看所有对象
find .git/objects -type f | head -10

# 查看对象类型分布
git cat-file --batch-check --batch-all-objects | \
  awk '{print $2}' | sort | uniq -c

# 查看对象大小
git cat-file --batch-check --batch-all-objects | \
  awk '{sum+=$3} END {print sum}'

总结

核心概念总结

  1. Blob :存文件内容
  2. Tree :存目录结构(文件名、子目录等)
  3. Commit :存项目快照历史tree + parent + 元信息)
  4. 分支 :一个指向特定 commit指针

Git 的设计优势

  1. 内容寻址:通过内容计算哈希,相同内容只存储一份
  2. 不可变性:对象一旦创建,内容不可修改
  3. 完整性:通过哈希值验证数据完整性
  4. 高效性:分支操作只是移动指针,非常快速

理解原理的价值

理解了这套对象模型,你就能明白:

  • resetrevertmerge 等高级操作背后的原理
  • 为什么 Git 如此高效
  • 如何解决复杂的问题
  • 如何优化 Git 仓库

深入学习

相关推荐
Mo_YuO.o2 小时前
git的安装以及本地仓库的创建
git·gitee·github
CoderJia程序员甲2 小时前
GitHub 热榜项目 - 日榜(2026-01-19)
git·ai·开源·llm·github
-大头.4 小时前
GIT教程系列(共3篇)---------第二篇:Git高级协作与团队实战完全指南
大数据·git·elasticsearch
Q741_1475 小时前
Git 添加文件基本操作与简单原理
git
好评1246 小时前
git常见操作及问题
linux·git
小王C语言7 小时前
版本控制器git和调试器gdb
git
-大头.9 小时前
GIT教程系列(共3篇)---------第一篇:Git入门与核心概念完全指南
大数据·git·elasticsearch
_Xiaosz12 小时前
Git 拉取子模块报错 Permission denied (publickey) 的排查与解决
git
晚霞的不甘12 小时前
Flutter for OpenHarmony 实战:[开发环境搭建与项目编译指南]
git·flutter·react native·react.js·elasticsearch·visual studio code