Files
STM32F4-Base/docs/pre-commit使用说明.md
T

13 KiB
Raw Blame History

pre-commit 自动化代码审查 — 部署与使用说明

生效前提:本机已运行过 pre-commit install(钩子写入 .git/hooks/)。 新机器/新开发者进入仓库后需执行一次 pre-commit install,详见「新机器启用」。


1. 部署架构

git commit
  └─ pre-commit(.git/hooks/pre-commit)
       ├─ trailing-whitespace      自动修复行尾空格
       ├─ end-of-file-fixer        自动修复文件尾换行
       ├─ check-added-large-files  阻止 >1MB 大文件
       ├─ check-merge-conflict     阻止含冲突标记的文件
       ├─ check-yaml               校验 YAML 语法
       ├─ cppcheck                 C 静态分析(自有代码)
       └─ no-mojibake             阻止 U+FFFD 乱码文件
  └─ commit-msg(.git/hooks/commit-msg)
       └─ gitlint                  校验 commit message 格式

两阶段钩子:pre-commit 阶段拦截代码问题,commit-msg 阶段拦截格式问题。任一失败 → commit 中止。

依赖清单

组件 安装方式 用途
pre-commit 4.6.2 uv tool install pre-commit 钩子框架
cppcheck 2.21.0 winget install cppcheck C 静态分析引擎
scripts/check_mojibake.py 随仓库提交 U+FFFD 乱码检测(纯 Python,无依赖)

PATH 要求:cppcheck 安装后需在系统 PATH 中(C:\Program Files\Cppcheck)。winget 自动写入 User PATH,但当前终端不会立即生效,需重开终端。


2. 各钩子规则详解

2.1 trailing-whitespace — 行尾空格清理

项 说明
触发条件 文件任意行末尾含空格或 Tab
处理方式 自动修复(直接改写文件,删除行尾空白)
影响范围 所有被 git 跟踪的文件
典型报错 Trim Trailing Whitespace → Fixing path/to/file.c
修复方式 无需手动修复,钩子已自动改写;重新 git add 即可

注意:首次对全库运行时,可能批量修改大量文件的行尾空格。这是正常行为,不会改变代码逻辑。

2.2 end-of-file-fixer — 文件尾换行

项 说明
触发条件 文件最后一行不是换行符(\n)
处理方式 自动修复(在文件尾部追加换行)
典型报错 Fix End of Files → Fixing path/to/file.c
修复方式 无需手动修复,钩子已自动改写;重新 git add 即可

2.3 check-added-large-files — 大文件拦截

项 说明
触发条件 单个文件 > 1MB(默认阈值)
处理方式 阻止提交(需手动修复)
典型报错 ilerge file added: path/to/file.bin
修复方式 确认文件是否应提交;若为构建产物则加入 .gitignore,若为必要大文件用 --no-verify 跳过

2.4 check-merge-conflict — 冲突标记拦截

项 说明
触发条件 文件含 <<<<<<<、=======、>>>>>>> 冲突标记
处理方式 阻止提交
修复方式 手动解决冲突后重新提交

2.5 check-yaml — YAML 语法校验

项 说明
触发条件 .yaml / .yml 文件语法错误
处理方式 阻止提交
检查对象 主要校验 .pre-commit-config.yaml 本身
修复方式 修正 YAML 缩进或语法后重新提交

2.6 cppcheck — C 静态分析

cppcheck 是开源 C/C++ 静态分析工具,不运行代码,通过语法分析和数据流推理发现潜在缺陷。

工作原理:逐个读取 C 文件 → 分析语法/数据流/控制流 → 发现问题则报错。

本项目开启的检查类别

类别 含义 典型问题
warning 通用警告 变量未使用、除零风险、空指针解引用、unreachable code
performance 性能问题 不必要的值拷贝、字符串重复查找、低效的函数调用
portability 可移植性问题 不同平台行为不一致的代码、隐式类型转换
style 代码风格 变量作用域过大、冗余赋值、恒假条件、switch 缺 default

不开启的常见类别

类别 为什么不开
error 严重错误(数组越界、内存泄漏),cppcheck 默认已包含,不需要显式加
information 信息类(未解析的头文件等),无实际价值

配置参数说明

--std=c99                    # 按 C99 标准检查(Keil 工程常用)
--error-exitcode=1           # 发现问题返回非零退出码(pre-commit 用来拦截提交)
--quiet                      # 只报问题,不显示扫描进度

