- add prepare.yml: install tools + maintain shared bare repo, anchors the workflow_run chain - add checks.yml: merge lint + full test suite into a single workflow_run consumer job - remove standards-check.yml and test.yml (superseded by checks.yml) - convert sync-tsl-playbook and update-thirdparty-skills to worktree-model consumers - unify workflow/job/step display names and emoji
158 lines
6.8 KiB
YAML
158 lines
6.8 KiB
YAML
name: 🔎 校验
|
||
|
||
on:
|
||
workflow_run:
|
||
workflows: ["🧰 准备环境"]
|
||
types:
|
||
- completed
|
||
|
||
concurrency:
|
||
group: checks-${{ github.repository }}-${{ github.event.workflow_run.head_sha }}
|
||
cancel-in-progress: true
|
||
|
||
# ==========================================
|
||
# 🔧 配置区域 - 校验参数
|
||
# ==========================================
|
||
env:
|
||
# Prepare 完成后经 workflow_run 触发;Prepare 已装工具 + fetch 到共享 bare 仓库。
|
||
# 本 workflow 只做只读校验(lint + 测试),不推任何分支,故 token 仅用于
|
||
# 极端情况下补取缺失提交(正常路径 Prepare 已 fetch 到位)。
|
||
#
|
||
# 安全:ACCESS_TOKEN 不放在 workflow 级 env。lint/test 步骤会执行上游提交
|
||
# (PR 场景为不受信输入)的脚本,其环境里不应出现 token;仅下放到
|
||
# prepare_worktree 步骤。
|
||
WORKSPACE_ROOT: "/data/workspace"
|
||
SOURCE_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||
SOURCE_SHA: ${{ github.event.workflow_run.head_sha }}
|
||
SOURCE_EVENT: ${{ github.event.workflow_run.event }}
|
||
COMMIT_LINT_REQUIRE_EMOJI: "1"
|
||
|
||
jobs:
|
||
checks:
|
||
# 仅在 Prepare 成功后触发。Prepare 覆盖 push(main) / pull_request /
|
||
# schedule / dispatch,故 checks 在这些场景均会运行。
|
||
# lint 与 test 合并为单 job:共用一个临时 worktree,准备一次,顺序执行
|
||
# (lint 在前 fail-fast,失败即不跑 test)。二者跑同一 SOURCE_SHA、皆只读、
|
||
# 无依赖,无需拆分为并行 job(那会重复整段 worktree 准备逻辑 + 多一个 worktree)。
|
||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||
name: 🔎 规范检查 + 全量测试
|
||
runs-on: standard-ubuntu-22
|
||
permissions:
|
||
contents: read
|
||
|
||
steps:
|
||
- name: 📥 准备临时 Worktree
|
||
id: prepare_worktree
|
||
shell: bash
|
||
env:
|
||
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
|
||
run: |
|
||
set -euo pipefail
|
||
|
||
REPO_NAME="${{ github.event.repository.name }}"
|
||
HEAD_SHA="${SOURCE_SHA}"
|
||
REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"
|
||
WORKTREE_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/worktrees/checks-${{ github.run_id }}-${{ github.run_attempt }}"
|
||
WORKTREE_LOCK="${WORKSPACE_ROOT}/${REPO_NAME}/worktree-admin.lock"
|
||
|
||
case "$HEAD_SHA" in
|
||
''|*[!0-9a-fA-F]*)
|
||
echo "无效的上游提交 SHA: $HEAD_SHA" >&2
|
||
exit 1
|
||
;;
|
||
esac
|
||
if [ ! -d "$REPOSITORY_DIR" ] || \
|
||
[ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null)" != "true" ]; then
|
||
echo "Prepare 未创建有效共享 bare 仓库: $REPOSITORY_DIR" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# 正常路径 Prepare 已把上游提交 fetch 到位;极端情况下(bare 仓库被清理
|
||
# 重建、或 Prepare 与本 run 之间发生 prune)补取一次,token 仅用于此。
|
||
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}" 2>/dev/null; then
|
||
git \
|
||
-c credential.helper= \
|
||
-c 'credential.helper=!f() { test "$1" = get || exit 0; printf "%s\n" username=oauth2; printf "%s\n" "password=${ACCESS_TOKEN}"; }; f' \
|
||
-c credential.interactive=never \
|
||
--git-dir="$REPOSITORY_DIR" fetch --prune --force origin \
|
||
'+refs/heads/*:refs/remotes/origin/*' || true
|
||
fi
|
||
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then
|
||
echo "共享仓库中不存在上游提交: $HEAD_SHA(事件: $SOURCE_EVENT)" >&2
|
||
exit 1
|
||
fi
|
||
|
||
exec 9>"$WORKTREE_LOCK"
|
||
flock 9
|
||
# 回收陈旧临时 worktree(超过 24h 的 checks-* 目录)。worktree prune 只清理
|
||
# 指向已消失目录的 metadata,不删仍存在的陈旧目录,故按 mtime 主动回收。
|
||
worktrees_root="$(dirname "$WORKTREE_DIR")"
|
||
if [ -d "$worktrees_root" ]; then
|
||
find "$worktrees_root" -mindepth 1 -maxdepth 1 -type d -name 'checks-*' \
|
||
-mmin +1440 -print 2>/dev/null | while IFS= read -r stale; do
|
||
[ "$stale" = "$WORKTREE_DIR" ] && continue
|
||
git --git-dir="$REPOSITORY_DIR" worktree remove --force "$stale" 2>/dev/null || rm -rf "$stale"
|
||
done
|
||
fi
|
||
git --git-dir="$REPOSITORY_DIR" worktree prune
|
||
if [ -e "$WORKTREE_DIR" ]; then
|
||
git --git-dir="$REPOSITORY_DIR" worktree remove --force "$WORKTREE_DIR" 2>/dev/null || rm -rf "$WORKTREE_DIR"
|
||
fi
|
||
mkdir -p "$(dirname "$WORKTREE_DIR")"
|
||
git --git-dir="$REPOSITORY_DIR" worktree add --detach "$WORKTREE_DIR" "$HEAD_SHA"
|
||
flock -u 9
|
||
|
||
git -C "$WORKTREE_DIR" reset --hard "$HEAD_SHA"
|
||
git -C "$WORKTREE_DIR" clean -ffdx
|
||
|
||
echo "REPOSITORY_DIR=$REPOSITORY_DIR" >> "$GITHUB_ENV"
|
||
echo "REPO_DIR=$WORKTREE_DIR" >> "$GITHUB_ENV"
|
||
echo "✅ Worktree 就绪: $WORKTREE_DIR @ $HEAD_SHA"
|
||
|
||
- name: 🔍 校验提交信息
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
cd "$REPO_DIR"
|
||
# 注:经 workflow_run 触发,事件 payload 为 workflow_run,不含 PR title;
|
||
# lint 脚本会回落到 head commit message 校验(PR 标题本身不再校验)。
|
||
echo "🔍 检查 commit message 规范(上游事件: $SOURCE_EVENT)..."
|
||
python3 .gitea/ci/commit_message_lint.py
|
||
echo "✅ Lint 检查通过"
|
||
|
||
- name: 🧪 运行全量测试
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
|
||
echo "========================================"
|
||
echo "🐍 Python 测试(上游事件: $SOURCE_EVENT)"
|
||
echo "========================================"
|
||
|
||
cd "$REPO_DIR"
|
||
echo "📋 覆盖:CLI、subtree/snapshot 部署路线、模板同步、文档一致性"
|
||
python3 -m unittest discover -s test -p "test_*.py" -v
|
||
echo "✅ Python 测试通过"
|
||
|
||
echo "========================================"
|
||
echo "🔗 文档链接检查"
|
||
echo "========================================"
|
||
|
||
python3 test/integration/check_doc_links.py
|
||
echo "✅ 文档链接检查通过"
|
||
echo "🎉 所有测试完成"
|
||
|
||
- name: 🧹 清理临时 Worktree
|
||
if: always()
|
||
shell: bash
|
||
run: |
|
||
set -uo pipefail
|
||
[ -n "${REPOSITORY_DIR:-}" ] || exit 0
|
||
[ -n "${REPO_DIR:-}" ] || exit 0
|
||
WORKTREE_LOCK="$(dirname "$REPOSITORY_DIR")/worktree-admin.lock"
|
||
exec 9>"$WORKTREE_LOCK"
|
||
flock 9
|
||
git --git-dir="$REPOSITORY_DIR" worktree remove --force "$REPO_DIR" 2>/dev/null || rm -rf "$REPO_DIR"
|
||
git --git-dir="$REPOSITORY_DIR" worktree prune
|
||
flock -u 9
|