Git 基础概念与原理
Git 基础概念与原理
深入理解 Git 的设计哲学与内部工作机制
版本控制系统概述
什么是版本控制?
版本控制系统(Version Control System, VCS)是一种记录文件内容变化,以便将来查阅特定版本修订情况的系统。它的核心价值在于:
- 历史追溯 - 查看任意时间点的文件状态
- 变更比对 - 了解文件的具体修改内容
- 协作支持 - 多人同时修改同一项目
- 分支开发 - 并行开发不同功能
- 错误恢复 - 回退到之前的稳定版本
版本控制系统的演进
| 类型 | 特点 | 局限 |
|---|---|---|
| 本地版本控制 | 单机使用 | 无法协作 |
| 集中式版本控制 | 中央服务器 | 单点故障、网络依赖 |
| 分布式版本控制 | 完全分布式 | 每人都有完整副本,离线工作 |
1. 本地版本控制系统
最早的版本控制方式是简单地复制整个项目目录来保存不同版本。后来出现了 RCS(Revision Control System),它通过保存文件的差异补丁来管理版本。
局限性:只能在单机使用,无法支持团队协作。
2. 集中式版本控制系统(CVCS)
代表:CVS、SVN、Perforce
所有版本数据存储在中央服务器,开发者从服务器获取最新版本进行开发。
优点:
- 集中管理,权限控制方便
- 开发者可以看到其他人的工作进度
缺点:
- 单点故障 - 服务器宕机影响所有人
- 网络依赖 - 离线无法工作
- 分支成本高
3. 分布式版本控制系统(DVCS)
代表:Git、Mercurial、Bazaar
每个开发者都拥有完整的仓库副本,包括完整的历史记录。
优点:
- 完全离线工作
- 无单点故障
- 分支操作极快
- 灵活的工作流程
Git 的诞生与设计哲学
Git 的起源
Git 由 Linux 内核之父 Linus Torvalds 于 2005 年创建。起因是 Linux 内核项目与 BitKeeper(一款商业分布式版本控制系统)的合作破裂。
Linus 在设计 Git 时确立了以下目标:
- 速度 - 必须非常快
- 简单的设计 - 核心概念要简洁
- 对非线性开发的强力支持 - 支持成千上万个并行分支
- 完全分布式 - 没有中央服务器的概念
- 有能力高效管理超大规模项目 - Linux 内核级别的规模
Git 的设计哲学
1. 快照,而非差异
传统版本控制系统存储的是文件差异(delta):
传统方式(基于差异):
Version 1: [File A] [File B] [File C]
Version 2: [Δ A] [File B] [Δ C]
Version 3: [Δ A] [Δ B] [File C]
Version 4: [File A] [Δ B] [Δ C]
Git 方式(基于快照):
Version 1: [A1] [B1] [C1]
Version 2: [A2] [B1] [C2] ← B1 是对同一文件的引用
Version 3: [A2] [B2] [C1]
Version 4: [A1] [B2] [C2]
Git 更像是一个小型文件系统,每次提交都保存项目的完整快照。如果文件没有修改,Git 只保留一个指向之前相同文件的引用。
2. 几乎所有操作都是本地的
Git 的大多数操作只需要访问本地文件和资源,不需要网络连接:
- 查看项目历史 → 本地
- 比较不同版本 → 本地
- 创建分支 → 本地
- 切换分支 → 本地
- 提交更改 → 本地
只有在需要与远程仓库同步时才需要网络。
3. Git 保证完整性
Git 中所有数据在存储前都会计算校验和(checksum),然后以校验和来引用。Git 使用 SHA-1 哈希算法:
SHA-1 哈希示例:
24b9da6552252987aa493b52f8696cd6d3b00373
特点:
- 40 位十六进制字符
- 基于文件内容计算
- 任何微小变化都会产生完全不同的哈希值
- 几乎不可能出现哈希冲突
这意味着:
- 不可能在 Git 不知情的情况下更改任何文件内容
- 传输过程中的数据丢失或损坏都能被检测到
- Git 数据库中的每个对象都有唯一标识
4. Git 一般只添加数据
Git 操作几乎只往数据库中添加数据。很难让 Git 执行任何不可逆操作,或者清除数据。这使得使用 Git 成为一个安全的操作——你可以放心地进行各种实验,因为几乎总能恢复。
Git 的三种状态与三个区域
三种状态
Git 中的文件有三种状态:
- 已修改(Modified):修改了文件,但还没有保存到数据库中
- 已暂存(Staged):对已修改文件做了标记,使之包含在下次提交的快照中
- 已提交(Committed):数据已经安全地保存在本地数据库中
三个区域
与三种状态对应,Git 项目有三个主要区域:
工作目录(Working Directory)
工作目录是项目某个版本的单次检出(checkout)。这些文件是从 Git 仓库的压缩数据库中提取出来的,放在磁盘上供你使用或修改。
暂存区(Staging Area / Index)
暂存区是一个文件(位于 .git/index),保存了下次将要提交的文件列表信息。有时候也被称为"索引"。
Git 仓库目录(Git Directory / Repository)
Git 仓库目录是 Git 用来保存项目的元数据和对象数据库的地方。这是 Git 最重要的部分,从其它计算机克隆仓库时,复制的就是这里的数据。
基本工作流程
- 在工作目录中修改文件
- 将想要下次提交的更改选择性地暂存(
git add) - 提交更新,将暂存区的文件快照永久存储到 Git 目录中(
git commit)
Git 内部原理
Git 对象模型
Git 是一个内容寻址文件系统,核心是一个简单的键值对数据库。Git 存储的对象主要有四种类型:
| 对象类型 | 存储内容 | 说明 |
|---|---|---|
| Blob(数据块) | 文件内容(二进制数据) | 不包含文件名 |
| Tree(树) | 目录结构(文件名和 blob/tree 引用) | 类似目录列表 |
| Commit(提交) | 提交信息(作者、时间、消息、父提交) | 项目快照 |
| Tag(标签) | 标签信息(版本标记) | 指向特定提交 |
1. Blob 对象(数据块)
Blob 存储文件的内容(不包含文件名)。每个不同的文件内容都会创建一个唯一的 blob 对象。
# 查看 blob 对象
$ git cat-file -p <blob-sha1>
# 输出文件内容
$ git cat-file -t <blob-sha1>
blob
2. Tree 对象(树)
Tree 对象存储目录结构,包含:
- 指向 blob 对象的指针(文件)
- 指向其他 tree 对象的指针(子目录)
- 文件名和模式
tree 对象结构示例:
100644 blob a906cb README.md
100644 blob 8f9462 package.json
040000 tree f30ab1 src
# 查看 tree 对象
$ git cat-file -p <tree-sha1>
100644 blob a906cb... README.md
100644 blob 8f9462... package.json
040000 tree f30ab1... src
3. Commit 对象(提交)
Commit 对象包含:
- 指向 tree 对象的指针(项目快照)
- 指向父 commit 的指针(可以有多个,如合并提交)
- 作者信息
- 提交者信息
- 提交消息
commit 对象结构:
tree d8329fc
parent a11bef0 (第一次提交没有 parent)
author Scott <scott@example.com> 1234567890 +0800
committer Scott <scott@example.com> 1234567890 +0800
Initial commit
# 查看 commit 对象
$ git cat-file -p <commit-sha1>
tree d8329fc...
parent a11bef0...
author Scott <scott@example.com> 1234567890 +0800
committer Scott <scott@example.com> 1234567890 +0800
Initial commit
4. Tag 对象(标签)
Tag 对象(标注标签)包含:
- 指向被标记对象的指针(通常是 commit)
- 标签名
- 标签创建者信息
- 标签消息
对象之间的关系
.git 目录结构
.git/
├── HEAD # 指向当前分支的指针
├── config # 项目特有的配置
├── description # GitWeb 程序使用
├── hooks/ # 客户端或服务端的钩子脚本
│ ├── pre-commit.sample
│ ├── post-commit.sample
│ └── ...
├── index # 暂存区信息
├── info/ # 包含 exclude 文件
│ └── exclude # 本地忽略模式
├── logs/ # 引用日志
│ ├── HEAD
│ └── refs/
├── objects/ # 所有数据内容(对象数据库)
│ ├── info/
│ ├── pack/ # 打包后的对象
│ ├── 4b/ # 对象存储(前2位作目录名)
│ │ └── 825dc6... # 对象文件(后38位作文件名)
│ └── ...
└── refs/ # 指向数据的提交对象的指针
├── heads/ # 本地分支
│ └── main
├── tags/ # 标签
└── remotes/ # 远程跟踪分支
└── origin/
└── main
引用(References)
引用是指向 commit 对象 SHA-1 值的指针,存储在 .git/refs/ 目录中。
# 查看 HEAD 引用
$ cat .git/HEAD
ref: refs/heads/main
# 查看分支引用
$ cat .git/refs/heads/main
98ca9a1b72f0f2b9cad2e8f3e4b5a6c7d8e9f0a1
# 查看远程跟踪分支
$ cat .git/refs/remotes/origin/main
98ca9a1b72f0f2b9cad2e8f3e4b5a6c7d8e9f0a1
特殊引用
| 引用 | 说明 |
|---|---|
HEAD | 当前检出的提交/分支 |
FETCH_HEAD | 最近获取的远程分支 |
ORIG_HEAD | 危险操作前的 HEAD 备份 |
MERGE_HEAD | 正在合并的提交 |
CHERRY_PICK_HEAD | 正在 cherry-pick 的提交 |
SHA-1 与数据完整性
SHA-1 哈希
Git 使用 SHA-1 算法为每个对象生成唯一标识:
输入:任意长度的数据
输出:40 位十六进制字符串(160 位二进制)
示例:
"hello world" → 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed
"hello world!" → 430ce34d020724ed75a196dfc2ad67c77772d169
内容寻址的优势
- 数据去重 - 相同内容只存储一次
- 完整性验证 - 自动检测数据损坏
- 快速比对 - 比较哈希值即可知道内容是否相同
- 分布式友好 - 全局唯一标识,无需中央协调
哈希碰撞
SHA-1 的理论碰撞概率极低(约 2^80 次操作找到碰撞),但已被证明存在实际碰撞攻击。Git 正在逐步迁移到 SHA-256。
打包与存储优化
松散对象与打包对象
Git 最初将每个对象存储为独立文件(松散对象)。当对象数量增多时,Git 会将它们打包成单个文件以节省空间和提高效率。
松散对象存储:
.git/objects/4b/825dc...
.git/objects/a1/1bef0...
.git/objects/98/ca9a1...
打包后:
.git/objects/pack/pack-xxx.pack # 打包数据
.git/objects/pack/pack-xxx.idx # 索引文件
打包过程
# 手动触发打包
$ git gc
# 查看打包统计
$ git count-objects -v
count: 7
size: 28
in-pack: 156
packs: 1
size-pack: 120
prune-packable: 0
garbage: 0
size-garbage: 0
Delta 压缩
打包时,Git 会查找相似的对象,只存储差异:
file.txt v1 (1000 bytes) → 完整存储
file.txt v2 (1010 bytes) → 只存储与 v1 的差异 (约 50 bytes)
file.txt v3 (1015 bytes) → 只存储与 v2 的差异 (约 30 bytes)
Git 引用规范
引用语法
# 完整引用
refs/heads/main # 本地分支
refs/remotes/origin/main # 远程跟踪分支
refs/tags/v1.0 # 标签
# 简写形式
main # 本地分支
origin/main # 远程跟踪分支
v1.0 # 标签
# 相对引用
HEAD # 当前提交
HEAD^ # 父提交
HEAD~3 # 往前第3个提交
HEAD^2 # 第二个父提交(合并时)
引用解析顺序
当使用简写时,Git 按以下顺序查找:
.git/<ref>(如 HEAD).git/refs/<ref>.git/refs/tags/<ref>.git/refs/heads/<ref>.git/refs/remotes/<ref>.git/refs/remotes/<ref>/HEAD
祖先引用
commit 历史:
A ← B ← C ← D ← E (HEAD)
↑
F (merged)
HEAD = E
HEAD^ = D (第一个父提交)
HEAD^2 = F (第二个父提交,来自合并)
HEAD~1 = D
HEAD~2 = C
HEAD~3 = B
HEAD^^ = C (等价于 HEAD~2)
HEAD^2^ = F 的父提交
总结
理解 Git 的基础概念和内部原理,能够帮助你:
- 更好地使用 Git - 理解命令背后的原理,避免错误操作
- 解决复杂问题 - 当出现问题时,知道去哪里找答案
- 优化工作流程 - 根据原理设计更高效的工作方式
- 深入学习 - 为学习高级特性打下基础
核心要点回顾
| 概念 | 要点 |
|---|---|
| 分布式 | 每个开发者都有完整仓库副本 |
| 快照模型 | 存储完整快照,而非差异 |
| 内容寻址 | SHA-1 哈希确保数据完整性 |
| 三种状态 | Modified → Staged → Committed |
| 三个区域 | 工作目录 → 暂存区 → Git 仓库 |
| 对象类型 | Blob、Tree、Commit、Tag |