# --- 必须抑制(误报/不可修改) ---
--suppress=missingInclude    # HAL/CMSIS 路径在 Keil 工程配置里,cppcheck 不知道
--suppress=invalidPrintfArgType_sint  # printf 的 %d 传 uint32_t —— 实际安全
--suppress=invalidPrintfArgType_uint  # printf 的 %u 传 int —— 实际安全
--suppress=constParameterPointer     # HAL 回调签名固定(如 SPI_HandleTypeDef*),不能加 const
--suppress=constParameterCallback    # 回调函数指针类型限制,不能改签名
--suppress=unusedFunction            # 第三方库回调/弱符号函数被误报为未使用
--suppress=unusedStructMember        # HAL 寄存器位域成员未显式使用
--suppress=noExplicitConstructor     # C++ 兼容,不影响纯 C
--suppress=useStlAlgorithm           # C++ STL 相关,不影响纯 C

# --- 暂时抑制(噪音多,后续可移除) ---
--suppress=constVariablePointer      # 局部指针加 const 是好习惯,但当前改动量大

# --- 头文件路径 ---
-I Inc -I App -I Drivers/BSP
-I Drivers/STM32F4xx_HAL_Driver/Inc -I Drivers/CMSIS/Include
-I Middlewares/Third_Party/FreeRTOS/Source/include

抑制规则说明

抑制规则 原因 是否永久
constParameterPointer HAL 回调签名由驱动框架定义,用户不能改 永久
constParameterCallback 同上,回调函数指针类型受限 永久
constVariablePointer 局部指针加 const 是好习惯,但当前代码量大、改动多 暂时
unusedFunction CubeMX 生成的弱符号、第三方库回调被误报 永久
unusedStructMember HAL 寄存器位域按位操作,cppcheck 不理解 永久
redundantAssignment HAL 清标志宏(如 __HAL_SPI_CLEAR_OVRFLAG)读寄存器覆盖旧值,cppcheck 不理解"读即目的" 永久

扫描范围

通过 types: [c] + exclude 控制:

检查:App/、Src/、Inc/、Drivers/BSP/ 下的 .c 文件

不检查(第三方库,问题不是我们的):

Drivers/CMSIS/              # ARM CMSIS
Drivers/STM32F4xx_HAL_Driver/  # ST HAL
Drivers/BSP/UI/             # LVGL UI 层
Drivers/BSP/NET/lftpd/      # LwIP FTP
Drivers/BSP/dhara/          # NAND FTL
Lib/                        # LVGL 库
Middlewares/                # FreeRTOS
MDK-ARM/                    # Keil 工程文件
test/                       # 测试代码

style 检查发现的有价值问题(待修复)

开启 style 后,以下问题值得修复(符合嵌入式C代码规范):

规则 问题 涉及文件 价值
knownConditionTrueFalse 条件恒假(uint16_t > 65535) net_socket.c 高:潜在 Bug
compareValueOutOfTypeRangeError 同上 net_socket.c 高:潜在 Bug
redundantAssignment 重复赋值覆盖旧值 ch395f.c 高:Bug 嫌疑
unreadVariable 变量赋值后未读取 gd5f2gq5ue.c, time_sync.c, ringbuf.c 中:冗余代码
variableScope 变量作用域可缩小 sys_clock.c, net_socket.c 等 低:规范6.1,可逐步修

日常速度

  • 增量扫描(git commit 时):只检查 staged 文件,通常 <1 秒
  • 全库扫描(pre-commit run --all-files):约30秒,仅在配置变更后使用

2.7 no-mojibake — U+FFFD 乱码拦截

项 说明
触发条件 C 文件含 EF BF BD(U+FFFD 替换符,表示不可逆的编码损坏)
处理方式 阻止提交(不可逆,必须修复)
检测原理 scripts/check_mojibake.py 以二进制读取文件,检测 0xEF 0xBF 0xBD 字节序列
典型报错 U+FFFD replacement chars found (encoding corrupted, irreversible!): path/to/file.c
修复方式 从 git 历史中找到最后一个干净版本,手动恢复损坏区域

⚠️ 编码铁律:U+FFFD 是不可逆损坏(无法从替换符反推原始中文字符)。若发现新文件出现 U+FFFD,必须排查写入方式(禁用 Get-Content / Set-Content 重写中文文件)。

