外观
Git原理
约 4474 字大约 15 分钟
Git
2025-08-26
Git 的核心是一个内容寻址的文件系统,结合版本控制逻辑。
.git文件
.git文件
hooks存放钩子脚本 自动化Git操作(如提交、推送执行什么东西)。
…
info
exclude仓库级别的忽略规则
…
logs存储 HEAD 和分支的变更历史,包含 git reflog 的
…
objects存储所有Git对象(Blob、Tree、Commit、Tag。hash前2位为文件夹,后38位文件本体)
…
refs存储引用
heads本地分支 执行最新的commit
…
remotes远程分支 执行远程仓库的commit
…
tags标签 指向commit或者tag对象
…
COMMIT_EDITMSG最近一次提交的描述
config存储仓库配置,如远程 URL、用户设置
description仓库描述
HEAD指定当前分支或 Commit 哈希
index暂存区,记录文件路径、权限、Blob 哈希
ORIG_HEAD记录危险操作前的 HEAD 位置,供恢复
packed-refs压缩的引用文件
内容寻址的文件系统
- 数据的存储和检索基于
SHA-1哈希值(40 位十六进制字符串),而非传统的路径、文件名形式 - 数据的唯一标识是由内容生成的,相同内容具有相同的 hash,因此可通过 hash 快速定位与复用
- Git 的所有对象(
Blob、Tree、Commit、Tag)都是以内容寻址的方式存储在.git/objects中
SHA-1
一种加密哈希函数,输入任意长度的数据,输出固定长度(160 位,20 字节)的哈希值,通常表示为 40 位十六进制字符串。具有确定性、单向性、唯一性、固定性
生成文件SHA-1:git hash-object <文件>
核心对象
- Blob 对象 (Binary Large Object:二进制大对象)
- 表示文件的内容,不包含文件名或路径。
- 每个文件版本对应一个唯一的 blob 对象,存储在
.git/objects/目录下。 - Blob 的哈希值由文件内容计算(
SHA-1)。
- Tree 对象
- 表示目录结构,包含指向 blob(文件内容)和其他 tree(子目录)的引用。
- 记录文件名、文件模式(权限)以及对应的 blob 或 tree 的哈希值。
- 类似于文件系统的目录快照。
- Commit 对象
- 表示一次提交,包含指向 tree(提交时的目录快照)、父提交、作者、提交信息等。
- 记录了每次提交的元数据和快照。
Git核心对象详解及流程
- 对象模型:
- Blob 对象(Binary Large Object:二进制大对象):存储文件内容(不包括文件名、路径),每个 blob 是文件内容的快照,用 SHA-1 哈希标识。
- 在 Git 中的作用
- 文件内容快照:每次 git add 和 git commit 时,Git 为变更的文件创建 Blob 对象,记录其内容快照。
- 去重机制:通过 SHA-1 哈希,Git 确保相同内容的 Blob 只存储一次,实现高效空间利用。
- 版本控制:Blob 对象与 Tree 和 Commit 对象结合,记录文件的版本历史。Tree 对象引用 Blob,Commit 对象引用 Tree,形成完整的历史记录。
- 定义:
- Git 中的 Blob 不限于二进制文件,也存储文本文件内容。
- 每个 Blob 对象通过 SHA-1 哈希值(40 位十六进制字符串)唯一标识,基于文件内容计算。
- 特点:
- Blob 的 SHA-1 哈希值由文件内容决定。相同的文件内容(即使文件名不同)生成相同的哈希值,Git 只存储一份,节省空间。
- 示例:如果项目中多个文件内容相同(如两个分支的 README.md 完全一样),Git 只存储一个 Blob 对象。(这也是为什么分支切换那么快)
- Blob 对象存储在 .git/objects 目录下,哈希值的前两位作为子目录名,后 38 位作为文件名。
- 使用 zlib 压缩存储,减少磁盘空间占用。随着仓库增长,通过 git gc(垃圾回收)打包到 packfile 中,进一步使用 delta 压缩优化存储。
- 每次 git add 和 git commit 时,Git 为变更的文件创建 Blob 对象,记录其内容快照。
- Blob 的 SHA-1 哈希值由文件内容决定。相同的文件内容(即使文件名不同)生成相同的哈希值,Git 只存储一份,节省空间。
- 在 Git 中的作用
- Tree 对象:表示目录结构,记录文件名、文件类型(blob 或子 tree)及其哈希值。
- Commit 对象:记录一次提交的元数据,包括作者、提交信息、时间戳、指向的 tree 对象和父提交的哈希。
- Tag 对象:用于标记特定提交(如版本发布)。
- 所有对象存储在
.git/objects目录中,以哈希值的前 2 位作为子目录名,后 38 位作为文件名。
- Blob 对象(Binary Large Object:二进制大对象):存储文件内容(不包括文件名、路径),每个 blob 是文件内容的快照,用 SHA-1 哈希标识。
- 工作流程:
- 工作目录:用户编辑代码的地方,包含实际文件。
- 暂存区(Index):通过
git add将变更文件添加到暂存区(.git/index),记录待提交的内容。 - 提交:通过
git commit创建一个 commit 对象,包含当前暂存区的 tree 对象快照、父提交引用和元数据。 - 分支:分支是一个指向 commit 对象的指针,存储在
.git/refs/heads中。HEAD 指向当前分支或提交。 - 合并与冲突:合并时,Git 比较分支的 commit 历史,基于共同祖先(merge base)计算差异,自动合并或提示冲突。
- 历史管理:
- Git 通过 commit 对象的父指针形成有向无环图(DAG),记录提交历史。
- 操作如
git log、git diff通过遍历 DAG 快速获取历史信息。
- 远程协作:
- 远程仓库(如 GitHub)存储在服务器上,
git push和git pull通过传输差异(packfile 格式)同步本地和远程仓库。 - Git 协议优化了网络传输,仅发送必要的对象和差异。
- 远程仓库(如 GitHub)存储在服务器上,
查看对象
#blob
git ls-files --stage #暂存区所有文件的详细信息
#tree
git ls-tree <hash>
#commit
git cat-file -p <hash>
-t <hash> #查看指定hash的类型
#git cat-file -p <hash> 什么都能看Git 实现速度快和节省空间的机制
- 快照机制(而非差异存储):
- 原理:Git 不存储每个文件的完整差异(delta),而是为每次提交(commit)创建整个工作目录的快照。快照记录文件内容的引用(通过 SHA-1 哈希),只存储实际发生变更的内容。
- 节省空间:如果文件在多次提交中未改变,Git 不会重复存储,而是通过引用指向相同的文件内容(使用 blob 对象)。这避免了冗余存储。
- 速度快:快照机制使 Git 在切换分支、查看历史或恢复版本时只需操作引用,而无需计算复杂的差异,速度极快。
- 内容寻址存储:
- 原理:Git 使用 SHA-1 哈希算法为每个文件内容、目录结构和提交生成唯一的 40 位哈希值,作为存储的键(key)。相同内容的哈希值相同,因此 Git 可以快速识别和复用相同内容。
- 节省空间:通过哈希去重,Git 确保相同内容只存储一次。例如,项目中多个分支包含相同的文件,Git 只存储一份。
- 速度快:哈希值使得查找和比较内容变得高效,Git 能快速定位所需数据。
- 对象存储与压缩:
- 原理:Git 将数据存储为四种对象:blob(文件内容)、tree(目录结构)、commit(提交元数据)、tag(标签)。这些对象存储在
.git/objects目录中,并使用 zlib 压缩。 - 节省空间:压缩后的对象占用空间小,尤其当文件内容变化较小时,Git 仅存储变更部分(通过 packfile 进一步优化)。
- 速度快:zlib 压缩和解压速度快,Git 在读写对象时性能几乎不受影响。
- Packfile 和 Delta 压缩:
- 原理:当仓库中的对象积累到一定程度,Git 会通过
git gc(垃圾回收)将对象打包为 packfile,并使用 delta 压缩技术存储相似对象之间的差异。这种压缩在对象级别进行,而非直接对文件差异操作。 - 节省空间:Packfile 将相似对象(例如文件的不同版本)进行增量压缩,大幅减少存储空间。
- 速度快:Git优化了packfile的读取,配合索引文件(pack index)快速定位数据。
- 原理:当仓库中的对象积累到一定程度,Git 会通过
- 原理:Git 将数据存储为四种对象:blob(文件内容)、tree(目录结构)、commit(提交元数据)、tag(标签)。这些对象存储在
- 分布式架构:
- 原理:Git是分布式版本控制系统,每个本地仓库包含完整的提交历史和数据,无需频繁访问远程服务器。
- 速度快:本地操作(如提交、查看历史、切换分支)无需网络请求,速度极快。
- 节省空间:本地仓库只存储必要的数据,远程交互(如 push/pull)仅传输差异部分。
- 高效的分支管理:
- 原理:Git 的分支只是一个指向某次提交(commit)的轻量级引用(存储在
.git/refs中),创建和切换分支只需修改一个指针。 - 速度快:分支操作(如创建、切换、删除)几乎是瞬时的,因为只涉及小文件的读写。
- 节省空间:分支引用占用极少空间(仅几字节),不会因分支增多而显著增加存储。
- 原理:Git 的分支只是一个指向某次提交(commit)的轻量级引用(存储在
工作流程
git add
- 生成 Blob 对象
- 读取文件内容
- 使用 SHA-1 计算文件内容 hash (40 位十六进制)
- 将文件内容(zlib)压缩存储为 blob 对象,保存在
.git/objects目录,路径.git/objects/哈希前2位/哈希后38位 - 更新暂存区
.git/index,记录文件路径、名称、权限、blob 哈希值 - 如果文件内容未变(hash 相同),直接复用现有的
git commit
- 生成 Tree 对象
- 检查暂存区(
.git/index)的内容,获取所有已暂存的文件(路径、名称、权限、blob 哈希) - 根据暂存区状态创建一个或多个Tree 对象表示当前目录结构:
- 每个 Tree 对象记录一个目录。(文件名、类型(blob 或子 tree)、权限、blob 哈希)
- 如果有子目录,递归创建子 tree 对象,最终形成 tree 树
- Tree 对象存储在
.git/objects目录下,格式与 blob 类型 - Tree 对象的生成基于暂存区,反应提交时的目录结构和文件引用
- 负责组织目录结构,引用 blob 和子 tree,不直接存储文件内容
- 创建 commit 对象,并将 HEAD 执行最新 commit 对象的哈希
引用关系
- Tree 引用 Blob 和子 Tree 的哈希。
- Commit 引用顶级 Tree 和父 Commit 的哈希。
- 分支(Branch)和 HEAD 指向 Commit 的哈希,形成版本历史的有向无环图(DAG)。
- 三者通过 SHA-1 关联 最终形成版本控制体系
举例说明
假设一个项目有文件 readme.md,内容为:
Hello, Git!- 初始提交:Git 为内容生成 blob 对象(假设哈希为
abc123),tree 对象记录目录结构,commit 对象记录元数据。 - 修改后提交:将
readme.md改为Hello, World!,Git 生成新的 blob 对象(新哈希,例如def456),但未变更的文件仍引用旧 blob(如其他文件)。新 commit 指向新的 tree 对象。 - 存储优化:如果
readme.md的内容在其他分支或提交中重复,Git 只存储一份Hello, Git!的 blob 对象,多个引用指向它。 - 分支操作:创建新分支仅生成一个指向当前 commit 的指针(几字节),切换分支只需更新 HEAD,速度极快。
合并(merge)的原理
Git合并的核心是基于DAG(有向无环图)和对象模型,通过父Commit哈希和三路合并算法整合分支变更。
步骤
确定分支和DAG
- 支指指针
- Git的分支是存储在
。git/refs/heads中的轻量指针,指向某个commit的SHA-1 - 当前分支:
main。在 HEAD (.git/HEAD) 中指向.git/refs/heads/main,记录最新的Commit (C4) - 被合并分支(
dev) 指向另一个commit(C3)
- Git的分支是存储在
- DAG结构:
C4 (main) --> C2 --> C1
\
C3 (dev) --> C1
C4 的父哈希指向 C2,C2 指向 C1;C3 指向 C1。查找共同祖先
- 使用DAG便利两个分支的父Commit链,找到他们的共同祖先(最近共享使用的commit,即交点) (C1)
- 共同祖先的tree提供合并的基准快照,就是以祖先节点为基准来合并差异
三路合并算法
- 三路合并基于三个快照(Tree对象)来合并
- 共同祖先(C1)
- 当前分支(C4)
- 被合并分支(C3)
- Git比较这些快照,分析变更生成合并后的新快照
- 合并过程
- 差异计算
- 比较共同祖先(C1)和当前分支(C4) 得到
main的变更 - 比较共同祖先(C1)和被合并分支(C3),得到
dev的变更
- 比较共同祖先(C1)和当前分支(C4) 得到
- 整合变更
- 如果不冲突(修改的文件不同部分或不同文件),Git直接合并内容 生成新的Tree
- 如果冲突(同一文件的同一部分被修改),Git会标记冲突,暂停合并 等待解决
- 冲突检测
- 逐行比较blob对象,确定是否存在冲突
- 冲突发生时,Git会在工作目录生成冲突标记(
<<<<<<< HEAD>>>>>>> dev),并更新暂存区状态 - 等待手动解决冲突后,
git add标记解决,生成新的tree
- 差异计算
使用命令:git ls-files --stage 可以查看冲突的各分支的文件
生成合并提交
- 无冲突时
- 根据合并后的工作目录生成新的Tree对象,反映整合后的目录结构
- 创建新的Commit对象(C5),包含
- Tree对象
- parent字段:两个父哈希(C4、C3),记录
main和dev的来源 - 元数据:作者、提交信息、
- 存储Commit对象到
.git/objects,更新当前分支指针(.git/refs/heads/main)执行新Commit(c5)的hash指针
- 有冲突
- 解决冲突后 add到缓存区
- 运行commit,流程同上
- 特性情况 快进式合并 (合并的两个分支无分叉时)
- 如果dev的Commit是main的后代(通过父哈希链可达)就不创建合并提交,直接将main的指针移动到C4,保持线性
C1 --> C2 (main) --> C3 (dev) - 不生成新Tree或Commit,复用C3的
- 禁用快进:使用
git merge --no-ff强制创建合并提交,保留分支历史
- 如果dev的Commit是main的后代(通过父哈希链可达)就不创建合并提交,直接将main的指针移动到C4,保持线性
总结
- DAG 和父哈希:合并依赖 DAG,父 Commit 哈希记录分支来源,构建历史关系,支持追溯和可视化。
- 三路合并:基于共同祖先比较两个分支的变更,自动整合或标记冲突。
- 内容寻址:SHA-1 哈希确保去重、快速比较和数据完整性,Tree 和 Blob 提供快照支持。
- 合并提交:生成新 Commit,包含多父哈希,保留分支历史。
- 快进合并:特殊情况下移动指针,保持线性历史。
各个命令对对象的影响
| 命令 | Blob | Tree | Commit | 其他 |
|---|---|---|---|---|
git init | 无影响 | 无影响 | 无影响 | 初始化 .git 目录 |
git add | 生成或复用 Blob | 无影响 | 无影响 | 更新 .git/index |
git commit | 无影响(复用 Blob) | 生成 Tree | 生成 Commit | 更新分支引用,重置 .git/index |
git status | 无影响(只读) | 无影响(只读) | 无影响(只读) | 比较工作目录、暂存区、HEAD |
git checkout <file> | 无影响(读取 Blob) | 无影响(读取 Tree) | 无影响(读取 Commit) | 更新工作目录、.git/index |
git checkout <branch> | 无影响(读取 Blob) | 无影响(读取 Tree) | 无影响(读取 Commit) | 更新 HEAD、工作目录、.git/index |
git merge | 可能生成新 Blob(冲突解决) | 生成新 Tree | 生成合并 Commit | 更新 .git/index、分支引用 |
git rebase | 可能生成新 Blob(冲突解决) | 生成新 Tree | 生成新 Commit | 更新 .git/index、分支引用 |
git push | 打包 Blob 传输 | 打包 Tree 传输 | 打包 Commit 传输 | 更新远程分支引用 |
git pull | 下载新 Blob | 下载新 Tree | 下载新 Commit | 合并,更新工作目录.git/index |
Git原理总结
- 速度快:快照机制、哈希索引、轻量级分支和本地化操作减少了计算和网络开销。
- 节省空间:内容去重、zlib 压缩和 packfile 的 delta 压缩最大化利用存储空间。
- 基本原理:Git 基于对象模型(blob、tree、commit)和内容寻址存储,通过 DAG 管理版本历史,结合高效的合并和传输机制实现版本控制。
