Files
blog/.githooks/commit-msg
2026-10-24 21:48:28 +08:00

113 lines
4.6 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# commit-msg hook — ai-code-style-kit
# 规则源:skills/commit-style/SKILL.md(v1.0)§2/§3.5/§7.1
# 同规则的权威实现见 extension/rules.cjs(两侧口径必须一致,tests 负责对照)
# 跨平台:Linux / macOS 原生 sh;Windows 经 Git for Windows 自带 sh。零依赖。
#
# 安装:hooks/install.sh(或 install.ps1),或手动:
# mkdir -p .githooks && cp hooks/commit-msg .githooks/ && git config core.hooksPath .githooks
msg_file="$1"
[ -f "$msg_file" ] || exit 0
# 首行(去首尾空白与 CR,与 rules.cjs 的 trim 对齐)
first=$(sed -n '1p' "$msg_file" | tr -d '\r' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
# 豁免:git 自动生成的 merge / revert 消息(SKILL.md §3.5)
case "$first" in
"Merge "*|'Revert "'*) exit 0 ;;
esac
errors=0
fail() {
echo "commit-msg: $*" >&2
errors=$((errors + 1))
}
TYPE_RE='(feat|fix|perf|refactor|test|docs|build|ci|chore|style|revert)'
# header 格式:type(scope)!: 主题
if ! printf '%s' "$first" | grep -Eq "^${TYPE_RE}(\\([a-z0-9_.-]+\\))?!?: .+"; then
echo "commit-msg: header 不符合 type(scope)!: 主题 格式(type ∈ feat/fix/perf/refactor/test/docs/build/ci/chore/style/revert):$first" >&2
echo "commit-msg: 提交被拒绝。规范全文见 skills/commit-style/SKILL.md" >&2
exit 1
fi
subject=$(printf '%s' "$first" | sed -E "s/^${TYPE_RE}(\\([a-z0-9_.-]+\\))?!?: //" | sed -E 's/^[[:space:]]+//')
hdr_type=$(printf '%s' "$first" | sed -E "s/^(${TYPE_RE}).*/\1/")
# 主题结尾不加句号("v1.0" 这类数字点结尾不算)
case "$subject" in
*。) fail "主题结尾不加句号" ;;
esac
if printf '%s' "$subject" | grep -Eq '[^0-9]\.$'; then
fail "主题结尾不加句号"
fi
# 显示宽度:全角记 2 / 半角记 1,上限 100(约 50 个汉字)
# 探测 wc -m 是否按码点计数(UTF-8 locale);不行则依次尝试常见 UTF-8 locale;
# 都不行(无 UTF-8 环境的极端情况)跳过宽度检查(echo 0),其余规则仍生效。
dw_width() {
_probe=$(printf '\344\275\240' | wc -m)
if [ "$_probe" -eq 1 ] 2>/dev/null; then
_c=$(printf '%s' "$1" | wc -m); _b=$(printf '%s' "$1" | wc -c)
echo $(( _c + (_b - _c) / 2 )); return
fi
for _l in C.UTF-8 en_US.UTF-8; do
_probe=$(printf '\344\275\240' | LC_ALL="$_l" wc -m)
if [ "$_probe" -eq 1 ] 2>/dev/null; then
_c=$(printf '%s' "$1" | LC_ALL="$_l" wc -m); _b=$(printf '%s' "$1" | wc -c)
echo $(( _c + (_b - _c) / 2 )); return
fi
done
echo 0
}
width=$(dw_width "$subject")
if [ "$width" -gt 100 ] 2>/dev/null; then
fail "主题显示宽度 $width > 100(全角记 2/半角记 1,约 50 个汉字)"
fi
# 空泛词封闭匹配(与 extension/rules.cjs 一致):黑名单词单独,或仅带泛称宾语
# en 归一化与 JS 的 trim+小写+折叠空白对齐(tr 只做小写与压缩空格会漏多空格/tab 形态)
zh=$(printf '%s' "$subject" | tr -d '[:space:]')
for _w in 更新 优化 修复 完善 调整 修改 重构 小改动; do
for _g in "" 代码 性能 bug BUG Bug 功能 问题 文件 逻辑 一些问题; do
if [ "$zh" = "$_w$_g" ]; then
fail "主题为空泛词(单独或仅带泛称宾语):\"$subject\" —— 宾语须具体到模块/现象/数字"
break 2
fi
done
done
en=$(printf '%s' "$subject" | tr '[:upper:]' '[:lower:]' | sed -E 's/[[:space:]]+/ /g; s/^ //; s/ $//')
for _w in update fix optimize refactor cleanup tweak improve wip; do
for _g in "" code codes performance perf bug bugs file files logic stuff things; do
if [ -z "$_g" ]; then _pat="$_w"; else _pat="$_w $_g"; fi
if [ "$en" = "$_pat" ]; then
fail "主题为空泛词(单独或仅带泛称宾语):\"$subject\" —— 宾语须具体到模块/现象/数字"
break 2
fi
done
done
# 验证行:feat/fix/perf 必须有(token 双语:验证: / Verified:)
case "$hdr_type" in
feat|fix|perf)
if ! grep -Eq '^验证:[[:blank:]]*[^[:space:]]' "$msg_file" && \
! grep -Eq '^[Vv]erified:[[:blank:]]*[^[:space:]]' "$msg_file"; then
fail "type 为 $hdr_type,必须有\"验证:\"行(英文项目 Verified:)——只写真实跑过的检查与结果;没跑过就先别提交"
fi
;;
esac
# BREAKING CHANGE 脚注必须全大写
if grep -Eiq '^[[:blank:]]*breaking[[:blank:]]+change[[:blank:]]*:' "$msg_file" && \
! grep -Eq '^[[:blank:]]*BREAKING CHANGE[[:blank:]]*:' "$msg_file"; then
fail "脚注 BREAKING CHANGE 必须全大写"
fi
if [ "$errors" -gt 0 ]; then
echo "commit-msg: 提交被拒绝($errors 个问题)。规范全文见 skills/commit-style/SKILL.md" >&2
exit 1
fi
exit 0