git-principle
Git 核心原理
理解 Git 的设计思想和内部机制,让你在遇到问题时不再慌张。
目录
1. Git 是什么
Git 是一个 分布式版本控制系统(DVCS),由 Linus Torvalds 于 2005 年为管理 Linux 内核源码而创建。
核心设计目标:
- 速度快:绝大多数操作在本地完成,不依赖网络
- 完全分布式:每个开发者的电脑上都有完整的仓库副本
- 数据完整性:所有对象都通过 SHA-1 哈希校验,任何篡改都会被发现
- 支持大规模并行开发:廉价的分支和高效的合并机制
2. 版本控制能解决什么问题
想象你在写一篇毕业论文:
论文_v1.docx
论文_v2.docx
论文_v2_导师修改.docx
论文_v3_最终版.docx
论文_v3_最终版_真的最终版.docx
论文_v3_最终版_打死不改版.docx
Git 帮你把这些混乱的版本管理变成一条清晰的时间线,你可以:
- 随时回退到任何一个历史版本
- 并行开发不同功能,互不干扰
- 多人协作,自动合并各自的修改
- 追踪每一行代码是谁、在什么时候、为什么改的
3. 分布式 vs 集中式
| 特性 | 集中式(SVN) | 分布式(Git) |
|---|---|---|
| 仓库位置 | 只在服务器上 | 每台电脑都有完整仓库 |
| 离线工作 | 不行 | 可以 |
| 速度 | 依赖网速 | 本地操作,极快 |
| 安全性 | 服务器挂了就完了 | 任何一份克隆都是完整备份 |
| 分支成本 | 高(需要复制目录) | 极低(仅创建指针) |
┌──── 集中式(SVN)────┐ ┌──── 分布式(Git)────┐
│ │ │ │
│ ┌──────────┐ │ │ ┌──────────┐ │
│ │ 服务器 │ │ │ │ 远程仓库 │ │
│ │ (唯一仓库)│ │ │ │ (GitHub) │ │
│ └────┬─────┘ │ │ └──┬───┬───┘ │
│ │ │ │ │ │ │
│ ┌────┴────┐ │ │ ┌────┴┐ ┌┴────┐ │
│ │ 客户端A │ │ │ │完整 │ │完整 │ │
│ │(无完整 │ │ │ │仓库A │ │仓库B │ │
│ │ 历史) │ │ │ └─────┘ └─────┘ │
│ └─────────┘ │ │ │
└──────────────────────┘ └──────────────────────┘
4. 三个区域
Git 管理文件时有三个核心区域,这是理解 Git 工作方式的关键:
┌─────────────┐ git add ┌──────────────┐ git commit ┌──────────────┐
│ 工作区 │ ───────���────→ │ 暂存区 │ ────────────→ │ 版本库 │
│ Working Dir │ │ Staging Area │ │ Repository │
│ │ ←──────────── │ │ │ │
│ 你编辑文件 │ git restore │ 准备提交的快照 │ │ 永久保存的 │
│ 的地方 │ │ │ │ 历史记录 │
└─────────────┘ └──────────────┘ └──────────────┘
工作区(Working Directory)
你实际编辑文件的地方,就是你在资源管理器/Finder 里看到的文件夹。你在这里创建、编辑、删除文件,和平时操作文件完全一样。
暂存区(Staging Area / Index)
一个临时区域,存放你「打算提交」的改动。
为什么需要暂存区? 想象你同时改了 5 个文件,但只想提交其中 3 个。暂存区就像一个购物车——你可以挑选哪些改动要提交,哪些暂时不管。这让你的每次提交都干净、有意义。
版本库(Repository)
.git 目录,存放所有历史记录。每次 commit 就是给当前暂存区的状态拍一张「快照」,永久保存。
5. 文件的四种状态
在 Git 的视角下,每个文件都处于以下四种状态之一:
git add
Untracked ─────────────────────→ Staged ──git commit──→ Committed
↑ │
│ 编辑文件 │
└──── Modified ←─────────┘
| 状态 | 含义 | 对应操作 |
|---|---|---|
| Untracked(未跟踪) | 新文件,Git 还不知道它的存在 | 刚创建的文件 |
| Staged(已暂存) | 文件的改动已被标记,等待下次 commit | git add 后 |
| Committed(已提交) | 改动已安全保存到版本库中 | git commit 后 |
| Modified(已修改) | 已提交过的文件被再次编辑 | 编辑已跟踪的文件后 |
用 git status 可以随时查看所有文件的当前状态。
6. 快照,不是差异
这是 Git 与大多数版本控制系统的根本区别。
差异存储(Delta-based)— 其他 VCS 的做法
Version 1: File A (original) | File B (original) | File C (original)
Version 2: ΔA₁ | | ΔC₁
Version 3: ΔA₂ | ΔB₁ |
Version 4: | ΔB₂ | ΔC₂
每个版本只存储相对于上一个版本的「差异(delta)」。恢复某个版本需要从头逐步叠加所有差异。
快照存储(Snapshot-based)— Git 的做法
Version 1: File A₁ | File B₁ | File C₁
Version 2: File A₂ | File B₁ | File C₂ ← B 没变,存一个链接即可
Version 3: File A₃ | File B₂ | File C₂ ← C 没变,存一个链接即可
Version 4: File A₃ | File B₃ | File C₃ ← A 没变,存一个链接即可
每次 commit 都是整个项目的一次完整快照。但 Git 很聪明:如果一个文件没有变化,它不会重新存储,而是保留一个指向上次快照中该文件的链接。这让 Git 既高效又可靠。
优势:
- 恢复任何版本都是 O(1) 操作,不需要逐步回放差异
- 数据完整性更好,不依赖差异链
- 分支切换极快
7. Commit 的本质
每次提交(commit)是一个不可变对象,包含:
- tree 对象:项目所有文件的一个完整快照
- 作者信息:谁写的代码、什么时候写的
- 提交者信息:谁提交的(可能和作者不同)
- 提交说明:commit message
- 父指针:指向上一次提交(形成链式结构)
commit c3 (SHA: a1b2c3...)
│ author: Alice <alice@example.com>
│ date: 2026-04-19 10:30:00
│ message: "添加搜索功能"
│ parent: → commit c2
│
└── tree
├── README.md → blob 对象 (文件内容)
├── src/
│ ├── main.py → blob 对象
│ └── search.py → blob 对象 (新增)
└── .gitignore → blob 对象
Git 的对象模型
Git 的底层是一个内容寻址文件系统,有四种对象:
| 对象类型 | 作用 |
|---|---|
| blob | 存储文件内容(不含文件名) |
| tree | 存储目录结构,记录文件名和 blob 的对应关系 |
| commit | 存储提交信息,指向一个 tree 和父 commit |
| tag | 标记特定 commit(如版本号 v1.0.0) |
每个对象都通过其内容的 SHA-1 哈希值来唯一标识。这意味着:
- 相同的文件内容永远只存一份
- 任何内容被篡改都会导致哈希不匹配
- 哈希值就是对象的「地址」
# 查看某次 commit 的详细信息
git cat-file -p HEAD
# 查看某个 tree 的内容
git cat-file -p HEAD^{tree}
# 查看某个 blob 的内容
git cat-file -p <blob-hash>
8. 分支的本质
分支只是一个指向某次 commit 的可移动指针。这是 Git 最优雅的设计之一。
main
↓
c1 ← c2 ← c3
↑
feature
- 创建一个分支只需要写一个 41 字节的文件(40 个字符的哈希 + 换行符)
- 切换分支只是把 HEAD 指向不同的分支指针
- 所以 Git 的分支操作几乎是瞬间完成的
HEAD 指针
HEAD 是一个特殊指针,指向你「当前所在的分支」:
HEAD → main → c3
当你切换分支时:
git switch feature
# HEAD → feature → c3
当你在 feature 分支上提交时:
git commit -m "新功能"
# HEAD → feature → c4
# main 仍然指向 c3
main
↓
c1 ← c2 ← c3
↖
c4
↑
feature ← HEAD
分离的 HEAD(Detached HEAD)
如果直接 checkout 到一个 commit(而不是分支),HEAD 就不再指向分支,这叫「分离的 HEAD」:
git checkout a1b2c3 # 直接跳到某个 commit
# HEAD → a1b2c3(不经过任何分支)
在这个状态下的提交不属于任何分支,容易丢失。如果要保留,需要创建一个分支:
git switch -c save-my-work
9. .git 目录结构
每个 Git 仓库的根目录下都有一个 .git 隐藏文件夹,这就是 Git 的「大脑」:
.git/
├── HEAD # 当前分支指针(如 ref: refs/heads/main)
├── config # 仓库级配置
├── description # 仓库描述(GitWeb 用)
├── hooks/ # Git 钩子脚本(提交前检查、推送后通知等)
├── index # 暂存区(Staging Area)
├── objects/ # 所有 Git 对象(blob, tree, commit, tag)
│ ├── pack/ # 打包后的对象(节省空间)
│ └── info/
├── refs/ # 所有引用(分支、标签)
│ ├── heads/ # 本地分支(如 heads/main)
│ ├── tags/ # 标签
│ └── remotes/ # 远程跟踪分支
└── logs/ # 引用变更日志(reflog)
关键要点:
- 删除
.git文件夹 = 丢失所有版本历史(工��区文件不受影响) objects/目录下的文件是不可变的,一旦写入就不会被修改refs/heads/main文件内容就是一个 40 位的 commit 哈希值
延伸阅读
推荐学习资源
| 资源 | 说明 |
|---|---|
| Pro Git - Git 内部原理 | 官方书籍中关于 Git 内部原理的章节 |
| Learn Git Branching | 交互式可视化学习 Git 分支 |
最后更新:2026-04-19