fix(hooks): 区分 SwiftLint 工具失败与 error 级违规

pre-commit 第 2 关只看 swiftlint 是否非零,把任何非零都报成「SwiftLint
发现 error 级违规」。而 SwiftLint 默认要把缓存 plist 写进
~/Library/Caches,写不进去时它自己以退出码 1 失败,于是受限环境下每次
提交都被拒绝、并被指向代码,实际原因在工具环境。

两处改动:

- 加 --no-cache,直接消灭缓存写入这一失败模式(本关只查几十个暂存文件,
  代价可忽略;task lint 本来就是这么调的)。
- 按退出码分派:0 放行(有 warning 则提示后放行)、2 报「发现 error 级
  违规」、其余报「执行失败(退出码 N)」并点明是工具错误而非代码违规。

不能改用解析输出文本判断:工具错误信息里同样含 "error:"(配置读不了时
输出 error: Could not read file at path ...),只有退出码可靠。退出码映射
(0 = 干净或仅 warning,2 = error 级违规,1 = 内部错误,134 = 配置读不了)
为 swiftlint 0.65.x 实测。

验证:在 /tmp 最小仓库(真钩子 + 真配置 + core.hooksPath)中不解沙箱跑
五个场景——旧钩子对只含 warning 的文件误报为 error 级违规(复现);新钩子
对同一文件放行并提示 warning;error 级违规正确拒绝且 HEAD 未动;配置读不了
时报「执行失败(退出码 134)」而非违规;复原后放行。

根 README 开发钩子一节同步这一区分。钩子只改第 2 关与头部注释。
This commit is contained in:
Qiu Yuzhou
2026-10-05 20:18:37 +08:00
parent 856d6b4272
commit 67d0afcda1
2 changed files with 35 additions and 9 deletions
+34 -8
View File
@@ -6,7 +6,8 @@
# 格式化结果直接写回暂存区(只改暂存区、不动工作区,部分暂存安全);
# 2. 再用 SwiftLint 检查暂存内容:error 级违规拒绝提交,warning 仅提示。
# 其余文件(含冻结的 Legacy/)一律跳过;没有 NG2 的 Swift 文件时立即放行。
# 工具缺失且确有 NG2 Swift 文件待查时,失败而不是放行。
# 工具缺失且确有 NG2 Swift 文件待查时,失败而不是放行。两关都把「工具没能
# 执行」与「查出问题」分开报告,不把工具错误说成代码违规。
set -u
@@ -71,14 +72,39 @@ while IFS= read -r path; do
done < "$STAGED_LIST"
# 第 2 关:SwiftLint 静态检查暂存内容(error 拒绝提交,warning 提示但放行)
#
# --no-cache:SwiftLint 默认要把缓存 plist 写进 ~/Library/Caches。写不进去时
# 它自己以非零退出,若只看「是否非零」就会把「工具没跑起来」误报成「发现
# error 级违规」(实测:受限环境下每次提交都这样失败)。本关只需要检查几十个
# 暂存文件,省掉缓存的代价可以忽略。
#
# 退出码(swiftlint 0.65.x 实测)区分两类失败;注意不能改用解析输出文本来
# 判断——工具错误信息里同样含 "error:"(配置读不了时输出
# "error: Could not read file at path ..."):
# 0 = 无 error 级违规(可能含 warning);2 = 发现 error 级违规;
# 其余(1 = 内部错误如缓存写失败;134 = 配置读不了等) = 工具执行失败。
LINT_LOG="${TMP_DIR}/swiftlint.log"
if ! swiftlint lint --config "${NG2_DIR}/.swiftlint.yml" --quiet "${TMP_DIR}" >"${LINT_LOG}" 2>&1; then
echo "pre-commit: SwiftLint 发现 error 级违规,提交被拒绝" >&2
echo "pre-commit: 报告中的 0001_xxx.swift 是暂存内容的临时副本,序号_ 后为原文件名:" >&2
cat "${LINT_LOG}" >&2
echo "pre-commit: 请修复后重新提交;手动检查方式见 ShadowsocksX-NG2/README.md" >&2
exit 1
fi
swiftlint lint --config "${NG2_DIR}/.swiftlint.yml" --no-cache --quiet "${TMP_DIR}" \
>"${LINT_LOG}" 2>&1
lint_status=$?
case "$lint_status" in
0) ;;
2)
echo "pre-commit: SwiftLint 发现 error 级违规,提交被拒绝" >&2
echo "pre-commit: 报告中的 0001_xxx.swift 是暂存内容的临时副本,序号_ 后为原文件名:" >&2
cat "${LINT_LOG}" >&2
echo "pre-commit: 请修复后重新提交;手动检查方式见 ShadowsocksX-NG2/README.md" >&2
exit 1
;;
*)
echo "pre-commit: SwiftLint 执行失败(退出码 ${lint_status}),提交被拒绝" >&2
echo "pre-commit: 这是工具错误而非代码违规,请检查配置与工具安装" >&2
echo "pre-commit:(基线版本见 ShadowsocksX-NG2/README.md):" >&2
cat "${LINT_LOG}" >&2
exit 1
;;
esac
if [ -s "${LINT_LOG}" ]; then
echo "pre-commit: SwiftLint warning(不拦截提交):" >&2
+1 -1
View File
@@ -22,7 +22,7 @@
git config core.hooksPath .githooks
```
启用后,`pre-commit` 只检查暂存区中 `ShadowsocksX-NG2/` 下的 `.swift` 文件:`swift format` 的结果写回暂存区、不修改工作区;SwiftLint 的 error 级违规拒绝提交,warning 仅提示。`Legacy/` 和其他文件跳过;没有 NG2 Swift 文件时直接放行。含有 NG2 Swift 文件但缺少工具时,提交检查失败。工具安装与版本见 [`ShadowsocksX-NG2/README.md`](ShadowsocksX-NG2/README.md)。
启用后,`pre-commit` 只检查暂存区中 `ShadowsocksX-NG2/` 下的 `.swift` 文件:`swift format` 的结果写回暂存区、不修改工作区;SwiftLint 的 error 级违规拒绝提交,warning 仅提示。`Legacy/` 和其他文件跳过;没有 NG2 Swift 文件时直接放行。含有 NG2 Swift 文件但缺少工具时,提交检查失败。两关都把「工具没能执行」与「查出问题」分开报告:看到「执行失败」应查工具与配置环境,而不是去改代码。工具安装与版本见 [`ShadowsocksX-NG2/README.md`](ShadowsocksX-NG2/README.md)。
## 领域文档语言