2. 版本控制入门:Git 基础操作与工作流

好,咱们开始聊版本控制。说实话,我见过太多嵌入式工程师,代码写得漂亮,但一提到 Git 就头疼。嗯,这很正常。我刚入行那会儿,也吃过没用好版本控制的亏——有一次改了个驱动,改完发现回不去了,硬是熬了一整夜重写。从那以后,我养成了一个习惯:写代码之前,先想好怎么管代码

这一章,咱们就搞定 Git 最核心的几个操作。你不需要成为 Git 专家,但 clonecommitpushpullbranch 这几个命令,必须烂熟于心。

2.1 为什么嵌入式开发必须用 Git?

你想想看,嵌入式项目有多乱?

  • 固件版本 A、B、C,客户要的是哪个?
  • 今天改了 GPIO 驱动,明天发现把定时器配置覆盖了。
  • 团队里三个人同时改同一个文件,最后谁的代码算数?

Git 就是来解决这些问题的。它不只是一个备份工具,更是一个协作框架。我个人习惯,每个嵌入式项目的第一件事,就是初始化 Git 仓库。哪怕只有我一个人写代码,也绝不偷懒。

核心思想:Git 记录的是「快照」,不是「差异」。每次 commit,都是对整个项目的一次完整拍照。

2.2 克隆仓库:git clone

场景很简单:你加入一个新项目,或者要接手别人的代码。第一步,把远程仓库拉到本地。

git clone https://github.com/example/firmware.git

这条命令会做三件事:

  1. 在本地创建一个同名文件夹(比如 firmware
  2. 把远程仓库的所有历史记录下载下来
  3. 自动设置好远程跟踪分支(origin/main)

我在项目中遇到过一种情况:克隆一个很大的仓库(比如包含整个 SDK),网络断了。这时候别慌,Git 支持断点续传:

git clone --depth 1 https://github.com/example/firmware.git

加上 --depth 1,只克隆最新版本,不下载历史。对于只想看看代码、不想考古的人来说,这招很实用。

小技巧:如果你在公司内网,记得先配好 SSH 密钥。用 HTTPS 克隆每次都要输密码,烦得很。我个人习惯用 SSH,一劳永逸。

2.3 提交更改:git commit

这是 Git 最核心的操作。说白了,就是把你改动的代码「拍一张照」,存到本地仓库里。

流程分三步:

  1. 修改文件——你写代码
  2. 暂存——告诉 Git 哪些改动要提交
  3. 提交——生成一个快照
# 查看当前状态
git status

# 暂存所有改动
git add .

# 或者只暂存某个文件
git add src/main.c

# 提交,并写清楚说明
git commit -m "fix: 修复 UART 波特率配置错误"

嗯,这里要注意:git add . 会把所有未跟踪的文件都加进去。如果你有编译生成的 .o 文件、或者 IDE 的临时文件,也会被加进去。所以,一定要配好 .gitignore 文件

我曾经踩过的坑:有一次没配 .gitignore,把整个 build 目录都提交了。结果仓库体积暴涨,同事拉代码等了半小时。从那以后,我每次初始化仓库,第一件事就是写 .gitignore。

关于 commit message,我建议遵循这个格式:

类型 说明 示例
feat 新功能 feat: 添加 I2C 驱动
fix 修复 bug fix: 修正 SPI 时钟极性
docs 文档变更 docs: 更新 API 说明
refactor 重构代码 refactor: 简化中断处理逻辑

2.4 推送与拉取:git push & git pull

本地 commit 只是第一步。要让别人看到你的代码,得推送到远程仓库。

# 推送到远程
git push origin main

# 拉取最新代码
git pull origin main

这里有个常见的误区:很多人以为 git pull 就是 git fetch + git merge。其实没错,但 git pull 会自动合并,有时候会带来冲突。

我个人习惯是:

  1. git fetch 看看远程有什么变化
  2. git log --oneline origin/main 确认一下
  3. 最后 git pull 或者手动 git merge

为什么这么麻烦?因为有一次我直接 git pull,结果远程有人改了同一个文件,冲突了。当时我一脸懵,花了好一阵才解决。所以,先看看再动手,永远没错

黄金法则:在 push 之前,永远先 pull。确保你的本地代码是最新的,再推送。

2.5 分支管理:git branch

分支是 Git 最强大的功能之一。说白了,就是让你在「平行宇宙」里开发,互不干扰。

嵌入式开发中,分支的典型用法:

  • main:稳定版本,只放经过测试的代码
  • develop:日常开发分支
  • feature/xxx:某个功能的分支,比如 feature/i2c-driver
  • fix/xxx:修复 bug 的分支
# 查看所有分支
git branch -a

# 创建新分支
git branch feature/timer-driver

# 切换到该分支
git checkout feature/timer-driver

# 或者一步到位
git checkout -b feature/timer-driver

我在项目中遇到过一种情况:开发一个新功能,改到一半,突然有个紧急 bug 要修。怎么办?

  1. 先把当前改动暂存起来:git stash
  2. 切回 main 分支,新建一个 fix 分支
  3. 修完 bug,合并,推送
  4. 切回 feature 分支,git stash pop 恢复改动

这个流程,我几乎每周都要用一次。你想想看,如果没有分支和 stash,你只能硬着头皮把半成品代码提交,或者干脆删掉重写。多难受。

分支命名规范:我建议用 类型/描述 的格式。比如 feature/add-pwm-supportfix/correct-adc-calibration。这样一看就知道这个分支是干嘛的。

2.6 合并分支:git merge

功能开发完了,bug 修好了,下一步就是把分支合并回主分支。

# 先切到目标分支(比如 main)
git checkout main

# 合并 feature 分支
git merge feature/timer-driver

合并的时候,最怕的就是冲突。冲突长什么样?

<<<<<<< HEAD
// 当前分支的代码
int baud_rate = 115200;
=======
// 要合并进来的代码
int baud_rate = 9600;
>>>>>>> feature/timer-driver

遇到冲突别慌。手动编辑文件,把 <<<<<<<=======>>>>>>> 这些标记删掉,保留你想要的代码。然后 git add,再 git commit 就行了。

我曾经犯过的错:合并冲突时,我直接删掉了对方的代码,没仔细看。结果把一个重要的校准参数弄丢了,导致整批设备出厂测试失败。所以,解决冲突时,一定要和写那段代码的人沟通清楚,别自作主张。

2.7 嵌入式 Git 工作流推荐

说了这么多,咱们总结一个适合嵌入式团队的工作流:

  1. main 分支:只放经过严格测试的版本。每次发布固件时,打一个 tag,比如 v1.2.0
  2. develop 分支:日常开发的主战场。所有 feature 分支从这里拉出来
  3. feature 分支:每个新功能一个分支。开发完成后,合并回 develop
  4. release 分支:准备发布时,从 develop 拉一个 release 分支。只修 bug,不加新功能
  5. hotfix 分支:线上出了紧急 bug,从 main 拉分支修,修完合并回 main 和 develop

这个流程,说白了就是 Git Flow 的简化版。对于嵌入式团队来说,够用了。我见过一些团队搞得太复杂,光分支策略就讲半天,结果开发效率反而下降了。记住:流程是为人服务的,不是反过来

一句话总结:Git 不是银弹,但它能帮你省下 80% 的版本管理烦恼。用好 clone、commit、push、pull、branch 这五个操作,你就已经超越了大多数嵌入式工程师。

下一章,咱们聊聊嵌入式项目的目录结构怎么组织。你会发现,好的结构比好的代码更重要。