概念入门:Git 是什么,为什么要学
没有 Git 的世界
想象你正在写一篇大作业报告,你可能会这样命名文件:
报告.docx
报告_最终版.docx
报告_最终版2.docx
报告_真的最终版.docx
报告_绝对最终版_别改了.docx
这就是没有版本控制的状态。你不知道哪个是最好的版本,也不记得每个版本改了什么。
现在,换成写代码的场景:你和三个队友一起做 RoboGame 的控制程序,每个人各写一部分,最后怎么合并?用 QQ 传文件?谁的版本算数?如果合并出了 Bug,怎么回退?
这就是 Git 要解决的问题。
Git 能做什么
- ✅ 记录每一次修改:知道什么时候改了什么、谁改的
- ✅ 随时回退:代码写崩了?一键回到任意历史版本
- ✅ 并行开发:多人同时开发不同功能,互不干扰
- ✅ 合并代码:智能合并多人的修改
- ✅ 备份到云端:通过 GitHub 把代码存到服务器
一个大比喻:Git 是游戏存档系统
把你的代码项目想象成一个RPG 游戏:
| 游戏概念 | Git 概念 | 说明 |
|---|---|---|
| 游戏存档 | commit(提交) | 保存当前状态的快照 |
| 存档槽位列表 | log(历史记录) | 所有存档的时间线 |
| 读取存档 | checkout(读档) | 回到某个历史状态 |
| 新开一周目 | branch(分支) | 从某点开辟新的时间线 |
| 合并两周目进度 | merge(合并) | 把两条时间线合并 |
| 游戏云存档 | GitHub / 远程仓库 | 把存档同步到云端 |
| 下载别人的存档 | clone(克隆) | 把他人项目完整下载 |
三个区域:工作区、暂存区、仓库
这是 Git 最重要的概念,很多初学者在这里卡住。
┌─────────────────────────────────────────────────────┐
│ 你的电脑 │
│ │
│ ┌──────────┐ git add ┌──────────┐ │
│ │ │ ──────────► │ │ │
│ │ 工作区 │ │ 暂存区 │ │
│ │(Working │ ◄────────── │(Staging │ │
│ │ Tree) │ git restore│ Area) │ │
│ │ │ │ │ │
│ └──────────┘ └──────────┘ │
│ │ │
│ git commit │
│ ▼ │
│ ┌──────────┐ │
│ │ 本地仓库 │ │
│ │ (Local │ │
│ │ Repo) │ │
│ └──────────┘ │
└─────────────────────────────────────────────────────┘
│ git push / pull
▼
┌────────────────┐
│ 远程仓库 │
│ (GitHub) │
└────────────────┘
类比理解:
- 工作区:你的桌面,你正在写/改的文件
- 暂存区:打包区,你把要提交的文件先放到这里"打包"
- 本地仓库:已封存的快递包裹,存在你电脑里的历史记录
- 远程仓库:寄出去的包裹,存在 GitHub 服务器上
为什么要有暂存区?
因为你可能改了 10 个文件,但只想提交其中 3 个相关的改动。暂存区让你可以精确控制每次提交的内容。
commit 是什么
Commit 是 Git 的基本单元,每个 commit 包含:
- 📸 一个完整的项目快照(不是差异,是完整状态)
- 💬 提交信息(你写的说明,比如"修复电机控制Bug")
- 🔑 唯一哈希值(如
a3f8c2d,用于标识这次提交) - 👤 作者信息(谁提交的,什么时间)
- ⬅️ 父提交指针(指向上一次提交)
Mav's Tips: 你可能会疑惑,既然git宣称自己每次 commit 保存的是“完整的项目快照”,那一个文件被提交100次,.git文件夹岂不是变成原来的100倍?
其实并不是这样,Git底层有三个机制保证空间利用最大化:
基于SHA-1 哈希的去重:
Git会把每一个文件的内容通过哈希算法计算转换成一个40位的SHA-1哈希值,之后压缩对应文件,并把压缩的文件打包到一个名为 Blob 的对象,该对象的名称即为哈希值,最后储存在.git/objects里面。(可以理解为,哈希值就是文件的门牌号)
如果某一次修改只修改了文件A而没有修改文件B,那么A会产生一个新的 Blob 对象,但B不会,新的快照会用指针直接指向原来旧的 Blob 对象。
因此,对于绝大多数只修改了少数文件的 commit,Git其实没有对所有文件进行处理,只是对更新的文件重新计算哈希值生成一个新的 Blob 对象,老文件直接继续使用。
基于 Packfile 的差异存储:
你可能好奇,虽然我们知道commit只会针对修改过的文件进行处理,但是如果我对一个10MB的文件修改100次,还不是会有近1GB的大小吗?
这个时候,git后台的git gc (Garbage Collection)就会触发 Packfile 机制。
简单说,Git 会把这 100 个历史版本的 Blob 找出来,对比它们的内容。它会发现,这 100 个文件有 99% 的内容是一模一样的!(因为我们不太可能每次都对一个大文件覆盖重写) 因此,他会使用 增量压缩,把这100个版本的差异记录下来。
重点来了,Git 的增量压缩是“倒着来”的:
1. 常规思维(正向差异): 存下第 1 版的完整内容。第 2 版只存相对于第 1 版改了什么,第 3 版存相对于第 2 版改了什么……
2. Git 的思维(逆向差异): Git 会把最新的一版(第 100 版)原封不动地完整存下来。然后,计算第 99 版相对于第 100 版需要“撤销”哪些修改,把它存为一个“差异补丁”。第 98 版再存一个基于第 99 版的差异补丁……依此类推。
为什么要“倒着来”?因为 Git 的作者(Linus Torvalds)太懂程序员了!
在日常开发中,我们 99% 的操作,都是在查看和修改最新的代码。
如果按常规的“正向差异”存,你要看最新的第 100 版,Git 就必须先拿出第 1 版,然后把后面 99 次的修改像叠罗汉一样一层层算出来给你,这会慢得令人发指。
而用了“逆向差异”,当你要看最新代码时,Git 直接从 Packfile 里把第 100 版的完整内容秒速拿出来给你,不需要任何计算!