chore(hooks): adopt pre-commit framework

This commit is contained in:
Qiu Yuzhou
2026-10-23 01:27:32 +08:00
parent b6b9349d64
commit a3d53e5813
4 changed files with 45 additions and 124 deletions
-114
View File
@@ -1,114 +0,0 @@
#!/bin/sh
#
# Pre-commit 检查(启用:git config core.hooksPath .githooks,
# 行为约定见仓库根目录 README.md,工具安装与版本见 ShadowsocksX-NG2/README.md):
# 1. 对暂存的 ShadowsocksX-NG2/ 下 .swift 文件先 `swift format` 格式化,
# 格式化结果直接写回暂存区(只改暂存区、不动工作区,部分暂存安全);
# 2. 再用 SwiftLint 检查暂存内容:error 级违规拒绝提交,warning 仅提示。
# 其余文件一律跳过;没有 NG2 的 Swift 文件时立即放行。
# 工具缺失且确有 NG2 Swift 文件待查时,失败而不是放行。两关都把「工具没能
# 执行」与「查出问题」分开报告,不把工具错误说成代码违规。
set -u
REPO_ROOT="$(git rev-parse --show-toplevel)" || exit 1
NG2_DIR="$REPO_ROOT/ShadowsocksX-NG2"
[ -d "$NG2_DIR" ] || exit 0
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/ng2-pre-commit.XXXXXX")" || exit 1
trap 'rm -rf "$TMP_DIR"' EXIT
# 暂存的新增/修改文件中只取 NG2 下的 .swift(删除的文件无需处理);
# 关闭 quotePath,非 ASCII 文件名按原文输出
STAGED_LIST="${TMP_DIR}/staged.txt"
git -c core.quotePath=false diff --cached --name-only --diff-filter=ACMR |
while IFS= read -r path; do
case "$path" in
ShadowsocksX-NG2/*.swift) printf '%s\n' "$path" ;;
esac
done > "$STAGED_LIST"
[ -s "$STAGED_LIST" ] || exit 0
for tool in swift swiftlint; do
if ! command -v "$tool" >/dev/null 2>&1; then
echo "pre-commit: 缺少 ${tool},无法执行提交检查(安装步骤见 ShadowsocksX-NG2/README.md)" >&2
exit 1
fi
done
# 暂存内容的临时副本路径:序号前缀避免不同目录同名文件冲突
tmp_name() {
printf '%s/%04d_%s' "${TMP_DIR}" "$1" "$(basename "$2")"
}
# 取暂存内容到临时目录处理,与工作区解耦
i=0
while IFS= read -r path; do
i=$((i + 1))
git show ":${path}" > "$(tmp_name "$i" "${path}")" || exit 1
done < "$STAGED_LIST"
# 第 1 关:swift format 格式化(显式传配置路径);语法解析失败等工具错误即拒绝
if ! swift format format --in-place --configuration "${NG2_DIR}/.swift-format" \
"${TMP_DIR}"/[0-9]*.swift 2>"${TMP_DIR}/format.err"; then
echo "pre-commit: swift format 执行失败,提交被拒绝:" >&2
cat "${TMP_DIR}/format.err" >&2
exit 1
fi
# 格式化结果写回暂存区
i=0
while IFS= read -r path; do
i=$((i + 1))
tmp_file="$(tmp_name "$i" "${path}")"
staged_hash="$(git show ":${path}" | git hash-object --stdin)"
formatted_hash="$(git hash-object -w "${tmp_file}")"
if [ "$staged_hash" != "$formatted_hash" ]; then
git update-index --cacheinfo "100644,${formatted_hash},${path}" || exit 1
echo "pre-commit: swift format 已重新格式化 ${path} (格式化结果已入提交)"
fi
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"
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
cat "${LINT_LOG}" >&2
fi
exit 0
+19
View File
@@ -0,0 +1,19 @@
minimum_pre_commit_version: "3.2.0"
repos:
- repo: local
hooks:
- id: ng2-swift-format
name: Format staged NG2 Swift files
entry: swift format format --in-place --configuration ShadowsocksX-NG2/.swift-format
language: system
files: ^ShadowsocksX-NG2/.*\.swift$
stages: [pre-commit]
- id: ng2-swiftlint
name: Lint staged NG2 Swift files
entry: swiftlint lint --config ShadowsocksX-NG2/.swiftlint.yml --no-cache --quiet
language: system
files: ^ShadowsocksX-NG2/.*\.swift$
stages: [pre-commit]
verbose: true
+13 -5
View File
@@ -61,20 +61,28 @@ You can build and run unit tests without a certificate: the Debug baseline uses
## Code style tools
- `swift format` is included with the Xcode/Swift toolchain and does not need a separate installation (baseline: Apple Swift 6.4). Install SwiftLint with `brew install swiftlint` (baseline: 0.65.0). If either tool is missing, the pre-commit hook rejects a commit that contains Swift files under the project directory instead of allowing it.
- `swift format` is included with the Xcode/Swift toolchain and does not need a separate installation (baseline: Apple Swift 6.4). Install SwiftLint with `brew install swiftlint` (baseline: 0.65.0), and install the hook manager with `brew install pre-commit`. If a required tool is missing, pre-commit rejects a commit that contains NG2 Swift files.
- `.swift-format` is a snapshot of the Swift 6.4 toolchain's default rules, checked in so the rules do not drift on toolchain upgrades. `.swiftlint.yml` starts with the default rules and declares only exclusions and individual opt-outs that conflict with the format baseline; comments in the file show triggering examples.
- See [Development hooks](#development-hooks) for how to enable the repository-level `pre-commit` hook and what it checks.
- The repository-level hook configuration is `.pre-commit-config.yaml`. See [Development hooks](#development-hooks) for how to install it and what it checks.
- Adjust rules only to address concrete problems. A PR that changes `.swift-format` or `.swiftlint.yml` must include a triggering example and update the baseline versions in this section.
## Development hooks
After cloning, enable Git hooks from the repository root:
After cloning, install the hooks from the repository root:
```bash
git config core.hooksPath .githooks
brew install pre-commit # Skip if already installed.
pre-commit install
```
Once enabled, `pre-commit` checks only staged `.swift` files under `ShadowsocksX-NG2/`. `swift format` writes its result back to the index without changing the working tree. SwiftLint errors reject the commit; warnings are informational. Other files are skipped. If there are no NG2 Swift files, the hook passes. If NG2 Swift files are present and a required tool is missing, the check fails. Both checks distinguish “tool execution failed” from “issues found”: when you see “execution failed,” investigate the tool or configuration environment instead of changing code. See [Code style tools](#code-style-tools) for installation and versions.
If you previously enabled the old `.githooks` setup, remove its local Git setting before installing the new hook:
```bash
git config --local --unset core.hooksPath
pre-commit install
```
The configuration checks only staged `.swift` files under `ShadowsocksX-NG2/`; other files are skipped. `swift format` formats those files in the working tree, and pre-commit stops that commit when formatting changes them. Review and stage the formatting changes, then commit again. SwiftLint errors reject the commit; warnings are shown but do not block it. If there are no NG2 Swift files, both hooks are skipped. If NG2 Swift files are staged and a required tool is missing, the corresponding hook fails. See [Code style tools](#code-style-tools) for tool installation and versions.
## Language of domain documents
+13 -5
View File
@@ -62,20 +62,28 @@ ACL 路由与插件监管),不在默认 `task test` 内,由独立 scheme
## 代码风格工具
- `swift format` 随 Xcode/Swift 工具链提供,无需单独安装(基线:Apple Swift 6.4);SwiftLint 需 `brew install swiftlint`(基线 0.65.0)。工具缺失时,含工程目录 Swift 文件的提交会被 pre-commit 钩子拒绝而不是放行。
- `swift format` 随 Xcode/Swift 工具链提供,无需单独安装(基线:Apple Swift 6.4);SwiftLint 需 `brew install swiftlint`(基线 0.65.0),钩子管理器需 `brew install pre-commit`。缺少必需工具时,暂存了 NG2 Swift 文件的提交会被拒绝。
- 配置文件:`.swift-format` 是 Swift 6.4 工具链默认规则的快照(固定成文件,工具链升级不漂移);`.swiftlint.yml` 默认规则起步,只声明排除项与个别和 format 基线冲突的关闭项(文件内有触发案例注释)。
- 仓库级 `pre-commit` 钩子的启用方式和检查范围见下节「开发钩子」。
- 仓库级 hook 配置位于 `.pre-commit-config.yaml`;安装方式和检查范围见下节「开发钩子」。
- 调整规则只针对实际痛点:修改 `.swift-format` / `.swiftlint.yml` 的 PR 须给出触发案例,并同步更新本节中的基线版本。
## 开发钩子
新 clone 后,在仓库根目录启用 Git hooks:
新 clone 后,在仓库根目录安装 hooks:
```bash
git config core.hooksPath .githooks
brew install pre-commit # 已安装时跳过
pre-commit install
```
启用后,`pre-commit` 只检查暂存区中 `ShadowsocksX-NG2/` 下的 `.swift` 文件:`swift format` 的结果写回暂存区、不修改工作区;SwiftLint 的 error 级违规拒绝提交,warning 仅提示。其他文件跳过;没有 NG2 Swift 文件时直接放行。含有 NG2 Swift 文件但缺少工具时,提交检查失败。两关都把「工具没能执行」与「查出问题」分开报告:看到「执行失败」应查工具与配置环境,而不是去改代码。工具安装与版本见上节「代码风格工具」。
如果之前启用过旧版 `.githooks`,先清除本地 Git 设置,再安装新 hook:
```bash
git config --local --unset core.hooksPath
pre-commit install
```
配置只检查暂存区中 `ShadowsocksX-NG2/` 下的 `.swift` 文件,其他文件跳过。`swift format` 会在工作区格式化这些文件;格式发生变化时,本次提交会被 pre-commit 拦下。检查并暂存格式化结果后再提交。SwiftLint 的 error 级违规拒绝提交,warning 会显示但不阻止提交。没有 NG2 Swift 文件时两个 hook 都跳过;暂存了 NG2 Swift 文件但缺少必需工具时,对应 hook 会失败。工具安装与版本见上节「代码风格工具」。
## 领域文档语言