2、版本控制基础:Git核心概念、Git工作流(GitFlow/GitHub Flow)、嵌入式项目中的.gitignore配置、标签与发布管理

版本控制这件事,说实话,是嵌入式CI/CD流水线的地基。没有它,后面的一切自动化都是空中楼阁。

我见过不少嵌入式团队,一开始觉得「我们项目小,用Git太麻烦」,结果代码越堆越乱,最后连哪个版本能跑都搞不清楚。嗯,这种坑我踩过不止一次。

这一章,咱们就把Git在嵌入式项目里的那些关键点捋清楚。不扯虚的,全是实战经验。

2.1 Git核心概念:你只需要记住这几个

Git的概念很多,但嵌入式开发中真正高频用到的,其实就这几个:

  • 工作区(Working Directory):你当前在改的代码文件。说白了,就是你IDE里看到的那堆东西。
  • 暂存区(Staging Area / Index):一个中间缓冲区。我习惯把它理解成「待提交清单」——你改完了,觉得这部分可以提交了,就先git add进去。
  • 本地仓库(Local Repository):你本机上的.git目录,存着所有历史版本。
  • 远程仓库(Remote Repository):服务器上的代码库,比如GitHub、GitLab或者自建的Gitea。
  • 提交(Commit):一次快照。记住,不是「增量备份」,是完整的项目状态快照。这也是Git快的原因。
  • 分支(Branch):一条独立的开发线。默认是master/main,但实际项目中我们很少直接在main上改。
我的小习惯:每次提交前,我都会用git diff --cached看一眼暂存区的内容。确认没有误加二进制文件或者临时文件。嵌入式项目里,.o、.hex、.bin这些文件一旦被提交,仓库体积会迅速膨胀。

2.2 Git工作流:GitFlow vs GitHub Flow

工作流这东西,没有绝对的好坏,只有合不合适。我两种都用过,说说我的感受。

2.2.1 GitFlow:适合固件版本迭代

GitFlow有两条常驻分支:main(稳定发布)和develop(日常开发)。再加上feature/*release/*hotfix/*这些临时分支。

我在做一款工业控制器的固件时,用的就是GitFlow。为什么呢?因为嵌入式产品有明确的版本号,比如v1.0、v1.1、v2.0。每个版本需要独立维护,hotfix也要基于特定版本打补丁。GitFlow的分支模型天然适合这种场景。

举个例子:

# 从develop拉取新功能分支
git checkout -b feature/add-can-bus develop

# 开发完成后合并回develop
git checkout develop
git merge --no-ff feature/add-can-bus

# 准备发布v1.2
git checkout -b release/v1.2 develop
# 在release分支上做最后的测试和bug修复
# 测试通过后,合并到main和develop
git checkout main
git merge --no-ff release/v1.2
git tag -a v1.2 -m "Release v1.2"
git checkout develop
git merge --no-ff release/v1.2
注意:GitFlow的--no-ff参数很重要。它强制创建合并提交,保留分支历史。我曾经因为没用这个参数,导致feature分支的提交历史全部丢失,排查问题时根本不知道哪个提交对应哪个功能。那叫一个痛苦。

2.2.2 GitHub Flow:适合快速迭代的嵌入式Linux项目

GitHub Flow就简单多了。只有一条main分支,所有开发都在feature/*分支上完成,然后通过Pull Request合并回main。

什么时候用这个?我个人的经验是:当你的嵌入式项目跑的是Linux系统,应用层代码频繁更新,而且有完善的自动化测试时,GitHub Flow更轻量。比如我们做的一个智能网关项目,每天可能有十几个PR,用GitFlow反而觉得累赘。

流程很简单:

# 从main拉取新分支
git checkout -b feature/update-mqtt-client main

# 开发、提交、推送
git add .
git commit -m "feat: update MQTT client to support TLS 1.3"
git push origin feature/update-mqtt-client

# 在GitHub上创建PR,经过Code Review和CI测试后合并到main

你想想看,GitHub Flow的核心就是「持续集成、持续部署」。每次合并到main,都意味着可以部署。这对于嵌入式Linux设备来说,非常实用。

2.3 嵌入式项目中的.gitignore配置

这个点,我敢说90%的嵌入式新手都栽过跟头。.gitignore配置不好,仓库里全是编译产物,clone一次项目要等半天。

嵌入式项目里,哪些文件不该被提交?我列一个实战清单:

类别 文件/目录 说明
编译产物 *.o, *.a, *.so, *.elf, *.hex, *.bin, *.map 这些是编译生成的,每次构建都会变
IDE/编辑器配置 .idea/, *.swp, .vscode/(部分情况) 每个人的IDE配置不同,不要污染仓库
构建系统生成 build/, cmake-build-*/ CMake、Make等生成的临时目录
工具链相关 *.srec, *.lst, *.d 链接器映射文件、依赖文件等
操作系统文件 .DS_Store, Thumbs.db macOS和Windows的垃圾文件
敏感信息 *.key, *.pem, .env, config.local 证书、密钥、本地配置,绝对不能提交

