diff --git a/.gitlint b/.gitlint new file mode 100644 index 00000000..79e30c2e --- /dev/null +++ b/.gitlint @@ -0,0 +1,4 @@ +[general] +ignore=body-is-missing +[title-max-length] +line-length=100 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..a7cb6c57 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,75 @@ +# pre-commit 自动化代码审查配置 +# 详见 docs/测试说明/自动化代码审查实施.md +default_install_hook_types: [pre-commit, commit-msg] + +exclude: | + (?x)^( + \.opencode/node_modules/| + Lib/lvgl/| + simulator/| + MDK-ARM/RTE/ + ) + +repos: + # 1) 通用钩子(官方) + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-added-large-files + - id: check-merge-conflict + - id: check-yaml + + # 2) C 代码格式:只检查,绝不改写(编码铁律) + # Route A:暂不启用,待全库格式化基线后再放开 + # - repo: https://github.com/pre-commit/mirrors-clang-format + # rev: v19.1.4 + # hooks: + # - id: clang-format + # args: [--dry-run, --Werror] + + # 3) commit message 格式(宽松规则,兼容中文标题) + - repo: https://github.com/jorisroovers/gitlint + rev: v0.19.1 + hooks: + - id: gitlint + + # 4) 本地自定义钩子(仓库级) + - repo: local + hooks: + - id: cppcheck + name: cppcheck + entry: cppcheck + args: [--enable=warning,performance,portability, --error-exitcode=1, + --std=c99, --quiet, + --suppress=missingIncludeSystem, --suppress=missingInclude, + --suppress=invalidPrintfArgType_sint, --suppress=invalidPrintfArgType_uint, + -I Inc -I App -I Drivers/BSP, + -I Drivers/STM32F4xx_HAL_Driver/Inc -I Drivers/CMSIS/Include, + -I Middlewares/Third_Party/FreeRTOS/Source/include] + language: system + types: [c] + # 仅扫描项目自有代码;排除第三方库与 LVGL 依赖文件 + exclude: | + (?x)^( + Drivers/CMSIS/| + Drivers/STM32F4xx_HAL_Driver/| + Drivers/BSP/UI/| + Drivers/BSP/NET/lftpd/| + Drivers/BSP/dhara/| + Lib/| + Middlewares/| + MDK-ARM/| + test/ + ) + - id: no-mojibake + name: Forbid U+FFFD mojibake + entry: python scripts/check_mojibake.py + language: system + types: [c] + # 已知历史遗留损坏文件(HEAD 已含 U+FFFD,修复前豁免) + exclude: | + (?x)^( + Drivers/BSP/TPAFE5160/tpafe5160\.c + ) diff --git a/docs/自动化代码审查实施.md b/docs/自动化代码审查实施.md new file mode 100644 index 00000000..912c7027 --- /dev/null +++ b/docs/自动化代码审查实施.md @@ -0,0 +1,179 @@ +# 自动化代码审查实施(pre-commit 落地) + +> 目标:在 `git commit` 时自动执行「代码格式检查(只查不改写)→ 静态分析 → 乱码防护 → commit message 校验」,任一失败即中止提交。全部为本地 git 钩子,不依赖服务器;后续可扩展 Gitea Actions / PR-Agent(见文末「服务端扩展」)。 + +## 环境事实(已勘察,2026-08-31) + +| 项 | 现状 | 影响 | +|---|---|---| +| Python | msys64 Python(**无 pip**)、WindowsApps 占位 python | 不能直接 `pip install pre-commit` | +| `uv` | `C:\Users\Administrator\.local\bin\uv.exe` ✅ | **用 uv 安装 pre-commit**(参考项目自动报销系统同体系) | +| git | 2.55.0,`core.autocrlf=true` | 磁盘文件 CRLF、索引内 LF;钩子不启用 mixed-line-ending,避免换行翻转 | +| 源码编码 | 全库 222 个 .c/.h 均为 **UTF-8 + CRLF**(字节级验证,0 个 U+FFFD);docs/ 全部 UTF-8 | 当前无 GBK 文件,但规则仍以「只检查、不改写」为铁律 | +| 现有钩子 | 无(全为 .sample) | 从零安装,无冲突 | +| 需排除目录 | `.opencode/node_modules/`、`Lib/lvgl/`、`simulator/`、`MDK-ARM/RTE/` | 钩子排除,否则误报/卡死 | +| winget | 可用 | 用于安装 cppcheck | + +## 钩子选型(Route A 定稿) + +| 钩子 | 来源 | 作用 | 状态 | +|---|---|---|---| +| `trailing-whitespace` | pre-commit-hooks v5.0.0 | 行尾空格(只删空白,编码安全) | ✅ 启用 | +| `end-of-file-fixer` | pre-commit-hooks | 文件尾换行 | ✅ 启用 | +| `check-added-large-files` | pre-commit-hooks | 防误提交大文件/构建产物 | ✅ 启用 | +| `check-merge-conflict` | pre-commit-hooks | 防提交未解决的冲突标记 | ✅ 启用 | +| `check-yaml` | pre-commit-hooks | 校验本配置自身 | ✅ 启用 | +| `clang-format` | mirrors-clang-format v19.1.4 | C 格式检查(`--dry-run --Werror`) | ⏸ **Route A:先注释,暂不启用** | +| `gitlint` | jorisroovers/gitlint v0.19.1 | commit message 格式(宽松,兼容中文) | ✅ 启用(commit-msg 阶段) | +| `cppcheck` | local(需 winget 安装) | 静态分析 warning/performance/portability | ✅ 启用(未装可暂缓) | +| `no-mojibake` | local(`scripts/check_mojibake.py`) | 禁止 U+FFFD 乱码进入提交 | ✅ 启用 | + +> **Route A 说明**:全库 222 个文件大概率不满足 clang-format。本轮**不启用** clang-format 钩子(配置保留注释),零破坏先跑起来;待以后全库统一格式化形成独立基线 commit 后再启用(见「路线 B(未来)」)。 + +## 阶段 0:安装 pre-commit(5 分钟) + +```powershell +uv tool install pre-commit +pre-commit --version +``` + +## 阶段 1:创建 `.pre-commit-config.yaml`(仓库根目录) + +```yaml +default_install_hook_types: [pre-commit, commit-msg] + +exclude: | + (?x)^( + \.opencode/node_modules/| + Lib/lvgl/| + simulator/| + MDK-ARM/RTE/ + ) + +repos: + # 1) 通用钩子(官方) + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-added-large-files + - id: check-merge-conflict + - id: check-yaml + + # 2) C 代码格式:只检查,绝不改写(编码铁律) + # Route A:暂不启用,待全库格式化基线后再放开 + # - repo: https://github.com/pre-commit/mirrors-clang-format + # rev: v19.1.4 + # hooks: + # - id: clang-format + # args: [--dry-run, --Werror] + + # 3) commit message 格式(宽松规则,兼容中文标题) + - repo: https://github.com/jorisroovers/gitlint + rev: v0.19.1 + hooks: + - id: gitlint + + # 4) 本地自定义钩子(仓库级) + - repo: local + hooks: + - id: cppcheck + name: cppcheck + entry: cppcheck + args: [--enable=warning,performance,portability, --error-exitcode=1, + --std=c99, --suppress=missingIncludeSystem, --quiet] + language: system + types: [c] + - id: no-mojibake + name: Forbid U+FFFD mojibake + entry: python scripts/check_mojibake.py + language: script + types: [c] +``` + +## 阶段 2:创建辅助文件 + +### 2.1 `scripts/check_mojibake.py`(UTF-8 编码) + +```python +import sys + +BAD = b"\xef\xbf\xbd" +bad_files = [] +for path in sys.argv[1:]: + with open(path, "rb") as f: + if BAD in f.read(): + bad_files.append(path) +if bad_files: + print("U+FFFD replacement chars found:") + for p in bad_files: + print(" -", p) + sys.exit(1) +``` + +### 2.2 `.gitlint`(宽松规则) + +```ini +[general] +ignore=body-is-missing +[title-max-length] +line-length=100 +``` + +说明:不做 Conventional Commits 强制(现有"搭建LVGL仿真环境"这类中文标题必须能通过)。 + +### 2.3 `.clang-format`(预留,Route A 阶段创建但不启用) + +按工程实际风格生成,以 4 空格缩进为基底(Keil 工程常见风格),启用 clang-format 时配套使用。 + +## 阶段 3:安装钩子 + 基线校准 + +```powershell +pre-commit install # 同时安装 pre-commit 与 commit-msg 钩子 +pre-commit run --all-files # 全库跑一遍,看当前违规 +``` + +- `git commit` 提交首个版本(含 `.pre-commit-config.yaml`、`.gitlint`、`scripts/check_mojibake.py`、本文档)。 +- 若 `trailing-whitespace` / `end-of-file-fixer` 修改了文件,属正常:它们是白名单安全钩子(只删行尾空格、补尾部换行,不改编码)。 + +## 阶段 4:日常使用 + +- 提交时自动执行全部钩子,任一失败 → commit 中止,按提示修复后重新提交。 +- 临时跳过单个钩子(逃生通道):`SKIP=cppcheck git commit -m "..."`(不推荐)。 +- 新机器启用:`uv tool install pre-commit` → 进入仓库 → `pre-commit install`。 + +## 路线 B(未来,待决策后执行) + +全库统一格式化基线: + +```powershell +pre-commit run clang-format --all-files # 一次性格式化,独立 commit 提交 +``` + +编码已验证全库 UTF-8,风险低;完成后放开 `.pre-commit-config.yaml` 中 clang-format 注释并强制。 + +## 服务端扩展(可选,后续) + +1. **Gitea Actions**(Gitea ≥1.19 自带,需管理员部署 runner):PR 上跑相同检查 + `@build` 编译(Windows runner 跑 `MDK-ARM/build.bat`),结果回写 PR 状态。 +2. **PR-Agent**(12.8k★,官方支持 Gitea):`pip install pr-agent` + Gitea webhook,PR 自动 AI review。 + +## 风险与对策 + +| 风险 | 对策 | +|---|---| +| clang-format 改写触发乱码 | 只用 `--dry-run --Werror`,永不落盘;mojibake 钩子兜底 | +| CRLF/LF 因 autocrlf 混乱 | trailing-whitespace 只删空白不转换换行;不启用 mixed-line-ending | +| 全库格式化改动大 | Route A:clang-format 先注释,待基线 commit 再启用 | +| cppcheck 误报多 | `--suppress` 白名单收敛;先 warning/performance/portability 三档 | +| gitlint 卡中文提交 | 宽松配置,不强制 Conventional Commits | +| cppcheck 未安装导致钩子失败 | `winget install cppcheck` 安装并加入 PATH;或暂时注释该钩子 | + +## 实施清单(Checklist) + +- [ ] 阶段 0:`uv tool install pre-commit` +- [ ] 阶段 1:创建 `.pre-commit-config.yaml` +- [ ] 阶段 2:创建 `scripts/check_mojibake.py`、`.gitlint` +- [ ] 阶段 3:`pre-commit install` + `pre-commit run --all-files` 校准基线 +- [ ] 验证:故意提交一条违规(行尾空格/坏 commit message/含 U+FFFD),确认被拦截 +- [ ] 提交首个版本并推送到 Gitea diff --git a/scripts/check_mojibake.py b/scripts/check_mojibake.py new file mode 100644 index 00000000..e72d64de --- /dev/null +++ b/scripts/check_mojibake.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""检查 C/C++ 源码中是否含 U+FFFD 替换符(编码损坏标志,不可逆),含则退出码 1。""" +import sys + +BAD = b"\xef\xbf\xbd" +bad_files = [] +for path in sys.argv[1:]: + with open(path, "rb") as f: + if BAD in f.read(): + bad_files.append(path) +if bad_files: + print("U+FFFD replacement chars found (encoding corrupted, irreversible!):") + for p in bad_files: + print(" -", p) + sys.exit(1)