4. 版本控制进阶:Git Flow工作流在嵌入式项目中的应用

嵌入式开发中,版本控制从来不是「把代码存起来」那么简单。我见过太多团队,用 Git 只是当个网盘用,结果一到版本发布就手忙脚乱。说实话,嵌入式项目对版本管理的要求,比纯软件项目要苛刻得多——你不仅要管代码,还要管硬件配置、工具链版本、甚至芯片的勘误表。

这一章,我重点聊聊三个实战中绕不开的话题:Git Flow 怎么在嵌入式项目里落地、怎么用 Submodule 管好第三方库、以及 Git LFS 怎么处理那些烦人的二进制文件。

4.1 Git Flow 工作流:嵌入式项目怎么用

Git Flow 本身是个经典的分支模型,但嵌入式项目有它的特殊性。比如,硬件团队和软件团队的节奏往往不同步,固件发布和硬件改版经常纠缠在一起。

我个人习惯这样调整 Git Flow:

  • master 分支:只放经过硬件验证的固件版本。注意,不是「代码 review 通过」就行,必须是在目标硬件上跑过完整测试的。
  • develop 分支:日常开发的主战场。所有 feature 分支从这里拉出去,合回来之前必须通过单元测试和静态分析。
  • feature 分支:每个功能模块独立开发。我建议命名带上模块名,比如 feature/ble_stack_optimize,方便追溯。
  • release 分支:从 develop 拉出来,专门做回归测试和 bug 修复。嵌入式项目里,这个分支通常要跑一轮硬件在环测试(HIL)。
  • hotfix 分支:线上固件出了紧急 bug,直接从 master 拉出来修。修完要同时合并回 master 和 develop。

关键点:嵌入式项目的 release 分支,一定要包含硬件版本号。比如 release/v2.1.0_hw_rev_b。这样你拿到一个固件,立刻知道它对应哪版硬件。

举个例子。我之前做过一个车载项目,MCU 固件和 Linux 上层应用混在一起。硬件团队突然改了 PCB 走线,导致某个 GPIO 的映射变了。如果当时没有在 release 分支里标注硬件版本,后果就是——固件刷上去,车机直接黑屏。

我的小技巧:在 commit message 里加上硬件变更的标签,比如 [HW_REV_B] fix gpio mapping for new pcb。这样 grep 一下就能定位所有跟硬件改版相关的提交。

4.2 Git Submodule:管好第三方库

嵌入式项目里,第三方库是个大麻烦。FreeRTOS、LwIP、FatFS……这些库各有各的更新节奏。你要是直接把源码拷进项目里,后面想升级就等着哭吧。

Git Submodule 就是用来解决这个问题的。它让你在项目里引用另一个 Git 仓库的某个特定版本。

基本用法很简单:

# 添加一个 submodule
git submodule add https://github.com/FreeRTOS/FreeRTOS.git libs/FreeRTOS

# 初始化并拉取子模块
git submodule update --init --recursive

# 更新子模块到最新提交
git submodule update --remote

但这里有个坑,我踩过不止一次——子模块的版本锁定。嵌入式项目对稳定性要求极高,你不能随便更新第三方库。所以,每次更新子模块后,一定要提交父项目的变更。

我曾经犯过的错:有次为了修复一个 bug,我直接改了子模块里的源码。结果下次更新子模块时,所有修改都被覆盖了。正确的做法是:fork 第三方库,在自己的 fork 里改,然后让父项目引用你的 fork。

我建议这样管理:

  • 每个第三方库都 fork 到自己公司的 Git 仓库下
  • 父项目只引用 fork 后的仓库
  • 每次更新子模块,都要在父项目里生成一个新的 commit
  • commit message 里写明更新原因,比如 chore: update FreeRTOS to v10.4.3 for task notification fix

另外,CI/CD 流水线里一定要加上 git submodule update --init --recursive 这一步。否则,新同事拉下代码一编译,发现一堆头文件找不到,那体验可就太糟了。

4.3 Git LFS:处理二进制文件的利器

嵌入式项目里,二进制文件多得让人头疼。编译好的固件、芯片的 datasheet、硬件原理图、甚至一些测试用的音频/图片文件……这些东西扔进 Git 仓库,仓库体积会迅速膨胀。

Git LFS(Large File Storage)就是干这个的。它把大文件替换成指针文件,真正的文件内容存在远端服务器上。

配置起来很简单:

# 安装 LFS
git lfs install

# 跟踪特定类型的文件
git lfs track "*.bin"
git lfs track "*.hex"
git lfs track "*.pdf"
git lfs track "*.elf"

# 确保 .gitattributes 被提交
git add .gitattributes

嗯,这里要注意:.gitattributes 文件一定要提交到仓库里。否则别人拉代码时,LFS 不会自动生效。

我遇到过最离谱的情况是,有个同事把 200MB 的编译中间文件(.o 文件)也提交了。仓库直接飙到 2GB,每次 clone 都要半小时。后来我们用 LFS 只跟踪最终固件(.hex.bin),中间文件全部加到 .gitignore 里。

我的建议:只跟踪那些「必须版本化」的二进制文件。比如:

  • 发布的固件镜像
  • 硬件原理图和 PCB 文件(如果团队用 Git 管理)
  • 测试用的标准输入数据

至于编译中间文件、临时生成的文件,一律忽略。

还有一个容易被忽略的点——LFS 的存储配额。GitHub 免费版只有 1GB 的 LFS 存储,超出要收费。公司项目建议自建 Git 服务器,或者用 GitLab 的免费 LFS 额度。

4.4 实战中的避坑指南

说了这么多,最后总结几条我这些年踩过的坑:

  • 分支命名要规范:我见过有人用 fix_bug 这种名字,过两周自己都忘了修的是啥。建议统一格式:类型/模块_描述,比如 fix/uart_baudrate_error
  • 子模块不要直接改源码:前面说过了,fork 才是正道。
  • LFS 要提前配置:项目一开始就配好 LFS,别等仓库大了再迁移。迁移过程很痛苦,我经历过一次,再也不想有第二次。
  • CI/CD 里检查子模块状态:在流水线里加一步 git submodule status,确保所有子模块都指向正确的 commit。

版本控制这件事,说白了就是「让每个人都能安心地改代码,同时知道改了什么、为什么改、能不能回退」。嵌入式项目里,这一点尤其重要——毕竟,你改的代码是要跑在硬件上的,出了问题可不是重启一下就能解决的。

下一章,我会聊聊怎么把这些版本控制的实践,真正融入到 CI/CD 流水线里。到时候你会发现,前面这些基础打得越扎实,后面自动化就越顺畅。