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:
83
Makefile
83
Makefile
@@ -70,15 +70,21 @@ clean: _backend-clean _frontend-clean _desktop-clean
|
|||||||
hooks-install:
|
hooks-install:
|
||||||
@hooks_dir=$$(git rev-parse --git-path hooks); \
|
@hooks_dir=$$(git rev-parse --git-path hooks); \
|
||||||
mkdir -p "$$hooks_dir"; \
|
mkdir -p "$$hooks_dir"; \
|
||||||
cp scripts/git-hooks/pre-commit "$$hooks_dir/pre-commit"; \
|
for hook in pre-commit commit-msg prepare-commit-msg; do \
|
||||||
cp scripts/git-hooks/commit-msg "$$hooks_dir/commit-msg"; \
|
src="scripts/git-hooks/$$hook"; \
|
||||||
chmod +x "$$hooks_dir/pre-commit" "$$hooks_dir/commit-msg"; \
|
if [ ! -f "$$src" ]; then \
|
||||||
|
printf 'ERROR: source hook not found: %s\n' "$$src" >&2; \
|
||||||
|
exit 1; \
|
||||||
|
fi; \
|
||||||
|
cp "$$src" "$$hooks_dir/$$hook"; \
|
||||||
|
chmod +x "$$hooks_dir/$$hook"; \
|
||||||
|
done; \
|
||||||
printf 'Installed Git hooks to %s\n' "$$hooks_dir"
|
printf 'Installed Git hooks to %s\n' "$$hooks_dir"
|
||||||
|
|
||||||
hooks-check:
|
hooks-check:
|
||||||
@hooks_dir=$$(git rev-parse --git-path hooks); \
|
@hooks_dir=$$(git rev-parse --git-path hooks); \
|
||||||
status=0; \
|
status=0; \
|
||||||
for hook in pre-commit commit-msg; do \
|
for hook in pre-commit commit-msg prepare-commit-msg; do \
|
||||||
if [ -x "$$hooks_dir/$$hook" ]; then \
|
if [ -x "$$hooks_dir/$$hook" ]; then \
|
||||||
printf 'OK: %s\n' "$$hook"; \
|
printf 'OK: %s\n' "$$hook"; \
|
||||||
else \
|
else \
|
||||||
@@ -92,17 +98,18 @@ hooks-test:
|
|||||||
@scripts/git-hooks/test-hooks.sh
|
@scripts/git-hooks/test-hooks.sh
|
||||||
|
|
||||||
_hooks-pre-commit:
|
_hooks-pre-commit:
|
||||||
@set -e; \
|
@set -ef; \
|
||||||
staged_files=$$(git diff --cached --name-only --diff-filter=ACM); \
|
staged_files=$$(git diff --cached --name-only --diff-filter=ACM); \
|
||||||
if [ -z "$$staged_files" ]; then \
|
if [ -z "$$staged_files" ]; then \
|
||||||
printf 'No staged files to check\n'; \
|
printf 'No staged files to check\n'; \
|
||||||
exit 0; \
|
exit 0; \
|
||||||
fi; \
|
fi; \
|
||||||
backend_pkgs=''; \
|
run_backend_lint=; \
|
||||||
versionctl_pkgs=''; \
|
run_versionctl_lint=; \
|
||||||
|
run_frontend_check=; \
|
||||||
|
lfs_patterns=$$(grep 'filter=lfs' .gitattributes 2>/dev/null | awk '{print $$1}' || true); \
|
||||||
for file in $$staged_files; do \
|
for file in $$staged_files; do \
|
||||||
[ -n "$$file" ] || continue; \
|
[ -n "$$file" ] || continue; \
|
||||||
case "$$file" in scripts/git-hooks/*) continue ;; esac; \
|
|
||||||
if git show ":$$file" 2>/dev/null | grep -Eq '^(<<<<<<<|=======|>>>>>>>)'; then \
|
if git show ":$$file" 2>/dev/null | grep -Eq '^(<<<<<<<|=======|>>>>>>>)'; then \
|
||||||
printf 'Found conflict markers in staged file: %s\n' "$$file" >&2; \
|
printf 'Found conflict markers in staged file: %s\n' "$$file" >&2; \
|
||||||
printf 'Resolve conflict markers before committing.\n' >&2; \
|
printf 'Resolve conflict markers before committing.\n' >&2; \
|
||||||
@@ -114,37 +121,41 @@ _hooks-pre-commit:
|
|||||||
printf 'Warning: large staged text file (%s bytes): %s\n' "$$size" "$$file" >&2; \
|
printf 'Warning: large staged text file (%s bytes): %s\n' "$$size" "$$file" >&2; \
|
||||||
fi; \
|
fi; \
|
||||||
fi; \
|
fi; \
|
||||||
|
if [ -n "$$lfs_patterns" ]; then \
|
||||||
|
for lfs_pat in $$lfs_patterns; do \
|
||||||
|
case "$$file" in $$lfs_pat) \
|
||||||
|
content=$$(git show ":$$file" 2>/dev/null | head -1); \
|
||||||
|
case "$$content" in \
|
||||||
|
"version https://git-lfs.github.com/spec/v1"*) ;; \
|
||||||
|
*) \
|
||||||
|
printf 'LFS-tracked file not using LFS pointer: %s\n' "$$file" >&2; \
|
||||||
|
printf 'Run "git lfs install" and re-add this file.\n' >&2; \
|
||||||
|
exit 1; \
|
||||||
|
;; \
|
||||||
|
esac; \
|
||||||
|
break; \
|
||||||
|
;; \
|
||||||
|
esac; \
|
||||||
|
done; \
|
||||||
|
fi; \
|
||||||
case "$$file" in \
|
case "$$file" in \
|
||||||
backend/*.go) \
|
backend/*.go) run_backend_lint=1 ;; \
|
||||||
dir=$$(dirname "$${file#backend/}"); \
|
versionctl/*.go) run_versionctl_lint=1 ;; \
|
||||||
case " $$backend_pkgs " in *" $$dir "*) ;; *) backend_pkgs="$$backend_pkgs $$dir" ;; esac; \
|
frontend/*.ts|frontend/*.tsx|frontend/*.scss) run_frontend_check=1 ;; \
|
||||||
;; \
|
|
||||||
versionctl/*.go) \
|
|
||||||
dir=$$(dirname "$${file#versionctl/}"); \
|
|
||||||
case " $$versionctl_pkgs " in *" $$dir "*) ;; *) versionctl_pkgs="$$versionctl_pkgs $$dir" ;; esac; \
|
|
||||||
;; \
|
|
||||||
frontend/*.ts|frontend/*.tsx) \
|
|
||||||
rel=$${file#frontend/}; \
|
|
||||||
printf 'Frontend lint: frontend/%s\n' "$$rel"; \
|
|
||||||
(cd frontend && bunx eslint "$$rel"); \
|
|
||||||
printf 'Frontend format: frontend/%s\n' "$$rel"; \
|
|
||||||
(cd frontend && bunx prettier --check "$$rel"); \
|
|
||||||
;; \
|
|
||||||
frontend/*.scss) \
|
|
||||||
rel=$${file#frontend/}; \
|
|
||||||
printf 'Frontend format: frontend/%s\n' "$$rel"; \
|
|
||||||
(cd frontend && bunx prettier --check "$$rel"); \
|
|
||||||
;; \
|
|
||||||
esac; \
|
esac; \
|
||||||
done; \
|
done; \
|
||||||
for dir in $$backend_pkgs; do \
|
if [ -n "$$run_backend_lint" ]; then \
|
||||||
printf 'Go lint: backend/%s\n' "$$dir"; \
|
printf 'Running backend lint...\n'; \
|
||||||
(cd backend && go tool golangci-lint run "$$dir/"); \
|
$(MAKE) _backend-lint; \
|
||||||
done; \
|
fi; \
|
||||||
for dir in $$versionctl_pkgs; do \
|
if [ -n "$$run_versionctl_lint" ]; then \
|
||||||
printf 'Go lint: versionctl/%s\n' "$$dir"; \
|
printf 'Running versionctl lint...\n'; \
|
||||||
(cd versionctl && go tool golangci-lint run "$$dir/"); \
|
$(MAKE) _versionctl-lint; \
|
||||||
done; \
|
fi; \
|
||||||
|
if [ -n "$$run_frontend_check" ]; then \
|
||||||
|
printf 'Running frontend check...\n'; \
|
||||||
|
$(MAKE) _frontend-check; \
|
||||||
|
fi; \
|
||||||
printf 'Pre-commit checks passed\n'
|
printf 'Pre-commit checks passed\n'
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|||||||
@@ -369,8 +369,9 @@ make desktop-clean # 清理 desktop 产物
|
|||||||
|
|
||||||
Git hooks 使用仓库内 `scripts/git-hooks/` 的原生脚本,不依赖额外 hook 框架。当前 hooks 包含:
|
Git hooks 使用仓库内 `scripts/git-hooks/` 的原生脚本,不依赖额外 hook 框架。当前 hooks 包含:
|
||||||
|
|
||||||
- pre-commit:检查 staged files 的冲突标记、Go lint、前端 lint/格式和大文件告警
|
- pre-commit:检查 staged files 的冲突标记、大文件告警和 LFS 指针,并按文件类型委托后端、versionctl、前端检查
|
||||||
- commit-msg:校验提交信息格式为 `类型: 简短描述`,描述需使用中文
|
- prepare-commit-msg:在编辑器打开时提供提交信息模板,辅助填写 `类型: 简短描述` 和多行说明
|
||||||
|
- commit-msg:校验提交信息格式为 `类型: 简短描述`,多行说明需在首行后保留空行;提交描述按项目规范使用中文,hook 不做字符集检测
|
||||||
|
|
||||||
## 版本与发布
|
## 版本与发布
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,8 @@ bun run build
|
|||||||
```bash
|
```bash
|
||||||
bun run lint # ESLint 检查
|
bun run lint # ESLint 检查
|
||||||
bun run format:check # Prettier 格式检查
|
bun run format:check # Prettier 格式检查
|
||||||
bun run check # 同时检查 lint 和格式
|
bun run typecheck # TypeScript 类型检查
|
||||||
|
bun run check # 同时检查类型、lint 和格式
|
||||||
```
|
```
|
||||||
|
|
||||||
### 代码格式化
|
### 代码格式化
|
||||||
|
|||||||
@@ -6,12 +6,13 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && bun run check && vite build",
|
"build": "bun run check && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"format:check": "prettier --check .",
|
"format:check": "prettier --check .",
|
||||||
"check": "bun run lint && bun run format:check",
|
"typecheck": "tsc -b",
|
||||||
|
"check": "bun run typecheck && bun run lint && bun run format:check",
|
||||||
"fix": "bun run lint:fix && bun run format",
|
"fix": "bun run lint:fix && bun run format",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ export function ModelTable({ providerId, onAdd, onEdit }: ModelTableProps) {
|
|||||||
text: id,
|
text: id,
|
||||||
onCopy: () => MessagePlugin.success('已复制统一模型 ID'),
|
onCopy: () => MessagePlugin.success('已复制统一模型 ID'),
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{''}
|
||||||
|
</Typography.Text>
|
||||||
</span>
|
</span>
|
||||||
) : null
|
) : null
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ export function ProviderTable({
|
|||||||
text: row.baseUrl,
|
text: row.baseUrl,
|
||||||
onCopy: () => MessagePlugin.success('已复制 Base URL'),
|
onCopy: () => MessagePlugin.success('已复制 Base URL'),
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{''}
|
||||||
|
</Typography.Text>
|
||||||
</span>
|
</span>
|
||||||
) : null,
|
) : null,
|
||||||
},
|
},
|
||||||
@@ -87,7 +89,9 @@ export function ProviderTable({
|
|||||||
text: row.apiKey,
|
text: row.apiKey,
|
||||||
onCopy: () => MessagePlugin.success('已复制 API Key'),
|
onCopy: () => MessagePlugin.success('已复制 API Key'),
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{''}
|
||||||
|
</Typography.Text>
|
||||||
</span>
|
</span>
|
||||||
) : null,
|
) : null,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -49,13 +49,14 @@ TBD - 定义前端 ESLint 规则配置、构建集成 lint 检查、以及自定
|
|||||||
|
|
||||||
### Requirement: 构建集成 lint 检查
|
### Requirement: 构建集成 lint 检查
|
||||||
|
|
||||||
前端 SHALL 在 `build` 命令中集成 ESLint 检查和 Prettier 格式检查。
|
前端 SHALL 在 `build` 命令中集成 TypeScript 类型检查、ESLint 检查和 Prettier 格式检查。
|
||||||
|
|
||||||
#### Scenario: 构建时执行 lint 和格式检查
|
#### Scenario: 构建时执行类型检查、lint 和格式检查
|
||||||
|
|
||||||
- **WHEN** 执行 `bun run build`
|
- **WHEN** 执行 `bun run build`
|
||||||
- **THEN** 构建 SHALL 依次执行 `tsc -b`、`bun run check`、`vite build`
|
- **THEN** 构建 SHALL 依次执行 `bun run check`、`vite build`
|
||||||
- **THEN** `bun run check` SHALL 执行 `bun run lint && bun run format:check`
|
- **THEN** `bun run check` SHALL 依次执行 `bun run typecheck`、`bun run lint`、`bun run format:check`
|
||||||
|
- **THEN** 若 `tsc -b` 报告类型错误,构建 SHALL 中断
|
||||||
- **THEN** 若 `eslint .` 报告任何错误,构建 SHALL 中断
|
- **THEN** 若 `eslint .` 报告任何错误,构建 SHALL 中断
|
||||||
- **THEN** 若 `prettier --check .` 报告任何格式问题,构建 SHALL 中断
|
- **THEN** 若 `prettier --check .` 报告任何格式问题,构建 SHALL 中断
|
||||||
|
|
||||||
@@ -77,8 +78,13 @@ TBD - 定义前端 ESLint 规则配置、构建集成 lint 检查、以及自定
|
|||||||
#### Scenario: 统一检查命令
|
#### Scenario: 统一检查命令
|
||||||
|
|
||||||
- **WHEN** 执行 `bun run check`
|
- **WHEN** 执行 `bun run check`
|
||||||
- **THEN** SHALL 运行 `bun run lint && bun run format:check`
|
- **THEN** SHALL 依次运行 `bun run typecheck`、`bun run lint`、`bun run format:check`
|
||||||
- **THEN** lint 错误和格式问题 SHALL 都被检查
|
- **THEN** 类型错误、lint 错误和格式问题 SHALL 都被检查
|
||||||
|
|
||||||
|
#### Scenario: 单独执行类型检查
|
||||||
|
|
||||||
|
- **WHEN** 执行 `bun run typecheck`
|
||||||
|
- **THEN** SHALL 运行 `tsc -b`
|
||||||
|
|
||||||
#### Scenario: 统一修复命令
|
#### Scenario: 统一修复命令
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,33 @@
|
|||||||
|
|
||||||
### Requirement: pre-commit hook 快速检查
|
### Requirement: pre-commit hook 快速检查
|
||||||
|
|
||||||
pre-commit hook SHALL 在 `git commit` 执行前对 staged files 进行快速检查,仅检查本次提交涉及的文件。
|
pre-commit hook SHALL 在 `git commit` 执行前对 staged files 进行快速检查。非代码检查(冲突标记、大文件告警、LFS 指针)SHALL 在 `_hooks-pre-commit` 中直接实现;代码检查(Go 后端、Go versionctl、前端)SHALL 根据 staged 文件类型有条件地委托给已有 Makefile target(`_backend-lint`、`_versionctl-lint`、`_frontend-check`),不再内联独立的 lint 命令。
|
||||||
|
|
||||||
#### Scenario: 无 Go 和前端文件变更时跳过
|
#### Scenario: 无 Go 和前端文件变更时跳过代码检查
|
||||||
|
|
||||||
- **WHEN** staged files 中既无 `.go` 文件也无 `.ts`/`.tsx`/`.scss` 文件
|
- **WHEN** staged files 中既无 `.go` 文件也无 `.ts`/`.tsx`/`.scss` 文件
|
||||||
- **THEN** pre-commit hook SHALL 直接通过,不运行任何 linter
|
- **THEN** pre-commit hook SHALL 跳过代码检查委托,仅执行非代码检查
|
||||||
|
|
||||||
|
#### Scenario: Go 文件变更时委托后端 lint
|
||||||
|
|
||||||
|
- **WHEN** staged files 中包含 `backend/*.go` 文件
|
||||||
|
- **THEN** pre-commit hook SHALL 委托 `_backend-lint` target 进行 Go 代码检查
|
||||||
|
- **THEN** `_backend-lint` SHALL 复用 `backend/.golangci.yml` 配置
|
||||||
|
- **THEN** 若 lint 报告任何错误,commit SHALL 被阻止
|
||||||
|
|
||||||
|
#### Scenario: versionctl Go 文件变更时委托 versionctl lint
|
||||||
|
|
||||||
|
- **WHEN** staged files 中包含 `versionctl/*.go` 文件
|
||||||
|
- **THEN** pre-commit hook SHALL 委托 `_versionctl-lint` target 进行 Go 代码检查
|
||||||
|
- **THEN** `_versionctl-lint` SHALL 复用 `versionctl/.golangci.yml` 配置
|
||||||
|
- **THEN** 若 lint 报告任何错误,commit SHALL 被阻止
|
||||||
|
|
||||||
|
#### Scenario: 前端文件变更时委托前端检查
|
||||||
|
|
||||||
|
- **WHEN** staged files 中包含 `.ts`、`.tsx` 或 `.scss` 文件
|
||||||
|
- **THEN** pre-commit hook SHALL 委托 `_frontend-check` target 进行前端代码检查
|
||||||
|
- **THEN** `_frontend-check` SHALL 运行 `bun run check`(包含 `tsc -b` TypeScript 类型检查、ESLint 和 Prettier 格式检查)
|
||||||
|
- **THEN** 若检查报告任何错误,commit SHALL 被阻止
|
||||||
|
|
||||||
#### Scenario: 冲突标记检测
|
#### Scenario: 冲突标记检测
|
||||||
|
|
||||||
@@ -21,37 +42,26 @@ pre-commit hook SHALL 在 `git commit` 执行前对 staged files 进行快速检
|
|||||||
- **THEN** pre-commit hook SHALL 报告错误并列出包含冲突的文件名
|
- **THEN** pre-commit hook SHALL 报告错误并列出包含冲突的文件名
|
||||||
- **THEN** commit SHALL 被阻止
|
- **THEN** commit SHALL 被阻止
|
||||||
|
|
||||||
#### Scenario: Go 文件 lint 检查
|
|
||||||
|
|
||||||
- **WHEN** staged files 中包含 `.go` 文件
|
|
||||||
- **THEN** pre-commit hook SHALL 对 staged `.go` 文件运行 `golangci-lint run`(复用 `backend/.golangci.yml` 配置)
|
|
||||||
- **THEN** 若 lint 报告任何错误,commit SHALL 被阻止
|
|
||||||
|
|
||||||
#### Scenario: 前端文件 lint 检查
|
|
||||||
|
|
||||||
- **WHEN** staged files 中包含 `.ts` 或 `.tsx` 文件
|
|
||||||
- **THEN** pre-commit hook SHALL 对 staged 前端文件运行 ESLint(复用 `frontend/eslint.config.js` 配置)
|
|
||||||
- **THEN** 若 ESLint 报告任何错误,commit SHALL 被阻止
|
|
||||||
|
|
||||||
#### Scenario: 前端文件格式检查
|
|
||||||
|
|
||||||
- **WHEN** staged files 中包含 `.ts`、`.tsx` 或 `.scss` 文件
|
|
||||||
- **THEN** pre-commit hook SHALL 对 staged 前端文件运行 Prettier 格式检查(复用 `frontend/.prettierrc` 配置)
|
|
||||||
- **THEN** 若存在格式不符合规范的文件,commit SHALL 被阻止
|
|
||||||
|
|
||||||
#### Scenario: 大文件告警
|
#### Scenario: 大文件告警
|
||||||
|
|
||||||
- **WHEN** staged files 中存在超过 500KB 的文本文件
|
- **WHEN** staged files 中存在超过 500KB 的文本文件
|
||||||
- **THEN** pre-commit hook SHALL 输出警告信息(不阻止提交),提示检查是否误提交
|
- **THEN** pre-commit hook SHALL 输出警告信息(不阻止提交),提示检查是否误提交
|
||||||
|
|
||||||
|
#### Scenario: LFS 指针校验
|
||||||
|
|
||||||
|
- **WHEN** staged files 匹配 `.gitattributes` 中 `filter=lfs` 的路径模式
|
||||||
|
- **THEN** pre-commit hook SHALL 检查 staged 内容是否为 LFS 指针格式(`version https://git-lfs.github.com/spec/v1`)
|
||||||
|
- **THEN** 若内容不是 LFS 指针格式,commit SHALL 被阻止,并提示安装 git-lfs
|
||||||
|
- **THEN** 若 staged files 不匹配任何 `filter=lfs` 路径模式,SHALL 跳过此检查
|
||||||
|
|
||||||
#### Scenario: commit 被阻止时显示修复提示
|
#### Scenario: commit 被阻止时显示修复提示
|
||||||
|
|
||||||
- **WHEN** pre-commit hook 检查失败
|
- **WHEN** pre-commit hook 检查失败
|
||||||
- **THEN** hook SHALL 输出明确的修复提示(如 `bun run fix`、手动解决冲突标记等)
|
- **THEN** hook SHALL 输出明确的修复提示(如 `make lint` 修复代码问题、手动解决冲突标记等)
|
||||||
|
|
||||||
### Requirement: commit-msg hook 校验提交信息格式
|
### Requirement: commit-msg hook 校验提交信息格式
|
||||||
|
|
||||||
commit-msg hook SHALL 在 `git commit` 输入提交信息后校验格式,确保符合项目规范。提交描述 SHALL 使用中文;版本号、英文专有名词可与中文描述混用。
|
commit-msg hook SHALL 在 `git commit` 输入提交信息后校验格式,确保首行符合项目规范。提交描述按项目规范应使用中文,但 hook SHALL NOT 通过 Python/CJK 字符集检测强制判断描述语言,以避免引入新的运行时依赖。
|
||||||
|
|
||||||
#### Scenario: 合法格式通过
|
#### Scenario: 合法格式通过
|
||||||
|
|
||||||
@@ -63,12 +73,6 @@ commit-msg hook SHALL 在 `git commit` 输入提交信息后校验格式,确
|
|||||||
- **WHEN** 提交信息首行使用的类型不在允许列表中(如 `update: xxx`)
|
- **WHEN** 提交信息首行使用的类型不在允许列表中(如 `update: xxx`)
|
||||||
- **THEN** commit-msg hook SHALL 报告错误,显示允许的类型列表,commit SHALL 被阻止
|
- **THEN** commit-msg hook SHALL 报告错误,显示允许的类型列表,commit SHALL 被阻止
|
||||||
|
|
||||||
#### Scenario: 英文描述被拒绝
|
|
||||||
|
|
||||||
- **WHEN** 提交信息首行为 `feat: add auth`
|
|
||||||
- **THEN** commit-msg hook SHALL 报告错误,提示提交描述需使用中文
|
|
||||||
- **THEN** commit SHALL 被阻止
|
|
||||||
|
|
||||||
#### Scenario: 缺少冒号空格被拒绝
|
#### Scenario: 缺少冒号空格被拒绝
|
||||||
|
|
||||||
- **WHEN** 提交信息首行为 `feat:xxx`(冒号后无空格)或 `feat xxx`
|
- **WHEN** 提交信息首行为 `feat:xxx`(冒号后无空格)或 `feat xxx`
|
||||||
@@ -89,16 +93,35 @@ commit-msg hook SHALL 在 `git commit` 输入提交信息后校验格式,确
|
|||||||
- **WHEN** commit-msg hook 检查失败
|
- **WHEN** commit-msg hook 检查失败
|
||||||
- **THEN** hook SHALL 输出包含正确格式示例的错误信息(如 `feat: 添加供应商批量管理功能`)
|
- **THEN** hook SHALL 输出包含正确格式示例的错误信息(如 `feat: 添加供应商批量管理功能`)
|
||||||
|
|
||||||
|
#### Scenario: 不执行字符集检测
|
||||||
|
|
||||||
|
- **WHEN** 提交信息首行格式合法且类型合法,但描述部分不包含 CJK 字符(如 `feat: add hook tests`)
|
||||||
|
- **THEN** commit-msg hook SHALL 通过
|
||||||
|
- **THEN** hook SHALL NOT 调用 `python3` 或其他额外运行时做 Unicode/CJK 检测
|
||||||
|
|
||||||
|
#### Scenario: 多行格式校验
|
||||||
|
|
||||||
|
- **WHEN** 提交信息忽略 `#` 注释行后,第三行及之后存在任一非空详细说明行
|
||||||
|
- **THEN** commit-msg hook SHALL 检查第二行是否为空行
|
||||||
|
- **THEN** 若第二行非空行,commit SHALL 被阻止,提示首行后应空行再写详细描述
|
||||||
|
|
||||||
|
#### Scenario: 模板注释不参与校验
|
||||||
|
|
||||||
|
- **WHEN** 提交信息文件中包含 prepare-commit-msg 写入的 `#` 注释模板
|
||||||
|
- **THEN** commit-msg hook SHALL 忽略这些注释行
|
||||||
|
- **THEN** 注释行 SHALL NOT 导致首行格式、多行空行分隔校验失败
|
||||||
|
|
||||||
### Requirement: hooks-install 安装命令
|
### Requirement: hooks-install 安装命令
|
||||||
|
|
||||||
`make hooks-install` SHALL 将 `scripts/git-hooks/` 下的 hook 脚本安装到 `.git/hooks/`,不覆盖 Git LFS 管理的 hook。
|
`make hooks-install` SHALL 将 `scripts/git-hooks/` 下的 hook 脚本安装到 `.git/hooks/`,不覆盖 Git LFS 管理的 hook。
|
||||||
|
|
||||||
#### Scenario: 安装 pre-commit 和 commit-msg
|
#### Scenario: 安装所有 hook 脚本
|
||||||
|
|
||||||
- **WHEN** 执行 `make hooks-install`
|
- **WHEN** 执行 `make hooks-install`
|
||||||
- **THEN** `scripts/git-hooks/pre-commit` SHALL 被复制到 `.git/hooks/pre-commit`
|
- **THEN** `scripts/git-hooks/pre-commit` SHALL 被复制到 `.git/hooks/pre-commit`
|
||||||
- **THEN** `scripts/git-hooks/commit-msg` SHALL 被复制到 `.git/hooks/commit-msg`
|
- **THEN** `scripts/git-hooks/commit-msg` SHALL 被复制到 `.git/hooks/commit-msg`
|
||||||
- **THEN** 两个文件 SHALL 被设置为可执行(`chmod +x`)
|
- **THEN** `scripts/git-hooks/prepare-commit-msg` SHALL 被复制到 `.git/hooks/prepare-commit-msg`
|
||||||
|
- **THEN** 所有复制文件 SHALL 被设置为可执行(`chmod +x`)
|
||||||
|
|
||||||
#### Scenario: 不覆盖 LFS 管理的 hook
|
#### Scenario: 不覆盖 LFS 管理的 hook
|
||||||
|
|
||||||
@@ -113,9 +136,15 @@ commit-msg hook SHALL 在 `git commit` 输入提交信息后校验格式,确
|
|||||||
#### Scenario: hooks-check 验证安装状态
|
#### Scenario: hooks-check 验证安装状态
|
||||||
|
|
||||||
- **WHEN** 执行 `make hooks-check`
|
- **WHEN** 执行 `make hooks-check`
|
||||||
- **THEN** 命令 SHALL 检查 `.git/hooks/pre-commit` 和 `.git/hooks/commit-msg` 是否存在且可执行
|
- **THEN** 命令 SHALL 检查 `.git/hooks/pre-commit`、`.git/hooks/commit-msg`、`.git/hooks/prepare-commit-msg` 是否存在且可执行
|
||||||
- **THEN** SHALL 输出每个 hook 的安装状态
|
- **THEN** SHALL 输出每个 hook 的安装状态
|
||||||
|
|
||||||
|
#### Scenario: 安装前验证 source 文件存在
|
||||||
|
|
||||||
|
- **WHEN** 执行 `make hooks-install`
|
||||||
|
- **THEN** 命令 SHALL 在复制前验证每个 source 文件(`scripts/git-hooks/<hook-name>`)是否存在
|
||||||
|
- **THEN** 若 source 文件不存在,命令 SHALL 报告错误并返回非零退出码
|
||||||
|
|
||||||
### Requirement: hooks-test 回归测试命令
|
### Requirement: hooks-test 回归测试命令
|
||||||
|
|
||||||
`make hooks-test` SHALL 运行仓库内 hook 回归测试,覆盖 commit-msg 格式校验和 pre-commit staged-file 检查,不污染真实 git index。
|
`make hooks-test` SHALL 运行仓库内 hook 回归测试,覆盖 commit-msg 格式校验和 pre-commit staged-file 检查,不污染真实 git index。
|
||||||
@@ -146,19 +175,19 @@ pre-commit 和 commit-msg hook 脚本 SHALL 可在 macOS 和 Windows(Git Bash
|
|||||||
|
|
||||||
### Requirement: pre-commit 核心逻辑在 Makefile 中复用
|
### Requirement: pre-commit 核心逻辑在 Makefile 中复用
|
||||||
|
|
||||||
pre-commit hook 的检查逻辑 SHALL 通过 Makefile target 调用项目已有工具链,不重复实现 hook 框架逻辑。commit-msg hook SHALL 在脚本内直接完成格式校验。
|
pre-commit hook 的检查逻辑 SHALL 通过 Makefile target 调用项目已有工具链,不重复实现。非代码检查(冲突标记、大文件、LFS 指针)SHALL 在 `_hooks-pre-commit` 中直接实现;代码检查 SHALL 委托 `_backend-lint`、`_versionctl-lint`、`_frontend-check` target。
|
||||||
|
|
||||||
#### Scenario: Go lint 复用后端配置
|
#### Scenario: Go lint 委托后端 lint target
|
||||||
|
|
||||||
- **WHEN** pre-commit 需要检查 Go 文件
|
- **WHEN** pre-commit 需要检查 Go 文件
|
||||||
- **THEN** SHALL 调用 Makefile 逻辑,在 `backend/` 目录对 staged `.go` 文件运行 `go tool golangci-lint run`
|
- **THEN** SHALL 委托 `_backend-lint` 或 `_versionctl-lint` target(根据文件路径 `backend/` vs `versionctl/`)
|
||||||
- **THEN** SHALL 复用 `backend/.golangci.yml` 中的 lint 配置
|
- **THEN** SHALL NOT 在 `_hooks-pre-commit` 中内联 `golangci-lint` 命令
|
||||||
|
|
||||||
#### Scenario: 前端 lint 使用 staged 文件参数
|
#### Scenario: 前端检查委托前端 check target
|
||||||
|
|
||||||
- **WHEN** pre-commit 需要检查前端文件
|
- **WHEN** pre-commit 需要检查前端文件
|
||||||
- **THEN** SHALL 调用 Makefile 逻辑,在 `frontend/` 目录对 staged 前端文件运行 ESLint 和 Prettier 的文件参数模式
|
- **THEN** SHALL 委托 `_frontend-check` target
|
||||||
- **THEN** SHALL NOT 在 pre-commit 阶段运行全量 `bun run check`
|
- **THEN** SHALL NOT 在 `_hooks-pre-commit` 中内联 `eslint` 或 `prettier` 命令
|
||||||
|
|
||||||
#### Scenario: 终端直接调试
|
#### Scenario: 终端直接调试
|
||||||
|
|
||||||
|
|||||||
57
openspec/specs/prepare-commit-msg-hook/spec.md
Normal file
57
openspec/specs/prepare-commit-msg-hook/spec.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# prepare-commit-msg-hook
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
定义 prepare-commit-msg Git hook,在 `git commit` 编辑器打开时为开发者提供提交信息模板。
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Requirement: prepare-commit-msg hook 提供提交信息模板
|
||||||
|
|
||||||
|
prepare-commit-msg hook SHALL 在 `git commit` 打开编辑器时,将规范格式的提交信息模板预填充到提交信息文件中,辅助开发者编写符合项目规范的多行提交信息。
|
||||||
|
|
||||||
|
#### Scenario: 模板预填充到提交信息文件
|
||||||
|
|
||||||
|
- **WHEN** `git commit` 被执行且编辑器打开提交信息文件
|
||||||
|
- **THEN** prepare-commit-msg hook SHALL 在提交信息文件中写入模板内容
|
||||||
|
- **THEN** 模板 SHALL 包含注释行(以 `#` 开头)引导开发者填写规范格式
|
||||||
|
|
||||||
|
#### Scenario: 模板包含格式引导
|
||||||
|
|
||||||
|
- **WHEN** 模板被写入提交信息文件
|
||||||
|
- **THEN** 模板 SHALL 包含首行格式提示:`# <类型>: <简短中文描述>`
|
||||||
|
- **THEN** 模板 SHALL 包含空行占位符
|
||||||
|
- **THEN** 模板 SHALL 包含详细描述区:`# <详细说明>`
|
||||||
|
- **THEN** 模板 SHALL 列出可用类型:`feat / fix / refactor / docs / style / test / chore`
|
||||||
|
- **THEN** 模板 SHALL 包含示例:`feat: 添加供应商批量管理功能`
|
||||||
|
|
||||||
|
#### Scenario: 注释行不被提交
|
||||||
|
|
||||||
|
- **WHEN** 用户在编辑器中基于模板填写提交信息并保存
|
||||||
|
- **THEN** 以 `#` 开头的模板注释行 SHALL 被 Git 作为注释过滤,不会成为提交信息的一部分
|
||||||
|
|
||||||
|
#### Scenario: 已有提交信息时跳过
|
||||||
|
|
||||||
|
- **WHEN** 提交信息文件已包含非注释内容(如 `-m` 参数指定、`git commit --amend`、merge commit、cherry-pick)
|
||||||
|
- **THEN** prepare-commit-msg hook SHALL NOT 覆盖已有内容,直接退出
|
||||||
|
|
||||||
|
#### Scenario: Git 默认注释不阻止模板写入
|
||||||
|
|
||||||
|
- **WHEN** 提交信息文件只包含空行或 Git 默认生成的 `#` 注释行
|
||||||
|
- **THEN** prepare-commit-msg hook SHALL 将其视为没有已有提交信息
|
||||||
|
- **THEN** hook SHALL 在文件顶部写入模板,并保留 Git 原有注释内容
|
||||||
|
|
||||||
|
### Requirement: 通过 hooks-install 安装
|
||||||
|
|
||||||
|
prepare-commit-msg hook SHALL 随 `make hooks-install` 一起安装到 `.git/hooks/`。
|
||||||
|
|
||||||
|
#### Scenario: 安装 prepare-commit-msg
|
||||||
|
|
||||||
|
- **WHEN** 执行 `make hooks-install`
|
||||||
|
- **THEN** `scripts/git-hooks/prepare-commit-msg` SHALL 被复制到 `.git/hooks/prepare-commit-msg`
|
||||||
|
- **THEN** 该文件 SHALL 被设置为可执行(`chmod +x`)
|
||||||
|
|
||||||
|
#### Scenario: hooks-check 验证安装状态
|
||||||
|
|
||||||
|
- **WHEN** 执行 `make hooks-check`
|
||||||
|
- **THEN** 命令 SHALL 检查 `.git/hooks/prepare-commit-msg` 是否存在且可执行
|
||||||
@@ -189,7 +189,8 @@
|
|||||||
|
|
||||||
- `format = "prettier --write ."` — 格式化所有文件
|
- `format = "prettier --write ."` — 格式化所有文件
|
||||||
- `format:check = "prettier --check ."` — 检查文件格式
|
- `format:check = "prettier --check ."` — 检查文件格式
|
||||||
- `check = "bun run lint && bun run format:check"` — 检查 lint 和格式
|
- `typecheck = "tsc -b"` — TypeScript 类型检查
|
||||||
|
- `check = "bun run typecheck && bun run lint && bun run format:check"` — 检查类型、lint 和格式
|
||||||
- `fix = "bun run lint:fix && bun run format"` — 修复 lint 问题并格式化
|
- `fix = "bun run lint:fix && bun run format"` — 修复 lint 问题并格式化
|
||||||
|
|
||||||
#### Scenario: 运行格式化命令
|
#### Scenario: 运行格式化命令
|
||||||
@@ -207,8 +208,14 @@
|
|||||||
#### Scenario: 运行统一检查命令
|
#### Scenario: 运行统一检查命令
|
||||||
|
|
||||||
- **WHEN** 执行 `bun run check`
|
- **WHEN** 执行 `bun run check`
|
||||||
- **THEN** SHALL 运行 `bun run lint && bun run format:check`
|
- **THEN** SHALL 依次运行 `bun run typecheck`、`bun run lint`、`bun run format:check`
|
||||||
- **THEN** lint 错误和格式问题 SHALL 都被检查
|
- **THEN** TypeScript 类型错误、lint 错误和格式问题 SHALL 都被检查
|
||||||
|
|
||||||
|
#### Scenario: 运行类型检查命令
|
||||||
|
|
||||||
|
- **WHEN** 执行 `bun run typecheck`
|
||||||
|
- **THEN** SHALL 运行 `tsc -b`
|
||||||
|
- **THEN** TypeScript 类型错误 SHALL 报告错误
|
||||||
|
|
||||||
#### Scenario: 运行统一修复命令
|
#### Scenario: 运行统一修复命令
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,33 @@ if [ ! -f "$MSG_FILE" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
case "$FIRST_LINE" in
|
||||||
Merge*)
|
Merge*)
|
||||||
@@ -31,12 +57,11 @@ EOF
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
if [ ${#FIRST_LINE} -gt 72 ]; then
|
||||||
printf '%s\n' '警告: 提交信息首行超过 72 个字符,建议保持简短。' >&2
|
printf '%s\n' '警告: 提交信息首行超过 72 个字符,建议保持简短。' >&2
|
||||||
fi
|
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 \
|
frontend/src/hook_format_fixture.ts \
|
||||||
docs/hook-doc-fixture.md \
|
docs/hook-doc-fixture.md \
|
||||||
docs/hook-conflict-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"
|
rm -rf "$TMP_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +37,14 @@ write_msg() {
|
|||||||
printf '%s\n' "$*" > "$file"
|
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() {
|
expect_success() {
|
||||||
name=$1
|
name=$1
|
||||||
shift
|
shift
|
||||||
@@ -66,12 +76,34 @@ run_precommit_for() {
|
|||||||
GIT_INDEX_FILE=$index make _hooks-pre-commit
|
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
|
MSG_FILE=$TMP_DIR/commit-msg.txt
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# commit-msg 测试
|
||||||
|
# ============================================
|
||||||
|
|
||||||
write_msg "$MSG_FILE" 'feat: 添加 hook 测试'
|
write_msg "$MSG_FILE" 'feat: 添加 hook 测试'
|
||||||
expect_success 'commit-msg accepts Chinese description' scripts/git-hooks/commit-msg "$MSG_FILE"
|
expect_success 'commit-msg accepts Chinese description' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||||
|
|
||||||
write_msg "$MSG_FILE" 'feat: add hook tests'
|
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 测试'
|
write_msg "$MSG_FILE" 'update: 添加 hook 测试'
|
||||||
expect_failure 'commit-msg rejects invalid type' scripts/git-hooks/commit-msg "$MSG_FILE"
|
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'
|
write_msg "$MSG_FILE" 'Merge branch feature'
|
||||||
expect_success 'commit-msg accepts merge commits' scripts/git-hooks/commit-msg "$MSG_FILE"
|
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'
|
cat > backend/pkg/buildinfo/hook_bad_test_fixture.go <<'EOF'
|
||||||
package buildinfo
|
package buildinfo
|
||||||
|
|
||||||
@@ -88,20 +195,20 @@ func hookBadTestFixture() {
|
|||||||
fmt.Println("bad")
|
fmt.Println("bad")
|
||||||
}
|
}
|
||||||
EOF
|
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
|
rm -f backend/pkg/buildinfo/hook_bad_test_fixture.go
|
||||||
|
|
||||||
cat > frontend/src/hook_bad_fixture.ts <<'EOF'
|
cat > frontend/src/hook_bad_fixture.ts <<'EOF'
|
||||||
console.log('bad')
|
console.log('bad')
|
||||||
EOF
|
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
|
rm -f frontend/src/hook_bad_fixture.ts
|
||||||
|
|
||||||
cat > frontend/src/hook_format_fixture.ts <<'EOF'
|
cat > frontend/src/hook_format_fixture.ts <<'EOF'
|
||||||
const hookFormatFixture={foo:"bar"}
|
const hookFormatFixture={foo:"bar"}
|
||||||
export { hookFormatFixture }
|
export { hookFormatFixture }
|
||||||
EOF
|
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
|
rm -f frontend/src/hook_format_fixture.ts
|
||||||
|
|
||||||
cat > docs/hook-doc-fixture.md <<'EOF'
|
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
|
expect_success 'pre-commit skips non-code staged files' run_precommit_for docs/hook-doc-fixture.md
|
||||||
rm -f docs/hook-doc-fixture.md
|
rm -f docs/hook-doc-fixture.md
|
||||||
|
|
||||||
cat > docs/hook-conflict-fixture.md <<'EOF'
|
write_conflict docs/hook-conflict-fixture.md
|
||||||
<<<<<<< HEAD
|
|
||||||
conflict
|
|
||||||
=======
|
|
||||||
other
|
|
||||||
>>>>>>> branch
|
|
||||||
EOF
|
|
||||||
expect_failure 'pre-commit rejects conflict markers' run_precommit_for 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
|
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
|
i=0
|
||||||
while [ "$i" -lt 40000 ]; do
|
while [ "$i" -lt 40000 ]; do
|
||||||
printf 'large hook fixture line\n'
|
printf 'large hook fixture line\n'
|
||||||
@@ -132,3 +242,32 @@ else
|
|||||||
fail 'pre-commit warns for large text files'
|
fail 'pre-commit warns for large text files'
|
||||||
fi
|
fi
|
||||||
rm -f docs/hook-large-fixture.txt
|
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