From a3d53e58134295539ca562bea996fa25a8d33c42 Mon Sep 17 00:00:00 2001 From: Qiu Yuzhou Date: Fri, 23 Oct 2026 01:27:32 +0800 Subject: [PATCH] chore(hooks): adopt pre-commit framework --- .githooks/pre-commit | 114 ---------------------------------------- .pre-commit-config.yaml | 19 +++++++ docs/development.md | 18 +++++-- docs/development.zh.md | 18 +++++-- 4 files changed, 45 insertions(+), 124 deletions(-) delete mode 100755 .githooks/pre-commit create mode 100644 .pre-commit-config.yaml diff --git a/.githooks/pre-commit b/.githooks/pre-commit deleted file mode 100755 index 58697cbe..00000000 --- a/.githooks/pre-commit +++ /dev/null @@ -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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..df71279b --- /dev/null +++ b/.pre-commit-config.yaml @@ -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 diff --git a/docs/development.md b/docs/development.md index da0c11b0..93fd6c33 100644 --- a/docs/development.md +++ b/docs/development.md @@ -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 diff --git a/docs/development.zh.md b/docs/development.zh.md index bf171f32..b6ac02ee 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -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 会失败。工具安装与版本见上节「代码风格工具」。 ## 领域文档语言