check_coding_standard.py 新增 9 条机械检查(S14 并入 S6): - S10 (3.3.1) 函数名全小写(HAL_ 厂商回调豁免) - S11 (2.3.5) 禁止相对/绝对路径 include - S12 (7.6) 禁止可变参数函数 - S13 (7.7) 禁止递归函数 - S15 (6.1.4) 文件作用域指针须初始化为 NULL - S16 (3.2.2) 非 static 全局变量须 g_ 前缀 - S17 (2.2) 非 static 函数须在配对头文件声明 - S18 (6.3.1) 禁止 Yoda 条件(常量在右) - S19 (4.4.2) 函数定义左大括号与右括号同行 S6 词表扩充 scanf/strtok timeSyncTask 重命名为 time_sync_task(3 处代码引用) 更新 pre-commit 使用说明规则表
12 KiB
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 乱码文件
└─ coding-standard C 代码规范检查(规范V1.0 可机械校验项)
└─ 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,无依赖) |
| scripts/check_coding_standard.py | 随仓库提交 | 代码规范检查(纯 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++ 静态分析工具,不运行代码,通过语法分析和数据流推理发现潜在缺陷。本项目开启 warning / performance / portability / style 四个类别,任一类别发现问题即阻止提交(--error-exitcode=1)。
| 项 | 说明 |
|---|---|
| 触发条件 | 自有代码 .c 文件(App/、Src/、Inc/、Drivers/BSP/,第三方库除外)含潜在缺陷、冗余代码或可疑写法 |
| 处理方式 | 阻止提交(必须修复) |
| 修复方式 | 按报告修复代码;确属误报用行内抑制 // cppcheck-suppress 或全局 --suppress |
| 典型报错 | [path/to/file.c:123]: (style) variableScope The scope of the variable ... can be reduced |
| 增量速度 | 单文件约 1~2 秒;全库约 5 秒 |
完整规则清单(style 类别 95 项详解)、配置参数、YAML 引号陷阱、误报抑制方法见
docs/代码规范/cppcheck使用说明.md。
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 coding-standard — C 代码规范检查
依据 docs/代码规范/嵌入式C语言代码规范(V1.0).md,用 scripts/check_coding_standard.py 对可机械化校验的规则做提交前拦截(纯 Python 正则扫描,无依赖)。
| 项 | 说明 |
|---|---|
| 触发条件 | 自有代码 .c/.h 文件(App/、Drivers/BSP/,第三方与 CubeMX 生成文件除外)违反下列规则 |
| 处理方式 | 阻止提交(必须修复) |
| 典型报错 | L123 [S3 3.4.1] 宏名须全大写+下划线: net_htonl |
检查项(S 编号与脚本输出一致):
| 编号 | 规范条款 | 规则 |
|---|---|---|
| S1 | 2.3.1 | .h 文件必须有 XXX_H 格式头文件保护宏(禁止 __ 前缀) |
| S2 | 5.2 | 文件开头必须有文件头注释(含 模块名称/作者) |
| S3 | 3.4.1 | #define 宏名必须全大写+下划线+数字 |
| S4 | 3.5.1 | typedef 类型名必须以 _t 结尾 |
| S5 | 6.3.2 | switch 必须包含 default 分支 |
| S6 | 7.5 | 禁用 strcpy/strcat/sprintf/gets/scanf/strtok(改用安全版本) |
| S7 | 6.3.3 | 禁止 goto |
| S8 | 4.1.1 | 禁止 Tab 缩进(4 空格) |
| S9 | 5.3 | 函数定义必须有函数注释(含「函数功能」) |
| S10 | 3.3.1 | 函数名必须全小写(HAL_ 厂商 API 回调名豁免) |
| S11 | 2.3.5 | 禁止相对/绝对路径 include |
| S12 | 7.6 | 禁止可变参数函数(...) |
| S13 | 7.7 | 禁止递归函数 |
| S15 | 6.1.4 | 文件作用域指针变量必须初始化为 NULL |
| S16 | 3.2.2 | 文件作用域非 static 全局变量必须 g_ 前缀 |
| S17 | 2.2 | 非 static 函数必须在配对 .h 中声明(HAL_ 豁免;无配对头则跳过) |
| S18 | 6.3.1 | 禁止 Yoda 条件(字面量/常量必须在运算符右侧) |
| S19 | 4.4.2 | 函数定义左大括号必须与右括号同行 |
不在检查范围(语义类规则,无法机械可靠判定):函数长度/单一职责、参数数量、错误处理、注释掉的代码等,仍需人工评审把关。
排除文件(与 cppcheck 钩子范围一致,另加):Src/、Inc/(CubeMX 生成,重新生成即覆盖)、Drivers/BSP/GD5F2GQ5UE/nand_ftl.*(dhara/FatFS 接口适配层,函数名由外部 API 固定)。
2.9 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/style | 根据报告修复代码;误报用行内抑制或 --suppress(详见 docs/cppcheck使用说明.md) |
| no-mojibake | U+FFFD replacement chars found |
从 git 历史恢复干净版本(不可逆!) |
| coding-standard | L行号 [Sx x.x.x] 规则说明 |
按违规项修复(命名加前缀/大写化、补函数注释、sprintf 改 snprintf 等) |
| 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 |
仓库根 | 钩子配置(8 个钩子 + 1 个预留) |
.gitlint |
仓库根 | gitlint 规则(宽松,兼容中文) |
scripts/check_mojibake.py |
scripts/ | U+FFFD 检测脚本 |
scripts/check_coding_standard.py |
scripts/ | 代码规范检查脚本(规范 V1.0 可机械校验项) |
docs/代码规范/嵌入式C语言代码规范(V1.0).md |
docs/代码规范/ | 代码规范原文(S1~S19 规则来源) |
docs/代码规范/cppcheck使用说明.md |
docs/代码规范/ | cppcheck 钩子详解(规则/配置/抑制) |
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