2.8 gitlint — Commit Message 格式

项 说明
触发条件 commit message 标题超 100 字符 / 含 body-is-missing 警告
处理方式 阻止提交(commit-msg 阶段)
当前规则 宽松:不强制 Conventional Commits,兼容中文标题
典型报错 T1 Title exceeds max length (159>100)
修复方式 精简 commit message 标题到 100 字符以内,或拆分为多行(标题+空行+body)

3. 日常工作流

3.1 正常提交(秒级完成)

git add .
git commit -m "修复网络超时重连逻辑"

全部钩子自动运行,只检查本次 staged 的文件(增量扫描)。通常 <5 秒完成。

3.2 钩子失败后的处理

# 提交被拦截,查看报错信息
# 修复后重新 add + commit
git add .
git commit -m "修复网络超时重连逻辑"

常见失败场景与处理:

钩子 失败表现 处理步骤
trailing-whitespace Fixing xxx.c → 需要重新 git add 钩子已自动修复文件,git add 后重新提交
end-of-file-fixer 同上 同上
cppcheck 报 warning/performance/portability 根据报告修复代码,或在 args 中添加 --suppress=规则名
no-mojibake U+FFFD replacement chars found 从 git 历史恢复干净版本(不可逆!)
gitlint Title exceeds max length 精简 commit message 标题

3.3 全库扫描(配置变更后)

pre-commit run --all-files

仅在以下场景使用:

  • 首次 pre-commit install 后确认基线
  • 修改 .pre-commit-config.yaml 后验证
  • cppcheck 新增 suppress 规则后验证

日常提交不需要跑 --all-files,pre-commit 会自动只检查 staged 文件。

3.4 手动运行单个钩子

pre-commit run cppcheck --all-files       # 只跑 cppcheck
pre-commit run trailing-whitespace --files path/to/file.c  # 只检查单个文件
pre-commit run no-mojibake --files path/to/file.c
pre-commit run gitlint --hook-stage commit-msg --commit-msg-filename .git/COMMIT_EDITMSG

3.5 临时跳过钩子(紧急情况)

git commit -m "紧急修复" --no-verify
# 或跳过特定钩子
SKIP=cppcheck git commit -m "紧急修复"

⚠️ 不推荐,仅用于紧急修复。跳过后的代码应在下一次提交中补上检查。


4. 新机器启用

# 1. 安装 pre-commit(需要 uv)
uv tool install pre-commit

# 2. 安装 cppcheck(需要 winget)
winget install cppcheck
# 重开终端使 PATH 生效

# 3. 进入仓库,安装钩子
cd D:\Code\DTU Code\STM32F4-Base
pre-commit install

# 4. 首次全库扫描(确认基线)
pre-commit run --all-files

5. 已知问题与注意事项

5.1 cppcheck 找不到

现象:Executable cppcheck not found

原因:cppcheck 安装后 PATH 更新在注册表,已运行的终端不会刷新。

解决:重开终端即可。若需在当前会话临时修复:

$env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Environment]::GetEnvironmentVariable("Path","User")

5.4 clang-format 未启用

原因:全库 222 个 .c/.h 文件大概率不满足 clang-format 默认格式,启用后会批量重写。

计划:Route B —— 全库统一格式化后形成独立基线 commit,再启用 clang-format 钩子。启用时取消 .pre-commit-config.yaml 中的注释即可。


6. 文件清单

文件 位置 说明
.pre-commit-config.yaml 仓库根 钩子配置(7 个钩子 + 1 个预留)
.gitlint 仓库根 gitlint 规则(宽松,兼容中文)
scripts/check_mojibake.py scripts/ U+FFFD 检测脚本
docs/自动化代码审查实施.md docs/ 实施规划文档(Route A/B 设计)
docs/pre-commit使用说明.md 本文档 部署与使用说明

7. 快速参考卡片

提交代码:        git add . && git commit -m "描述"
全库扫描:        pre-commit run --all-files
单钩子扫描:      pre-commit run <hook-id> --all-files
扫描单个文件:    pre-commit run <hook-id> --files <path>
跳过钩子:        git commit --no-verify  (紧急)
查看已安装钩子:  ls .git/hooks/
卸载钩子:        pre-commit uninstall
更新钩子版本:    pre-commit autoupdate