refactor: 重写 Git hooks 体系,委托已有检查、新增模板与 LFS 校验
pre-commit 代码检查改为委托 _backend-lint / _versionctl-lint / _frontend-check,新增 LFS 指针校验;commit-msg 新增多行空行格式校验和模板注释忽略,移除 CJK/Python 字符集检测;新增 prepare-commit-msg 提交信息模板;hooks-install 增加 source 文件存在性校验;前端 check 补入 tsc -b 类型检查并修复暴露的类型错误
This commit is contained in:
@@ -8,7 +8,33 @@ if [ ! -f "$MSG_FILE" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IFS= read -r FIRST_LINE < "$MSG_FILE" || FIRST_LINE=
|
||||
FIRST_LINE=
|
||||
SECOND_LINE=
|
||||
HAS_BODY=
|
||||
LINE_NO=0
|
||||
|
||||
while IFS= read -r LINE || [ -n "$LINE" ]; do
|
||||
case "$LINE" in
|
||||
\#*) continue ;;
|
||||
esac
|
||||
|
||||
if [ -z "$FIRST_LINE" ]; then
|
||||
[ -n "$LINE" ] || continue
|
||||
FIRST_LINE=$LINE
|
||||
LINE_NO=1
|
||||
continue
|
||||
fi
|
||||
|
||||
LINE_NO=$((LINE_NO + 1))
|
||||
case "$LINE_NO" in
|
||||
2) SECOND_LINE=$LINE ;;
|
||||
*)
|
||||
if [ -n "$LINE" ]; then
|
||||
HAS_BODY=1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done < "$MSG_FILE"
|
||||
|
||||
case "$FIRST_LINE" in
|
||||
Merge*)
|
||||
@@ -31,12 +57,11 @@ EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DESCRIPTION=${FIRST_LINE#*: }
|
||||
if printf '%s\n' "$DESCRIPTION" | LC_ALL=C grep -Eq '^[ -~]+$'; then
|
||||
printf '%s\n' '提交描述需使用中文。' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ${#FIRST_LINE} -gt 72 ]; then
|
||||
printf '%s\n' '警告: 提交信息首行超过 72 个字符,建议保持简短。' >&2
|
||||
fi
|
||||
|
||||
if [ -n "$HAS_BODY" ] && [ -n "$SECOND_LINE" ]; then
|
||||
printf '%s\n' '提交信息首行后应为空行,再写详细描述。' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
49
scripts/git-hooks/prepare-commit-msg
Executable file
49
scripts/git-hooks/prepare-commit-msg
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
MSG_FILE=$1
|
||||
MSG_SOURCE=$2
|
||||
|
||||
case "$MSG_SOURCE" in
|
||||
"") ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
if [ ! -f "$MSG_FILE" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
has_content=0
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
case "$line" in
|
||||
''|\#*) ;;
|
||||
*)
|
||||
has_content=1
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done < "$MSG_FILE"
|
||||
|
||||
if [ "$has_content" -eq 1 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tmp_file=${MSG_FILE}.nex-template.$$
|
||||
{
|
||||
cat <<'EOF'
|
||||
# <类型>: <简短中文描述>
|
||||
#
|
||||
# <详细说明>
|
||||
#
|
||||
# 类型: feat / fix / refactor / docs / style / test / chore
|
||||
# 示例: feat: 添加供应商批量管理功能
|
||||
EOF
|
||||
if [ -s "$MSG_FILE" ]; then
|
||||
printf '\n'
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
printf '%s\n' "$line"
|
||||
done < "$MSG_FILE"
|
||||
fi
|
||||
} > "$tmp_file"
|
||||
|
||||
mv "$tmp_file" "$MSG_FILE"
|
||||
@@ -14,7 +14,9 @@ cleanup() {
|
||||
frontend/src/hook_format_fixture.ts \
|
||||
docs/hook-doc-fixture.md \
|
||||
docs/hook-conflict-fixture.md \
|
||||
docs/hook-large-fixture.txt
|
||||
docs/hook-large-fixture.txt \
|
||||
"$TMP_DIR/lfs-pointer-fixture" \
|
||||
"$TMP_DIR/lfs-bad-fixture"
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
|
||||
@@ -35,6 +37,14 @@ write_msg() {
|
||||
printf '%s\n' "$*" > "$file"
|
||||
}
|
||||
|
||||
write_conflict() {
|
||||
file=$1
|
||||
less7=$(printf '<%.0s' $(seq 7))
|
||||
eq7=$(printf '=%.0s' $(seq 7))
|
||||
gt7=$(printf '>%.0s' $(seq 7))
|
||||
printf '%s\n' "${less7} HEAD" '' "${eq7}" '' "${gt7} branch" > "$file"
|
||||
}
|
||||
|
||||
expect_success() {
|
||||
name=$1
|
||||
shift
|
||||
@@ -66,12 +76,34 @@ run_precommit_for() {
|
||||
GIT_INDEX_FILE=$index make _hooks-pre-commit
|
||||
}
|
||||
|
||||
run_hooks_install_missing_source() {
|
||||
install_repo=$TMP_DIR/hooks-install-missing
|
||||
rm -rf "$install_repo"
|
||||
mkdir -p "$install_repo/scripts/git-hooks"
|
||||
cp Makefile "$install_repo/Makefile"
|
||||
cp scripts/git-hooks/pre-commit "$install_repo/scripts/git-hooks/pre-commit"
|
||||
cp scripts/git-hooks/commit-msg "$install_repo/scripts/git-hooks/commit-msg"
|
||||
git -C "$install_repo" init >/dev/null 2>&1
|
||||
(cd "$install_repo" && make hooks-install)
|
||||
}
|
||||
|
||||
MSG_FILE=$TMP_DIR/commit-msg.txt
|
||||
|
||||
# ============================================
|
||||
# commit-msg 测试
|
||||
# ============================================
|
||||
|
||||
write_msg "$MSG_FILE" 'feat: 添加 hook 测试'
|
||||
expect_success 'commit-msg accepts Chinese description' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
write_msg "$MSG_FILE" 'feat: add hook tests'
|
||||
expect_failure 'commit-msg rejects English-only description' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
expect_success 'commit-msg accepts English-only description without CJK enforcement' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
write_msg "$MSG_FILE" 'fix: 修复 auth 模块 bug'
|
||||
expect_success 'commit-msg accepts Chinese with English technical terms' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
write_msg "$MSG_FILE" 'docs: ajouter une fonctionnalité'
|
||||
expect_success 'commit-msg accepts non-CJK unicode description without CJK enforcement' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
write_msg "$MSG_FILE" 'update: 添加 hook 测试'
|
||||
expect_failure 'commit-msg rejects invalid type' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
@@ -79,6 +111,81 @@ expect_failure 'commit-msg rejects invalid type' scripts/git-hooks/commit-msg "$
|
||||
write_msg "$MSG_FILE" 'Merge branch feature'
|
||||
expect_success 'commit-msg accepts merge commits' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
write_msg "$MSG_FILE" 'feat: 添加新功能
|
||||
'
|
||||
expect_success 'commit-msg accepts single line with trailing newline' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
printf 'feat: 添加新功能\n\n详细描述内容\n' > "$MSG_FILE"
|
||||
expect_success 'commit-msg accepts multi-line with blank line separator' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
printf 'feat: 添加新功能\n缺少空行\n详细描述\n' > "$MSG_FILE"
|
||||
expect_failure 'commit-msg rejects multi-line without blank line separator' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
printf 'feat: 添加新功能\n\n' > "$MSG_FILE"
|
||||
expect_success 'commit-msg accepts two lines with blank line 2' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
printf 'feat: 添加新功能\n非空行\n' > "$MSG_FILE"
|
||||
expect_success 'commit-msg accepts two lines without body (no line 3)' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
printf 'feat: 添加模板测试\n# <类型>: <简短中文描述>\n#\n# <详细说明>\n' > "$MSG_FILE"
|
||||
expect_success 'commit-msg ignores template comments after subject' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
printf '# <类型>: <简短中文描述>\n#\nfeat: 添加模板测试\n' > "$MSG_FILE"
|
||||
expect_success 'commit-msg ignores leading template comments' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
printf 'feat: 添加新功能\n缺少空行\n# 模板注释\n详细描述\n' > "$MSG_FILE"
|
||||
expect_failure 'commit-msg rejects non-blank separator with intervening comments' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||
|
||||
# ============================================
|
||||
# prepare-commit-msg 测试
|
||||
# ============================================
|
||||
|
||||
prepare_msg_file="$TMP_DIR/prepare-msg.txt"
|
||||
rm -f "$prepare_msg_file"
|
||||
touch "$prepare_msg_file"
|
||||
expect_success 'prepare-commit-msg writes template for empty commit' scripts/git-hooks/prepare-commit-msg "$prepare_msg_file" ""
|
||||
|
||||
if grep -q '<类型>' "$prepare_msg_file" && grep -q 'feat / fix / refactor' "$prepare_msg_file"; then
|
||||
pass 'prepare-commit-msg template contains format guidance'
|
||||
else
|
||||
fail 'prepare-commit-msg template contains format guidance'
|
||||
fi
|
||||
|
||||
printf '\n# Please enter the commit message for your changes.\n# On branch main\n' > "$prepare_msg_file"
|
||||
expect_success 'prepare-commit-msg writes template before git comments' scripts/git-hooks/prepare-commit-msg "$prepare_msg_file" ""
|
||||
if grep -q '<类型>' "$prepare_msg_file" && grep -q 'Please enter the commit message' "$prepare_msg_file"; then
|
||||
pass 'prepare-commit-msg preserves git comments after template'
|
||||
else
|
||||
fail 'prepare-commit-msg preserves git comments after template'
|
||||
fi
|
||||
|
||||
write_msg "$prepare_msg_file" 'existing content'
|
||||
expect_success 'prepare-commit-msg skips when file has content' scripts/git-hooks/prepare-commit-msg "$prepare_msg_file" ""
|
||||
if printf '%s\n' "$(cat "$prepare_msg_file")" | grep -q '^existing content$'; then
|
||||
pass 'prepare-commit-msg does not overwrite existing content'
|
||||
else
|
||||
fail 'prepare-commit-msg does not overwrite existing content'
|
||||
fi
|
||||
|
||||
rm -f "$prepare_msg_file"
|
||||
touch "$prepare_msg_file"
|
||||
expect_success 'prepare-commit-msg skips for merge' scripts/git-hooks/prepare-commit-msg "$prepare_msg_file" "merge"
|
||||
if [ ! -s "$prepare_msg_file" ]; then
|
||||
pass 'prepare-commit-msg skips template for merge'
|
||||
else
|
||||
fail 'prepare-commit-msg skips template for merge'
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# hooks-install 测试
|
||||
# ============================================
|
||||
|
||||
expect_failure 'hooks-install rejects missing source hook' run_hooks_install_missing_source
|
||||
|
||||
# ============================================
|
||||
# pre-commit 测试
|
||||
# ============================================
|
||||
|
||||
cat > backend/pkg/buildinfo/hook_bad_test_fixture.go <<'EOF'
|
||||
package buildinfo
|
||||
|
||||
@@ -88,20 +195,20 @@ func hookBadTestFixture() {
|
||||
fmt.Println("bad")
|
||||
}
|
||||
EOF
|
||||
expect_failure 'pre-commit rejects Go lint errors' run_precommit_for backend/pkg/buildinfo/hook_bad_test_fixture.go
|
||||
expect_failure 'pre-commit rejects Go lint errors (delegated to _backend-lint)' run_precommit_for backend/pkg/buildinfo/hook_bad_test_fixture.go
|
||||
rm -f backend/pkg/buildinfo/hook_bad_test_fixture.go
|
||||
|
||||
cat > frontend/src/hook_bad_fixture.ts <<'EOF'
|
||||
console.log('bad')
|
||||
EOF
|
||||
expect_failure 'pre-commit rejects frontend lint errors' run_precommit_for frontend/src/hook_bad_fixture.ts
|
||||
expect_failure 'pre-commit rejects frontend lint errors (delegated to _frontend-check)' run_precommit_for frontend/src/hook_bad_fixture.ts
|
||||
rm -f frontend/src/hook_bad_fixture.ts
|
||||
|
||||
cat > frontend/src/hook_format_fixture.ts <<'EOF'
|
||||
const hookFormatFixture={foo:"bar"}
|
||||
export { hookFormatFixture }
|
||||
EOF
|
||||
expect_failure 'pre-commit rejects frontend format errors' run_precommit_for frontend/src/hook_format_fixture.ts
|
||||
expect_failure 'pre-commit rejects frontend format errors (delegated to _frontend-check)' run_precommit_for frontend/src/hook_format_fixture.ts
|
||||
rm -f frontend/src/hook_format_fixture.ts
|
||||
|
||||
cat > docs/hook-doc-fixture.md <<'EOF'
|
||||
@@ -110,16 +217,19 @@ EOF
|
||||
expect_success 'pre-commit skips non-code staged files' run_precommit_for docs/hook-doc-fixture.md
|
||||
rm -f docs/hook-doc-fixture.md
|
||||
|
||||
cat > docs/hook-conflict-fixture.md <<'EOF'
|
||||
<<<<<<< HEAD
|
||||
conflict
|
||||
=======
|
||||
other
|
||||
>>>>>>> branch
|
||||
EOF
|
||||
write_conflict docs/hook-conflict-fixture.md
|
||||
expect_failure 'pre-commit rejects conflict markers' run_precommit_for docs/hook-conflict-fixture.md
|
||||
rm -f docs/hook-conflict-fixture.md
|
||||
|
||||
index=$TMP_DIR/index
|
||||
rm -f "$index"
|
||||
GIT_INDEX_FILE=$index git read-tree HEAD
|
||||
write_conflict "$TMP_DIR/hook-conflict-fixture.sh"
|
||||
hash=$(git hash-object -w "$TMP_DIR/hook-conflict-fixture.sh")
|
||||
rm -f "$TMP_DIR/hook-conflict-fixture.sh"
|
||||
GIT_INDEX_FILE=$index git update-index --add --cacheinfo 100644 "$hash" "scripts/git-hooks/hook-conflict-fixture.sh"
|
||||
expect_failure 'pre-commit rejects conflict markers in hook scripts' env GIT_INDEX_FILE=$index make _hooks-pre-commit
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 40000 ]; do
|
||||
printf 'large hook fixture line\n'
|
||||
@@ -132,3 +242,32 @@ else
|
||||
fail 'pre-commit warns for large text files'
|
||||
fi
|
||||
rm -f docs/hook-large-fixture.txt
|
||||
|
||||
# LFS pointer 校验
|
||||
lfs_pointer='version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:abc123
|
||||
size 100
|
||||
'
|
||||
printf '%s\n' "$lfs_pointer" > "$TMP_DIR/lfs-pointer-fixture"
|
||||
hash=$(git hash-object -w "$TMP_DIR/lfs-pointer-fixture")
|
||||
index=$TMP_DIR/index
|
||||
rm -f "$index"
|
||||
GIT_INDEX_FILE=$index git read-tree HEAD
|
||||
GIT_INDEX_FILE=$index git update-index --add --cacheinfo 100644 "$hash" "assets/test-lfs-fixture.png"
|
||||
if GIT_INDEX_FILE=$index make _hooks-pre-commit > "$TMP_DIR/out" 2>&1; then
|
||||
pass 'pre-commit allows LFS pointer files'
|
||||
else
|
||||
cat "$TMP_DIR/out" >&2
|
||||
fail 'pre-commit allows LFS pointer files'
|
||||
fi
|
||||
|
||||
printf 'fake binary content\n' > "$TMP_DIR/lfs-bad-fixture"
|
||||
hash=$(git hash-object -w "$TMP_DIR/lfs-bad-fixture")
|
||||
rm -f "$index"
|
||||
GIT_INDEX_FILE=$index git read-tree HEAD
|
||||
GIT_INDEX_FILE=$index git update-index --add --cacheinfo 100644 "$hash" "assets/test-lfs-bad-fixture.png"
|
||||
if GIT_INDEX_FILE=$index make _hooks-pre-commit > "$TMP_DIR/out" 2>&1; then
|
||||
cat "$TMP_DIR/out" >&2
|
||||
fail 'pre-commit rejects non-pointer LFS files'
|
||||
fi
|
||||
pass 'pre-commit rejects non-pointer LFS files'
|
||||
|
||||
Reference in New Issue
Block a user