主题
字号
CHAPTER 01 ≈ 12 MIN READ

概念入门:Git 是什么,为什么要学

没有 Git 的世界

想象你正在写一篇大作业报告,你可能会这样命名文件:

报告.docx
报告_最终版.docx
报告_最终版2.docx
报告_真的最终版.docx
报告_绝对最终版_别改了.docx

这就是没有版本控制的状态。你不知道哪个是最好的版本,也不记得每个版本改了什么。

现在,换成写代码的场景:你和三个队友一起做 RoboGame 的控制程序,每个人各写一部分,最后怎么合并?用 QQ 传文件?谁的版本算数?如果合并出了 Bug,怎么回退?

这就是 Git 要解决的问题。

Git 能做什么

一个大比喻: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)     │
                         └────────────────┘

类比理解:

为什么要有暂存区?

因为你可能改了 10 个文件,但只想提交其中 3 个相关的改动。暂存区让你可以精确控制每次提交的内容。

commit 是什么

Commit 是 Git 的基本单元,每个 commit 包含:

Mav's Tips: 你可能会疑惑,既然git宣称自己每次 commit 保存的是“完整的项目快照”,那一个文件被提交100次,.git文件夹岂不是变成原来的100倍?

其实并不是这样,Git底层有三个机制保证空间利用最大化:

  1. 基于SHA-1 哈希的去重:

    Git会把每一个文件的内容通过哈希算法计算转换成一个40位的SHA-1哈希值,之后压缩对应文件,并把压缩的文件打包到一个名为 Blob 的对象,该对象的名称即为哈希值,最后储存在.git/objects里面。(可以理解为,哈希值就是文件的门牌号)

    如果某一次修改只修改了文件A而没有修改文件B,那么A会产生一个新的 Blob 对象,但B不会,新的快照会用指针直接指向原来旧的 Blob 对象。

    因此,对于绝大多数只修改了少数文件的 commit,Git其实没有对所有文件进行处理,只是对更新的文件重新计算哈希值生成一个新的 Blob 对象,老文件直接继续使用。

  2. 基于 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 版的完整内容秒速拿出来给你,不需要任何计算!