2. Git核心概念:工作区、暂存区、本地仓库、远程仓库的关系、Git对象模型(Blob/Tree/Commit)、HEAD指针与分支的本质
好,咱们直接进入正题。Git这东西,刚接触时容易懵。为什么?因为它的概念层比SVN多了一层。我当年从SVN转Git,第一周就踩了坑——以为git add就是提交,结果代码丢了半天的改动。嗯,今天咱们就把这几个核心概念彻底讲透。
2.1 四个区域:工作区、暂存区、本地仓库、远程仓库
Git的代码流转,说白了就是文件在四个区域之间搬家。你想想看,这跟咱们平时写文档、审稿、归档、分享的流程一模一样。
| 区域名称 | 位置 | 作用 | 我常用的比喻 |
|---|---|---|---|
| 工作区(Working Directory) | 你电脑上看到的文件夹 | 你实际编辑代码的地方 | 你的办公桌,文件摊在上面 |
| 暂存区(Staging Area / Index) | .git/index 文件 | 临时存放你准备提交的改动 | 你的待办清单,勾选了才提交 |
| 本地仓库(Local Repository) | .git/objects 目录 | 永久保存你提交的历史版本 | 你的文件柜,归档好的档案 |
| 远程仓库(Remote Repository) | 服务器上(GitHub/GitLab等) | 团队共享代码的地方 | 公司的档案室,大家都能看 |
它们之间的关系,我用一句话总结:工作区改代码 → 暂存区挑改动 → 本地仓库存版本 → 远程仓库做同步。
核心流程:
# 1. 在工作区修改文件
vim main.c
# 2. 把改动加入暂存区
git add main.c
# 3. 把暂存区内容提交到本地仓库
git commit -m "修复了定时器溢出bug"
# 4. 把本地仓库推送到远程仓库
git push origin main
我个人习惯,每次提交前都会用 git status 看一眼。为什么?因为有一次我在嵌入式项目里,改了三个文件,只add了两个就commit了。结果编译报错,排查了半天才发现少提交了一个头文件。从那以后,git status 成了我的肌肉记忆。
我的小技巧: 用 git diff --cached 查看暂存区里到底有哪些改动。这比靠脑子记靠谱多了。
2.2 Git对象模型:Blob、Tree、Commit
Git底层其实是个内容寻址的文件系统。什么意思?就是每个文件、每个目录、每次提交,都被Git算出一个40位的SHA-1哈希值,然后存起来。你想想看,这就像图书馆给每本书一个唯一的索书号。
Git的对象模型有三种核心类型:
- Blob(二进制大对象):存储文件内容。文件名不重要,内容才重要。同样的内容,不管文件名是什么,哈希值都一样。
- Tree(树对象):存储目录结构。它记录了文件名、文件权限,以及对应的Blob哈希值。
- Commit(提交对象):存储一次提交的快照。它指向一个Tree对象,还记录了作者、时间、提交信息,以及父Commit的哈希值。
它们的关系是这样的:
Commit (abc123)
├── 作者: 张三
├── 时间: 2024-01-15
├── 信息: "修复了看门狗复位问题"
└── Tree (def456)
├── src/
│ ├── main.c → Blob (a1b2c3)
│ └── uart.c → Blob (d4e5f6)
└── inc/
└── config.h → Blob (g7h8i9)
我在项目中遇到过一个问题:有同事把同一个文件复制了十份,改了不同的配置。结果Git仓库变得巨大。为什么?因为每个文件内容不同,Git会生成十个不同的Blob对象。后来我们改用配置文件模板+条件编译,仓库大小直接降了80%。
避坑指南: 我曾经见过有人把编译生成的 .hex 和 .bin 文件也提交到Git里。这些二进制文件每次编译都会变,Git会为每个版本生成新的Blob对象。仓库很快就膨胀到几个G。记住:只提交源码,不提交编译产物。
2.3 HEAD指针与分支的本质
HEAD是什么?说白了,就是一个指针。它指向你当前所在的位置。大多数时候,HEAD指向某个分支的最新提交。
分支又是什么?很多人以为分支是目录的副本,其实不是。分支本质上也是一个指针,指向某个Commit对象。创建分支就是创建一个新的指针,成本极低。
# 查看HEAD指向哪里
cat .git/HEAD
# 输出: ref: refs/heads/main
# 查看main分支指向哪个commit
cat .git/refs/heads/main
# 输出: a1b2c3d4e5f6...(40位哈希值)
你想想看,为什么Git创建分支这么快?因为它只是写了一个40字节的文件。不像SVN,要复制整个目录树。
我刚开始用Git时,对分支切换总是提心吊胆。有一次在嵌入式项目里,我在feature分支上改了三天代码,切回main分支时发现工作区的代码全变了。当时吓出一身冷汗。后来才明白:Git切换分支时,会用分支指向的Commit里的Tree对象,去更新你的工作区。所以切换前,一定要确保工作区是干净的(没有未提交的改动)。
HEAD的三种状态:
- 指向分支:正常状态,HEAD → main → Commit
- 分离头指针(Detached HEAD):HEAD直接指向某个Commit,不在任何分支上。这时候提交的代码,切换分支后就找不到了。我建议新手不要碰这个模式。
- 指向标签:HEAD → v1.0.0 → Commit,用于查看历史版本
我的习惯: 每次切分支前,先执行 git status 确认工作区干净。如果有未提交的改动,要么 git stash 暂存起来,要么先提交。这个习惯帮我避免了好几次代码丢失的悲剧。
2.4 四个区域的完整流转图
咱们用一张表格来总结这四个区域之间的操作关系:
| 操作 | 从哪到哪 | 常用命令 | 注意事项 |
|---|---|---|---|
| 修改代码 | 工作区 → 工作区 | vim / vscode | 改完记得保存 |
| 暂存改动 | 工作区 → 暂存区 | git add | 可以只add部分文件 |
| 提交版本 | 暂存区 → 本地仓库 | git commit | 写清楚提交信息 |
| 推送到远程 | 本地仓库 → 远程仓库 | git push | 先pull再push |
| 拉取更新 | 远程仓库 → 本地仓库 | git pull | 等于fetch+merge |
| 查看历史 | 本地仓库 → 工作区 | git log | 可以用--oneline简化 |
嗯,这里要注意一点:git pull 其实是 git fetch + git merge 的组合。我个人习惯先 git fetch 看看远程有什么变化,再决定要不要合并。直接 git pull 有时候会引入你不想要的合并冲突。
好了,Git的核心概念就讲到这里。你想想看,工作区是你写代码的桌面,暂存区是你挑拣改动的购物车,本地仓库是你家的保险柜,远程仓库是银行的保险柜。理解了这四个区域的关系,Git就入门了一半。下一章咱们聊聊分支策略,那才是嵌入式项目里真正考验人的地方。