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就入门了一半。下一章咱们聊聊分支策略,那才是嵌入式项目里真正考验人的地方。