一个典型的嵌入式C/C++项目的.gitignore长这样:

# 编译产物
*.o
*.a
*.so
*.elf
*.hex
*.bin
*.map
*.srec
*.lst
*.d

# 构建目录
build/
cmake-build-*/
out/

# IDE
.idea/
.vscode/
*.swp
*.swo
*~

# 操作系统
.DS_Store
Thumbs.db

# 工具链
toolchain/
*.key
*.pem

# 本地配置
config.local
.env
避坑指南:我曾经犯过一个错误——在项目已经提交了编译产物之后,才想起来加.gitignore。结果那些文件已经被Git跟踪了,加了.gitignore也没用。正确的做法是:git rm --cached先把它们从暂存区移除,再提交。记住,--cached只删除Git的跟踪,不会删除你本地的文件。

2.4 标签与发布管理

标签(Tag)在嵌入式项目中,就是版本号的代名词。没有标签,你根本不知道哪个commit对应哪个发布版本。

我习惯用语义化版本号v主版本.次版本.修订号。比如v1.2.3。

  • 主版本:不兼容的API变更,或者硬件平台更换。比如从STM32F4换到STM32H7。
  • 次版本:向下兼容的功能新增。比如增加了一个新的传感器驱动。
  • 修订号:向下兼容的问题修复。比如修复了一个I2C通信的bug。

创建标签很简单:

# 创建轻量标签(只有版本号)
git tag v1.0.0

# 创建附注标签(推荐,包含作者、日期、说明)
git tag -a v1.0.0 -m "Release v1.0.0: 支持CAN总线通信"

# 推送到远程
git push origin v1.0.0

# 推送所有标签
git push origin --tags
我的建议:永远使用附注标签(-a参数)。轻量标签只是一个指针,没有元信息。当你几个月后回头看,根本想不起来这个版本改了啥。附注标签可以写详细的发布说明,比如「修复了看门狗超时导致系统重启的问题」——这种信息在排查问题时价值连城。

发布管理方面,我推荐的做法是:

  1. 每次发布前,在release/*分支上做最后的回归测试。
  2. 测试通过后,合并到main,打上标签。
  3. 在GitHub/GitLab上创建Release,上传固件文件(.hex、.bin等)。
  4. 写清楚Changelog,包括新增功能、修复的bug、已知问题。

举个例子,一个Release的Changelog可以这样写:

## v1.2.0 (2024-03-15)

### 新增
- 支持MQTT over TLS 1.3
- 新增看门狗超时自动恢复机制

### 修复
- 修复I2C总线在高速模式下数据丢失的问题
- 修复OTA升级过程中断后无法回滚的问题

### 已知问题
- 在极端低温(-40°C)下,RTC时钟偏差约2ppm

嗯,这样写,不管是自己回头看,还是团队其他人接手,都能一目了然。

好了,这一章的内容就这些。Git是CI/CD的基石,把这些基础打牢,后面搭建流水线的时候你会觉得特别顺手。下一章,咱们聊聊嵌入式项目的构建系统——Makefile、CMake,还有那些让人头疼的交叉编译工具链配置。