第2章:Git核心原理——工作区、暂存区、本地仓库、远程仓库与对象模型
说实话,很多嵌入式工程师刚开始用Git时,都把它当成一个高级的“文件备份工具”。
我见过不少同事,上来就是git add .加git commit -m "update",然后完事。
但这样用Git,说白了就是暴殄天物。
要真正用好Git,你得先理解它的核心原理。尤其是那四个区域和四个对象。
嗯,今天我们就来把这些东西彻底讲透。
2.1 四个工作区域:你的代码流动路线
Git的代码管理,本质上就是文件在不同区域之间的流转。
我习惯把这四个区域想象成一条生产线:
- 工作区(Working Directory):你正在编辑的代码文件。就是你在IDE里看到的那堆东西。
- 暂存区(Staging Area / Index):一个临时存放区。你决定“这次提交要包含哪些改动”,就先放到这里。
- 本地仓库(Local Repository):你本地的
.git目录。所有历史版本都存在这里。 - 远程仓库(Remote Repository):服务器上的仓库。比如GitHub、GitLab,或者你们公司内网的Git服务器。
核心流程:工作区 → git add → 暂存区 → git commit → 本地仓库 → git push → 远程仓库
我在项目中遇到过好几次这样的情况:有人直接在服务器上改代码,改完才发现没提交。为什么?因为他跳过了暂存区,直接在工作区里改,然后以为改了就等于保存了。
记住:工作区的改动,Git不会自动跟踪。你必须手动git add,它才会进入暂存区。
2.2 为什么要多一个“暂存区”?
你可能会问:为什么不能直接从工作区提交到仓库?
其实,这个设计非常巧妙。
举个例子:你正在开发一个嵌入式驱动,同时改了i2c.c、spi.c和gpio.c三个文件。但你想把I2C的bug修复和SPI的新功能分成两次提交。
怎么办?
暂存区就是干这个的。你可以只git add i2c.c,提交一次。然后再git add spi.c,再提交一次。
我的习惯:每次提交前,我都会用git status看一眼暂存区。确认只有我想提交的文件在里面。这能避免很多“手滑”提交。
2.3 Git对象模型:四个核心对象
Git之所以快,是因为它的底层存储方式很特别。
它不是存“文件差异”,而是存“文件快照”。
这些快照,就是由四种对象组成的:
| 对象类型 | 存储内容 | 唯一标识 |
|---|---|---|
| Blob | 文件内容(二进制数据) | SHA-1哈希值 |
| Tree | 目录结构(包含文件名、权限、指向Blob或子Tree的指针) | SHA-1哈希值 |
| Commit | 一次提交的快照(包含指向Tree的指针、作者、时间、提交信息、父Commit) | SHA-1哈希值 |
| Tag | 给某个Commit打上的标签(通常用于版本发布) | SHA-1哈希值 |
2.4 Blob:文件内容的“身份证”
Blob是Git里最基础的对象。
它只存文件内容,不存文件名。你想想看,这意味着什么?
意味着两个不同目录下的同名文件,如果内容一样,Git只会存一份Blob。
我在做嵌入式Linux BSP时,经常需要复制一些头文件。如果内容没变,Git不会浪费空间去存第二份。这就是Blob的设计精妙之处。
2.5 Tree:目录结构的“骨架”
Tree对象解决了“文件名和目录结构”的问题。
一个Tree对象里,记录了:
- 每个文件的权限模式(比如100644表示普通文件,100755表示可执行文件)
- 每个文件的类型(Blob还是子Tree)
- 每个文件的SHA-1哈希(指向对应的Blob或子Tree)
- 每个文件的文件名
说白了,Tree就是你的项目目录结构的一个快照。
我曾经踩过的坑:有一次我误删了.git/objects目录下的某个文件,结果整个仓库都打不开了。因为Tree对象指向的Blob找不到了。所以,永远不要手动修改.git目录下的任何东西。
2.6 Commit:历史的“锚点”
Commit对象,就是一次提交的完整记录。
它包含:
- 一个指向Tree对象的指针(这次提交的项目快照)
- 一个或多个指向父Commit的指针(第一次提交没有父Commit)
- 作者和提交者信息(名字、邮箱、时间戳)
- 提交信息(你写的
-m "fix bug")
你每次执行git commit,Git都会生成一个新的Commit对象。这个对象是不可变的。一旦生成,就不能修改。
嗯,这也是为什么我反复强调:提交信息一定要写清楚。因为以后你回看历史时,只能靠这些信息来理解当时为什么改。
2.7 Tag:给历史打上“路标”
Tag对象,说白了就是一个“别名”。
它指向某个Commit,通常用于标记版本号。比如v1.0.0、v2.1.3。
有两种Tag:
- 轻量标签(Lightweight Tag):只是一个指向Commit的指针。不包含额外信息。
- 附注标签(Annotated Tag):是一个完整的Git对象。包含打标签的人、时间、信息,甚至可以用GPG签名。
我建议:对于正式发布的版本,一定要用附注标签。因为你可以写上版本说明,比如“修复了I2C时钟延时的bug”。轻量标签只适合临时用用。
2.8 对象之间的关系:一张图看懂
我们来梳理一下这些对象是怎么串起来的:
Commit (abc123)
|
+---> Tree (def456)
|
+---> Blob (111aaa) "main.c"
+---> Blob (222bbb) "i2c.c"
+---> Tree (333ccc) "include/"
|
+---> Blob (444ddd) "i2c.h"
你每次提交,Git都会:
- 把工作区里所有文件生成对应的Blob(如果内容没变,复用已有的Blob)
- 根据目录结构生成Tree对象
- 创建一个新的Commit对象,指向这个Tree,并指向上一个Commit
这就是Git为什么这么快的原因——它只存快照,不存差异。而且通过哈希值来去重。
2.9 远程仓库:不只是“备份”
很多人觉得远程仓库就是用来备份代码的。
其实不止。
远程仓库的核心作用是协作。
你本地仓库有的东西,远程仓库不一定有。反之亦然。
常见的远程仓库操作:
git clone:从远程仓库复制一份到本地git fetch:从远程仓库拉取最新数据,但不合并git pull:git fetch+git merge,拉取并合并git push:把本地仓库的提交推送到远程仓库
注意:git push之前,一定要先git pull。否则如果远程仓库有别人推送的新提交,你的推送会被拒绝。我曾经有一次忘了pull,直接push,结果冲突了半个项目……嗯,那天的晚饭都没吃好。
2.10 小结:理解原理,才能用好工具
好了,我们来总结一下今天的内容:
- 四个区域:工作区(你改代码的地方)→ 暂存区(你决定提交什么的地方)→ 本地仓库(存历史的地方)→ 远程仓库(协作的地方)
- 四个对象:Blob(文件内容)、Tree(目录结构)、Commit(历史快照)、Tag(版本标签)
- 核心思想:Git存的是快照,不是差异。每个对象都有唯一的SHA-1哈希值。
你想想看,理解了这些原理之后,你再遇到“文件丢了”、“提交错了”、“合并冲突”这些问题时,是不是心里就有底了?
下一章,我们会深入讲解Git的分支模型。那才是Git真正的杀手锏。
一句话记住今天的内容:工作区是你干活的地方,暂存区是你挑活的地方,本地仓库是你存活的地方,远程仓库是大家伙一起干活的地方。