【保姆级】Git第二课:STM32日常开发实战——从“乱提交“到“原子化版本管理“(基础命令与规范详解)
适用人群:已完成Git环境配置的STM32开发者、从SVN转Git的嵌入式工程师、团队协作新手
阅读时间:20分钟 + 实践45分钟
前置要求:已完成Git环境配置(SSH、.gitignore、换行符设置)
目标:掌握日常开发80%的核心命令,建立适合硬件迭代的提交规范,学会用git blame追溯硬件Bug
📑 文章目录
- 回顾:三区模型速查
- 一、仓库生命周期:init vs clone 的抉择
- 1.1 从零创建:git init 与 .git 目录解剖
- 1.2 加入现有项目:git clone 与远程关联
- 1.3 嵌入式.gitignore进阶:不止要忽略.hex
- 二、文件状态流转:理解代码的"生命旅程"
- 2.1 四种状态详解
- 2.2 实战:CAN驱动开发的状态流转
- 2.3 撤销与恢复:add错了怎么办?
- 三、提交规范:让两个月后的自己看得懂
- 3.1 原子提交原则(Atomic Commit)
- 3.2 提交信息规范:嵌入式专用格式
- 3.3 多行提交与提交模板
- 四、历史追溯:嵌入式调试的时光机
- 4.1 日志查看:过滤与格式化
- 4.2 差异比较:定位寄存器修改
- 4.3 git blame:精确定位Bug引入点
- 五、阶段实战:完整的Feature开发流程
- 六、常见误区与团队协作禁忌
- 总结与下篇预告
回顾:三区模型速查
在深入命令前,快速回顾代码在哪里:
| 命令 | 代码位置 | 状态 | 是否永久保存 |
|---|---|---|---|
| 编辑文件后 | 工作区 (Working Directory) | Modified | ❌ 断电即失 |
git add后 | 暂存区 (Staging Area) | Staged | ❌ 仍在本地,未入历史 |
git commit后 | 本地仓库 (Repository) | Committed | ✅ 永久保存(只要不动.git目录) |
git push后 | 远程仓库 (Remote) | Shared | ✅ 团队协作基准 |
嵌入式特别提醒:.hex和.bin文件生成后位于工作区,但不应add到暂存区(除非使用Git LFS),否则仓库体积会指数级膨胀。
一、仓库生命周期:init vs clone 的抉择
1.1 从零创建:git init 与 .git 目录解剖
当你用STM32CubeMX生成新工程时:
cdD:\Projects\STM32F407_IMUgitinit发生了什么?
- 创建
.git隐藏目录(Linux/macOS下ls -la可见,Windows下需开启"显示隐藏文件") - 初始化
HEAD指针指向默认分支(旧版Git是master,新版可能是main)
.git目录结构(嵌入式开发者需知):
.git/ ├── HEAD# 当前分支指针(如ref: refs/heads/main)├── config# 仓库级配置(覆盖全局配置)├── description# 供GitWeb使用,通常不用管├── hooks/# 客户端钩子脚本(如pre-commit检查代码格式)├── index# 暂存区二进制表示(即Staging Area的实体)├── info/# 排除模式等├── objects/# Git对象数据库(blob文件内容、tree目录结构、commit提交信息)└── refs/# 引用存储(heads分支、tags标签、remotes远程追踪)关键理解:objects目录是Git的"时光机"核心。每次git commit,Git会:
- 将文件内容压缩存储为blob对象(如果内容未变则复用已有blob)
- 将目录结构存储为tree对象(指向blob和其他tree)
- 将提交信息(作者、时间、提交说明、父提交指针)存储为commit对象
嵌入式场景:理解这点很重要——如果你不小心提交了10MB的.elf调试文件,即使后续删除,它仍在objects中占用空间。这也是为什么必须配置好.gitignore再提交。
1.2 加入现有项目:git clone 与远程关联
当你入职拿到GitLab仓库地址时:
# HTTPS方式(需输入密码,适合临时下载)gitclone https://gitlab.company.com/firmware/imu-project.git# SSH方式(免密,推荐日常开发)gitclone git@gitlab.company.com:firmware/imu-project.git# 指定本地目录名(避免默认用仓库名)gitclone git@gitlab.company.com:firmware/imu-project.git IMU_v2_Localclone后的自动配置:
- 自动添加
origin远程别名,指向源地址 - 自动设置本地
main分支追踪远程origin/main(upstream) - 自动下载所有历史记录(
.git目录已包含完整仓库)
查看远程关联:
gitremote-v# 输出:# origin git@gitlab.company.com:firmware/imu-project.git (fetch)# origin git@gitlab.company.com:firmware/imu-project.git (push)# 查看追踪关系gitbranch-vv# * main 2a3b4c5 [origin/main] feat: add I2C initialization1.3 嵌入式.gitignore进阶:不止要忽略.hex
基础模板(承接第一阶段,增加细节):
# ===== 编译输出(绝对不能提交) ===== # Keil MDK *.axf # 调试文件,巨大 *.hex # 固件,应在CI中生成 *.bin # 二进制固件 *.map # 地图文件,体积大 *.lst # 列表文件 Objects/ # 编译目录 Listings/ # 列表目录 *.scvd # 调试视图配置 JLinkLog.txt # 调试日志,每次运行都变 # STM32CubeIDE Debug/ Release/ *.elf # ===== IDE配置(争议区域) ===== # 策略1:不提交(推荐大型团队,避免冲突) *.uvguix.* # Keil用户界面布局(每人显示器分辨率不同) *.uvoptx # 调试断点配置(个人调试习惯不同) # 策略2:提交关键配置(小型团队统一环境) # !startup_stm32g431.s # 如果修改了启动文件则强制追踪 # ===== 敏感信息(绝对不能提交) ===== config_wifi.h # 包含WiFi密码 secrets.h # API Key *.pem # 证书文件 # ===== 文档与中间文件 ===== *.pdf # 手册应放LFS或网盘,不应在源码仓 Datasheets/ # 硬件手册目录(过大) # ===== OS垃圾文件 ===== .DS_Store # macOS Thumbs.db # Windows desktop.ini技巧:全局.gitignore(避免每个项目都复制):
# 创建全局忽略文件gitconfig--globalcore.excludesfile ~/.gitignore_global# 编辑该文件,加入个人IDE配置(如VS Code的.vscode/settings.json)notepad ~/.gitignore_global二、文件状态流转:理解代码的"生命旅程"
2.1 四种状态详解
Git中文件只有四种状态,用git status查看:
$gitstatus On branch main Your branch is up todatewith'origin/main'.Untracked files:# 未追踪:Git从未管理过(如新创建的driver文件)(use"git add <file>..."to includeinwhat will be committed)Drivers/BMI323/bmi323.c Changes not stagedforcommit:# 已修改但未暂存:Git知道这文件,但你改了没add(use"git add <file>..."to update what will be committed)(use"git restore <file>..."to discard changesinworking directory)modified: Core/Src/main.c Changes to be committed:# 已暂存:已add,等待commit(use"git restore --staged <file>..."to unstage)(use"git restore <file>..."to discard changesinworking directory)new file: .gitignore modified: Core/Src/can_driver.c状态流转图:
Untracked ──git add──► Staged ──git commit──► Unmodified (Committed) ▲ │ └──git add──┘ │ │ 编辑文件 ▼ Modified │ git restore <file>────┘ (丢弃修改,回退到上次commit版本)2.2 实战:CAN驱动开发的状态流转
场景:你正在开发STM32G4的CAN FD驱动,同时处理三个文件。
# 初始状态:所有文件已提交,工作区干净gitstatus# nothing to commit, working tree clean# 1. 新增BMI323驱动文件(Untraged)touchDrivers/BMI323/bmi323.c Drivers/BMI323/bmi323.hgitstatus# Untracked files: bmi323.c, bmi323.h# 2. 修改CAN波特率计算(Modified)# 编辑 Core/Src/can_driver.c,修改波特率从500k改为1Mgitstatus# Changes not staged: modified: Core/Src/can_driver.c# 3. 选择性暂存(Staged)# 假设CAN修改已完成测试,但BMI323刚新建还没写内容gitaddCore/Src/can_driver.cgitstatus# Changes to be committed: modified: can_driver.c# Untracked files: bmi323.c(保持不变,不提交半成品)# 4. 提交gitcommit-m"feat(can): switch baudrate from 500k to 1Mbps for v2.1 hardware - Update FDCAN1 nominal prescaler from 4 to 2 - Tested with PCAN-View, bus load <30%"# 此时can_driver.c回到Unmodified状态,bmi323.c仍是Untracked# 5. 继续开发BMI323(新的Modified)# 编写bmi323.c后gitaddDrivers/BMI323/gitcommit-m"feat(imu): add BMI323 driver skeleton with SPI interface"2.3 撤销与恢复:add错了怎么办?
场景1:你git add了一个巨大的.map文件(不该提交的),但还没commit。
# 查看暂存区(即将提交的内容)gitdiff--staged|head-20# 发现错误!从暂存区移除(保留工作区文件)gitrestore--stagedDebug/project.map# 或旧版Git:git reset HEAD Debug/project.map# 现在文件回到Untracked或Modified状态,不再被提交gitstatus# Untracked files: Debug/project.map(如果在工作区新建)# 或 Changes not staged(如果是修改已有文件)场景2:你修改了main.c但想放弃所有更改(回退到上次commit版本)。
# 危险操作!工作区修改会永久丢失!gitrestore Core/Src/main.c# 更安全的方式:先stash保存,确认不需要再dropgitstash push Core/Src/main.c-m"WIP: temp printf debug"# 之后如果确实不需要gitstash drop stash@{0}场景3:你删除了一个文件(无论是rm命令还是在IDE里删),想恢复。
# 情况A:文件已提交过,误删了工作区文件rmCore/Src/tmp.cgitstatus# 显示 deleted: Core/Src/tmp.cgitrestore Core/Src/tmp.c# 从仓库恢复# 情况B:文件从未提交过(Untracked),误删了# 悲剧:Git帮不了你,去回收站或备份找吧# 教训:重要新文件一旦创建就立即git add(即使不commit)三、提交规范:让两个月后的自己看得懂
3.1 原子提交原则(Atomic Commit)
反例(糟糕的提交):
gitcommit-m"update"# 或gitcommit-m"fix can and add imu and update hal lib and modify gpio"问题:
- 两周后回退时,无法单独回退"CAN修复"而不影响"IMU添加"
- Code Review时,Reviewer无法区分哪些变更属于哪个功能
git bisect定位Bug时,一次提交引入多个变更,二分查找失效
正例(原子提交):
# 提交1:修复CAN时序(独立可回退)gitaddCore/Src/can_driver.cgitcommit-m"fix(can): correct sample point calculation for 1Mbps - Change BS1 from 13 to 12 tq - Change BS2 from 2 to 3 tq - Fixes arbitration loss issue on v2.1 PCB"# 提交2:添加IMU驱动(独立功能)gitaddDrivers/BMI323/gitcommit-m"feat(imu): add BMI323 initialization sequence - Add soft-reset with 10ms delay - Verify chip ID 0x43 before init"# 提交3:升级HAL库(基础设施)gitaddDrivers/STM32G4xx_HAL_Driver/gitcommit-m"chore(hal): upgrade HAL from v1.2.0 to v1.3.0 - Fixes I2C deadlock bug when NACK received"3.2 提交信息规范:嵌入式专用格式
推荐采用约定式提交(Conventional Commits)+硬件版本标记:
<type>(<scope>): <subject> <BLANK LINE> <body> <BLANK LINE> <footer>Type(必须):
feat: 新功能(新传感器驱动、新通信协议)fix: Bug修复(时序错误、计算修正)docs: 文档(README更新、注释完善)style: 格式(缩进、分号,不影响功能)refactor: 重构(函数拆分、变量重命名,不增功能不修Bug)perf: 性能优化(DMA替代轮询、算法优化)test: 测试(单元测试、硬件在环测试)chore: 构建/工具(Makefile修改、CI配置、库升级)
Scope(推荐):模块名,如can、imu、uart、hal、build
Subject(必须):简短描述(<50字符),用祈使语气(“Fix"而非"Fixed"或"Fixes”)
Body(可选):详细说明,解释为什么(Why)而非做了什么(What,代码已说明)。可包含:
- 硬件版本关联:
[HW-v2.1] - 问题现象:
Fixes hardfault when CAN bus off - 测试方法:
Tested with 10 nodes @ 500kbps for 24h
Footer(可选):
BREAKING CHANGE:破坏性变更(如修改API导致旧硬件不兼容)Closes #123关联Issue
嵌入式专用示例:
# 修复硬件v2.1的CAN仲裁丢失问题gitcommit-m"fix(can): [HW-v2.1] correct sample point to 87.5% - v2.1 PCB uses TJA1051T transceiver with higher delay - Change BS1:12, BS2:2 (was BS1:13, BS2:2 causing 92.3% sample point) - Passes ISO 11898-2 compliance test - Tested with PCAN stress tool, 100% bus load, 0 errors Closes #45"# 添加新功能,但仅在v2.2硬件支持(BOM变更)gitcommit-m"feat(power): [HW-v2.2] add battery gauge BQ40Z50 support - Requires new I2C GPIO pins on v2.2 schematic - Backward compatible: v2.1 will skip init if ID read fails - Add compile flag HAS_BATTERY_GAUGE for conditional build BREAKING CHANGE: v2.1 firmware.bin works on v2.2 HW, but battery status will show N/A (acceptable for production)"3.3 多行提交与提交模板
命令行输入多行(Git Bash/Terminal):
gitcommit-m"feat(imu): add temperature compensation"-m"- Read BMI323 internal temp sensor"-m"- Apply linear correction factor from calibration data"-m"[CAL-2024-04-15]"使用文本编辑器(推荐长提交):
# 配置默认编辑器(Windows推荐VS Code或Notepad++)gitconfig--globalcore.editor"code --wait"# VS Code# 或gitconfig--globalcore.editor"notepad++ -multiInst -nosession"# 提交时自动打开编辑器gitcommit# 在打开的文本中写入:feat(imu):addtemperature compensation - Read BMI323 internal temp sensor every 100ms - Apply linear correction factor from calibration data storedinFlash - Correction formula: angle_true=angle_raw +(temp -25)*0.0034- Validatedintemperature chamber(-40°C to +85°C)[CAL-2024-04-15]Refs: calibration_sheet_v3.xlsx提交模板(团队统一):
在项目根目录创建.gitmessage文件:
# <type>(<scope>): <subject> # [HW-vX.X] 硬件版本标记(如适用) # |<---- 使用最多50个字符 ---->| # 详细说明做了什么以及为什么 # |<---- 尝试每行限制72个字符 ---->| # 关联Issue或文档 Refs: Test-method: Hardware-verified: [ ] v2.1 [ ] v2.2 # --- 提交类型 --- # feat: 新功能 # fix: Bug修复 # docs: 文档 # style: 格式 # refactor: 重构 # perf: 性能 # test: 测试 # chore: 工具/构建启用模板:
gitconfig--localcommit.template .gitmessage四、历史追溯:嵌入式调试的时光机
4.1 日志查看:过滤与格式化
基础日志:
gitlog# 默认输出:commit hash, Author, Date, 提交信息(多行)# 紧凑视图(推荐日常查看)gitlog--oneline# 2a3b4c5 feat(can): add 1Mbps support# 1d2e3f4 fix(imu): correct gyro scaling factor# 9c8d7e6 init: STM32G431 base project# 图形化分支图(理解合并历史神器)gitlog--oneline--graph--all# * 2a3b4c5 (HEAD -> main, origin/main) feat(can): add 1Mbps support# * 1d2e3f4 fix(imu): correct gyro scaling factor# |\# | * 4f5g6h7 (feature/spi-optimize) perf(spi): use DMA for BMI323 read# |/# * 9c8d7e6 init: STM32G431 base project条件过滤(排查Bug时有用):
# 只看某个文件的修改历史(追溯寄存器配置变更)gitlog--oneline-- Core/Src/can_driver.c# 只看特定作者的提交(找队友改了什么)gitlog--oneline--author="Li Si"# 查看某段时间的提交(找版本发布前的修改)gitlog--oneline--since="2024-04-01"--until="2024-04-15"# 查看包含特定内容的提交(找哪个提交引入了某个变量)gitlog--oneline-S"FDCAN1"# 查找所有涉及FDCAN1字符串变更的提交4.2 差异比较:定位寄存器修改
场景:客户反馈v1.2固件CAN通信不稳定,v1.1正常,你需要对比CAN初始化代码的差异。
# 比较工作区与暂存区(尚未add的修改)gitdiffCore/Src/can_driver.c# 比较暂存区与上次提交(即将提交的变更)gitdiff--stagedCore/Src/can_driver.c# 比较两个提交之间的差异(核心调试技能)gitdiffv1.1..v1.2 -- Core/Src/can_driver.c# 或gitdiff9c8d7e6..2a3b4c5 -- Core/Src/can_driver.c# 比较特定文件的版本(查看某次提交时的完整文件)gitshow v1.1:Core/Src/can_driver.c>can_v1.1.cgitshow v1.2:Core/Src/can_driver.c>can_v1.2.c# 然后用Beyond Compare等工具对比diff输出解读(嵌入式工程师必备):
diff --git a/Core/Src/can_driver.c b/Core/Src/can_driver.c index 3f4a5b6..7c8d9e0 100644 --- a/Core/Src/can_driver.c +++ b/Core/Src/can_driver.c @@ -45,7 +45,7 @@ void CAN_Init(void) hfdcan1.Instance = FDCAN1; hfdcan1.Init.NominalPrescaler = 4; // 旧值 - hfdcan1.Init.NominalPrescaler = 2; // 新值:v1.2改为2,导致波特率加倍! + hfdcan1.Init.NominalPrescaler = 4; // 恢复为4,修复通信问题 hfdcan1.Init.NominalTimeSeg1 = 13; hfdcan1.Init.NominalTimeSeg2 = 2;统计变更(Code Review前快速了解):
gitdiff--statv1.1..v1.2# Core/Src/can_driver.c | 5 +++--# Core/Src/main.c | 10 +++++++++-# 2 files changed, 11 insertions(+), 4 deletions(-)4.3 git blame:精确定位Bug引入点
终极调试神器:查看每一行代码最后是由哪个提交修改的。
# 查看CAN初始化函数的每一行归属gitblame-L40,60Core/Src/can_driver.c# 输出格式:# commit_hash (Author Date LineNumber) content9c8d7e6(Zhang San2024-04-0140)void CAN_Init(void){9c8d7e6(Zhang San2024-04-0141)FDCAN_HandleTypeDef hfdcan1;2a3b4c5(Li Si2024-04-1042)// Fixforv2.1 hardware 2a3b4c5(Li Si2024-04-1043)hfdcan1.Init.NominalPrescaler=2;//<- 就是这行! 9c8d7e6(Zhang San2024-04-0144)hfdcan1.Init.NominalTimeSeg1=13;场景:发现hfdcan1.Init.NominalPrescaler = 2;这行导致了CAN错误。
git blame显示这是由2a3b4c5提交在4月10日由Li Si修改的- 立即查看该提交的详情:
git show 2a3b4c5 - 发现提交信息写着"[HW-v2.1] correct sample point…",但当前硬件是v1.0,不适用此修改!
忽略空白比较(格式化代码时不破坏blame信息):
gitblame-wCore/Src/can_driver.c# 忽略空格变更gitblame-MCore/Src/can_driver.c# 检测移动/复制行五、阶段实战:完整的Feature开发流程
场景:需要在现有STM32G431 IMU项目中添加BMI323温度补偿功能。
步骤1:查看当前状态
cdD:\Projects\STM32_IMU_v1gitstatus# 确认工作区干净gitlog--oneline-3# 查看最新3次提交# 2a3b4c5 (HEAD -> main, origin/main) feat(can): add 1Mbps support# 1d2e3f4 fix(imu): correct gyro scaling factor# 9c8d7e6 init: STM32G431 base project步骤2:创建特性分支(防止干扰main分支)
gitcheckout-bfeature/temp-compensation# 或新版Git:git switch -c feature/temp-compensationgitbranch-vv# * feature/temp-compensation 2a3b4c5 feat(can): add 1Mbps support# main 2a3b4c5 [origin/main] feat(can): add 1Mbps support步骤3:开发并原子提交
// 修改1:添加温度读取函数(Drivers/BMI323/bmi323.c)int16_tBMI323_ReadTemp(void){// ... SPI读取寄存器0x13returntemp_raw;}gitaddDrivers/BMI323/bmi323.cgitcommit-m"feat(bmi323): add temperature sensor read function - Read register 0x13 (TEMP_DATA) - Convert to 0.125°C/LSB format - Valid range: -40°C to +85°C"// 修改2:应用补偿算法(Core/Src/imu_process.c)floatApplyTempCompensation(floatraw_angle,int16_ttemp){returnraw_angle+(temp-25)*TEMP_COEFF;}gitaddCore/Src/imu_process.cgitcommit-m"feat(imu): add temperature compensation algorithm - Linear correction based on calibration data - Update fusion filter to use compensated gyro data - [CAL-2024-04-15] coefficients from chamber test"步骤4:查看开发历史
gitlog--oneline--graph--all# * 5f6g7h8 (HEAD -> feature/temp-compensation) feat(imu): add temperature compensation algorithm# * 6h7j8k9 feat(bmi323): add temperature sensor read function# | * 2a3b4c5 (origin/main, main) feat(can): add 1Mbps support# |/# * 1d2e3f4 fix(imu): correct gyro scaling factor步骤5:合并到main分支(下一课详细讲解merge/rebase)
gitcheckout maingitmerge feature/temp-compensationgitpush origin main六、常见误区与团队协作禁忌
| 误区 | 后果 | 正确做法 |
|---|---|---|
用git commit -a(或-am)一键提交 | 把不该提交的临时文件、调试printf、.map都提交了 | 始终git status检查,选择性git add,明确知道在提交什么 |
| 提交信息写"fix bug" | 两个月后不知道修了哪个Bug,无法cherry-pick到维护分支 | 必须引用Issue ID或描述现象,如fix(can): resolve bus-off err when TX queue full (issue #123) |
| 在main分支直接开发新功能 | 新功能开发到一半,产线紧急Bug需要修复,代码混乱 | 始终开feature分支,main保持随时可发布状态 |
用git push -f(强推)解决问题 | 覆盖远程历史,队友的本地仓库与远程冲突,数据丢失 | 除非绝对确定只有自己使用分支(如feature/temp),否则永不强推 |
| 提交二进制文件(.hex/.bin)到源码仓 | 仓库体积几天内从10MB膨胀到1GB,clone耗时半小时 | 使用Git LFS,或在CI/CD中生成hex作为Artifacts,源码仓只存代码 |
| 从不pull直接push | 远程已有新提交,push被拒绝,强制合并产生奇怪冲突 | 养成习惯:git pull --rebase(或fetch + rebase)后再push |
总结与下篇预告
今天我们掌握了日常开发的80%核心命令:
- 仓库管理:
init创建,clone加入,理解.git目录结构避免"仓库污染" - 状态流转:清晰掌握Untracked→Modified→Staged→Committed的生命周期,善用
restore挽回失误 - 提交规范:遵循原子提交原则,使用约定式提交格式(
type(scope): subject),硬件版本标记[HW-vX.X]让历史可追溯 - 历史追溯:
log --oneline查看历史,diff定位差异,blame精准定位Bug引入点(嵌入式调试神器)
自检问题:
- 如果你
git add后又修改了同一文件,此时git diff和git diff --staged显示的内容有何不同?如何只提交暂存区的旧版本,保留工作区的新修改到下次提交? git blame显示某行代码由abc1234提交修改,但你想知道这行最初是谁写的(可能经过多人修改),应该加什么参数?
下篇预告:《【保姆级】Git第三课:分支管理与硬件版本并行开发——如何用Gitflow管理"量产维护"与"新传感器实验"》
将涵盖:
- 分支本质:HEAD指针、轻量级分支 vs 重量级分支
- Gitflow工作流:
main(量产固件)、develop(集成测试)、feature/*(新硬件实验)、hotfix/*(产线紧急修复) - 合并策略:
merge(保留历史) vsrebase(整洁线性) - 冲突解决:当两人修改了同一寄存器配置位时的解决流程
- 储藏现场:
git stash紧急切换上下文修复产线Bug
思考题:在你的硬件项目中,如果v1.0硬件正在量产(需要维护),同时v2.0硬件在研发(需要新驱动),你会如何用Git分支管理这两条线?欢迎在评论区画出你的分支图。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
分类:嵌入式开发 > 版本控制 > Git
标签:Git, STM32, 原子提交, git blame, 提交规范, 嵌入式调试, 版本追溯
---