🐛 fix(ci): restore workflow-run commit verification

This commit is contained in:
csh
2026-08-24 14:48:19 +08:00
parent fd653ff49d
commit d1568c02cd
4 changed files with 90 additions and 97 deletions
+1 -20
View File
@@ -13,9 +13,6 @@ on:
branches: branches:
- main - main
workflow_dispatch: workflow_dispatch:
schedule:
# 北京时间(UTC+8)每日 06:00Prepare 成功后内部调用 thirdparty updater。
- cron: "0 22 * * *"
concurrency: concurrency:
group: prepare-${{ github.repository }} group: prepare-${{ github.repository }}
@@ -167,7 +164,7 @@ jobs:
# refs/heads/* 里,直接校验会失败;改用 PR head SHA,并在下方额外 # refs/heads/* 里,直接校验会失败;改用 PR head SHA,并在下方额外
# fetch PR ref 把 head 提交拉进 bare 仓库。下游 workflow_run 消费者 # fetch PR ref 把 head 提交拉进 bare 仓库。下游 workflow_run 消费者
# 读到的 head_sha 也正是这个 PR head,两者一致。 # 读到的 head_sha 也正是这个 PR head,两者一致。
# - 其它事件(push / schedule / dispatch):沿用 github.sha。 # - 其它事件(push / dispatch):沿用 github.sha。
if [ "$EVENT_NAME" = "pull_request" ]; then if [ "$EVENT_NAME" = "pull_request" ]; then
HEAD_SHA="${{ github.event.pull_request.head.sha }}" HEAD_SHA="${{ github.event.pull_request.head.sha }}"
PR_NUMBER="${{ github.event.pull_request.number }}" PR_NUMBER="${{ github.event.pull_request.number }}"
@@ -293,19 +290,3 @@ jobs:
echo "- npm\`$actual_npm_version\`" echo "- npm\`$actual_npm_version\`"
echo "- 缓存目录:\`$FNM_DIR\`" echo "- 缓存目录:\`$FNM_DIR\`"
} >> "$GITHUB_STEP_SUMMARY" } >> "$GITHUB_STEP_SUMMARY"
update_thirdparty_scheduled:
# Gitea 1.27 会抑制由 gitea-actions 定时运行产生的后续 workflow_run。
# 因此 schedule 在 Prepare 成功后直接调用 updaterupdater 本身不暴露
# schedule / workflow_dispatch 入口。push / 手动 Prepare 仍走原 workflow_run 链。
needs: prepare
if: ${{ github.event_name == 'schedule' }}
uses: ./.gitea/workflows/update-thirdparty-skills.yml
with:
source_branch: ${{ github.ref_name }}
source_sha: ${{ github.sha }}
secrets:
WORKFLOW: ${{ secrets.WORKFLOW }}
concurrency:
group: update-thirdparty-${{ github.repository }}
cancel-in-progress: false
+61 -43
View File
@@ -1,19 +1,11 @@
name: ♻️ 更新第三方 Skills name: ♻️ 更新第三方 Skills
on: on:
# schedule 由 prepare.yml 在 prepare job 成功后内部调用;这里不提供独立 # Gitea 1.27 不会从 schedule 触发的 Prepare 继续派生 workflow_run。
# schedule / workflow_dispatch 入口 # 定时刷新因此由 updater 直接拥有;push / PR 仍在 Prepare 成功后进入
workflow_call: schedule:
inputs: # 北京时间(UTC+8)每日 06:00。
source_branch: - cron: "0 22 * * *"
required: true
type: string
source_sha:
required: true
type: string
secrets:
WORKFLOW:
required: true
workflow_run: workflow_run:
workflows: ["🧰 准备环境"] workflows: ["🧰 准备环境"]
types: types:
@@ -24,12 +16,10 @@ concurrency:
cancel-in-progress: false cancel-in-progress: false
env: env:
# Prepare 维护共享仓库并读取代码;此处 token 用于 worktree 内的 fetch/push。 # updater 会校准共享仓库;token 用于 fetch/push。
ACCESS_TOKEN: ${{ secrets.WORKFLOW }} ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
WORKSPACE_ROOT: "/data/workspace" WORKSPACE_ROOT: "/data/workspace"
WORKSPACE_SLOT: "thirdparty" WORKSPACE_SLOT: "thirdparty"
SOURCE_BRANCH: ${{ inputs.source_branch || github.event.workflow_run.head_branch }}
SOURCE_SHA: ${{ inputs.source_sha || github.event.workflow_run.head_sha }}
THIRDPARTY_BRANCH: "thirdparty/skill" THIRDPARTY_BRANCH: "thirdparty/skill"
MANIFEST_PATH: ".gitea/ci/thirdparty_skills.json" MANIFEST_PATH: ".gitea/ci/thirdparty_skills.json"
GIT_USER_NAME: "ci[bot]" GIT_USER_NAME: "ci[bot]"
@@ -37,9 +27,8 @@ env:
jobs: jobs:
update_and_sync: update_and_sync:
# workflow_call 只能由 schedule Prepare 的 needs 成功路径进入; # schedule 直接进入;workflow_run 需要 Prepare 成功且来自主分支。
# workflow_run 仍需要显式校验 Prepare 成功且来自主分支。 if: ${{ github.event_name == 'schedule' || (github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'master')) }}
if: ${{ github.event_name == 'workflow_call' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'schedule' && (github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'master')) }}
name: 📥 更新快照并同步 main name: 📥 更新快照并同步 main
runs-on: standard-ubuntu-22 runs-on: standard-ubuntu-22
permissions: permissions:
@@ -54,9 +43,19 @@ jobs:
echo "❌ 未配置 WORKFLOW secret,无法 fetch/push" >&2 echo "❌ 未配置 WORKFLOW secret,无法 fetch/push" >&2
exit 1 exit 1
fi fi
if [ "$GITHUB_EVENT_NAME" = "schedule" ]; then
SOURCE_BRANCH="$GITHUB_REF_NAME"
SOURCE_SHA="$GITHUB_SHA"
echo "🌟 定时运行;目标分支: $SOURCE_BRANCH,提交: $SOURCE_SHA"
else
SOURCE_BRANCH="${{ github.event.workflow_run.head_branch }}"
SOURCE_SHA="${{ github.event.workflow_run.head_sha }}"
fi
echo "SOURCE_BRANCH=$SOURCE_BRANCH" >> "$GITHUB_ENV"
echo "SOURCE_SHA=$SOURCE_SHA" >> "$GITHUB_ENV"
echo "✅ Token 已配置" echo "✅ Token 已配置"
echo "🌿 上游分支: ${{ env.SOURCE_BRANCH }}" echo "🌿 上游分支: $SOURCE_BRANCH"
echo "📝 上游提交: ${{ env.SOURCE_SHA }}" echo "📝 上游提交: $SOURCE_SHA"
- name: 📥 对齐 Worktree - name: 📥 对齐 Worktree
id: prepare_worktree id: prepare_worktree
@@ -65,7 +64,8 @@ jobs:
set -euo pipefail set -euo pipefail
REPO_NAME="${{ github.event.repository.name }}" REPO_NAME="${{ github.event.repository.name }}"
HEAD_SHA="${{ env.SOURCE_SHA }}" HEAD_SHA="$SOURCE_SHA"
REMOTE_URL="${{ github.server_url }}/${{ github.repository }}.git"
REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git" REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"
WORKTREE_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/worktrees/${WORKSPACE_SLOT}" WORKTREE_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/worktrees/${WORKSPACE_SLOT}"
WORKTREE_LOCK="${WORKSPACE_ROOT}/${REPO_NAME}/worktree-admin.lock" WORKTREE_LOCK="${WORKSPACE_ROOT}/${REPO_NAME}/worktree-admin.lock"
@@ -76,13 +76,23 @@ jobs:
exit 1 exit 1
;; ;;
esac esac
if [ ! -d "$REPOSITORY_DIR" ] || \ case "$REMOTE_URL" in
[ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null)" != "true" ]; then https://*) ;;
echo "Prepare 未创建有效共享 bare 仓库: $REPOSITORY_DIR" >&2 *)
exit 1 echo "拒绝通过非 HTTPS remote 使用 WORKFLOW token: $REMOTE_URL" >&2
fi exit 1
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then ;;
echo "共享仓库中不存在上游提交: $HEAD_SHA" >&2 esac
mkdir -p "$(dirname "$REPOSITORY_DIR")"
exec 9>"$WORKTREE_LOCK"
flock 9
if [ ! -e "$REPOSITORY_DIR" ]; then
git init --bare "$REPOSITORY_DIR"
elif [ ! -d "$REPOSITORY_DIR" ] || \
[ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null)" != "true" ]; then
echo "共享仓库路径不是有效 bare 仓库: $REPOSITORY_DIR" >&2
exit 1 exit 1
fi fi
@@ -99,24 +109,32 @@ jobs:
# `git remote set-url origin https://oauth2:$TOKEN@...` 仅在设置了 # `git remote set-url origin https://oauth2:$TOKEN@...` 仅在设置了
# WORKFLOW env 时才触发;本 job 严禁设置 WORKFLOW,否则 token 会落盘到 # WORKFLOW env 时才触发;本 job 严禁设置 WORKFLOW,否则 token 会落盘到
# 共享 bare 仓库的 config 并污染所有其它 slot。此处显式重置以固化约束。 # 共享 bare 仓库的 config 并污染所有其它 slot。此处显式重置以固化约束。
git --git-dir="$REPOSITORY_DIR" remote set-url origin "${{ github.server_url }}/${{ github.repository }}.git" if git --git-dir="$REPOSITORY_DIR" remote get-url origin >/dev/null 2>&1; then
git --git-dir="$REPOSITORY_DIR" remote set-url origin "$REMOTE_URL"
else
git --git-dir="$REPOSITORY_DIR" remote add origin "$REMOTE_URL"
fi
git --git-dir="$REPOSITORY_DIR" fetch \
--prune --prune-tags --tags --force origin \
'+refs/heads/*:refs/remotes/origin/*'
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then
echo "共享仓库中不存在上游提交: $HEAD_SHA" >&2
exit 1
fi
if [ ! -f "$WORKTREE_DIR/.git" ]; then if [ ! -f "$WORKTREE_DIR/.git" ]; then
exec 9>"$WORKTREE_LOCK" if [ -e "$WORKTREE_DIR" ]; then
flock 9 echo "Worktree 路径已存在但不是 linked worktree: $WORKTREE_DIR" >&2
exit 1
if [ ! -f "$WORKTREE_DIR/.git" ]; then
if [ -e "$WORKTREE_DIR" ]; then
echo "Worktree 路径已存在但不是 linked worktree: $WORKTREE_DIR" >&2
exit 1
fi
mkdir -p "$(dirname "$WORKTREE_DIR")"
git --git-dir="$REPOSITORY_DIR" worktree add --detach "$WORKTREE_DIR" "$HEAD_SHA"
fi fi
mkdir -p "$(dirname "$WORKTREE_DIR")"
flock -u 9 git --git-dir="$REPOSITORY_DIR" worktree add --detach "$WORKTREE_DIR" "$HEAD_SHA"
fi fi
flock -u 9
common_dir=$(git -C "$WORKTREE_DIR" rev-parse --path-format=absolute --git-common-dir) common_dir=$(git -C "$WORKTREE_DIR" rev-parse --path-format=absolute --git-common-dir)
if [ "$common_dir" != "$(realpath "$REPOSITORY_DIR")" ]; then if [ "$common_dir" != "$(realpath "$REPOSITORY_DIR")" ]; then
echo "Worktree 不属于共享仓库: $WORKTREE_DIR" >&2 echo "Worktree 不属于共享仓库: $WORKTREE_DIR" >&2
@@ -818,5 +836,5 @@ jobs:
# 持久化 worktree 由下次运行前的 detached checkout + reset + clean 复用, # 持久化 worktree 由下次运行前的 detached checkout + reset + clean 复用,
# 此处仅在结束时 detach,避免留下已检出的分支阻塞其它 worktree。 # 此处仅在结束时 detach,避免留下已检出的分支阻塞其它 worktree。
if [ -n "${REPO_DIR:-}" ] && [ -f "$REPO_DIR/.git" ]; then if [ -n "${REPO_DIR:-}" ] && [ -f "$REPO_DIR/.git" ]; then
git -C "$REPO_DIR" checkout --detach --force "${SOURCE_SHA}" 2>/dev/null || true git -C "$REPO_DIR" checkout --detach --force "${SOURCE_SHA:-}" 2>/dev/null || true
fi fi
+6 -4
View File
@@ -34,13 +34,15 @@ python test/integration/check_doc_links.py
## 🧭 CI 自动化测试 ## 🧭 CI 自动化测试
测试套件通过 Gitea Actions 自动运行(见 `.gitea/workflows/checks.yml` `tests` job): 测试套件通过 Gitea Actions 自动运行(见 `.gitea/workflows/checks.yml`
`🔎 规范检查 + 全量测试` job):
- **触发方式**`workflow_run`——「🧰 准备环境」(`prepare.yml`)完成后触发。 - **触发方式**`workflow_run`——「🧰 准备环境」(`prepare.yml`)完成后触发。
Prepare 监听 `push:main``pull_request``workflow_dispatch` 与每日 `schedule` Prepare 监听 `push:main``pull_request``workflow_dispatch`;推送到 main、
故推送到 main、对 main 开 PR 或手动运行 Prepare 都会经 Prepare 传导到测试。 对 main 开 PR 或手动运行 Prepare 都会经 Prepare 传导到测试。每日 `schedule`
只属于 thirdparty updater,不会触发这套提交验证。
- **运行平台**standard-ubuntu-22 - **运行平台**standard-ubuntu-22
- **注**`checks.yml` 同时包含 commit message 规范校验(`standards` job - **注**同一 job 先校验 commit message,再运行全量测试
## 📚 测试详解 ## 📚 测试详解
+22 -30
View File
@@ -228,44 +228,36 @@ class ThirdpartySkillsPipelineTests(unittest.TestCase):
self.assertIn("update_thirdparty_snapshots", text) self.assertIn("update_thirdparty_snapshots", text)
self.assertIn("sync_thirdparty_skills", text) self.assertIn("sync_thirdparty_skills", text)
def test_scheduled_prepare_calls_updater_without_an_independent_entry(self): def test_schedule_runs_updater_without_changing_prepare_run_shape(self):
prepare_text = PREPARE_WORKFLOW.read_text(encoding="utf-8") prepare_text = PREPARE_WORKFLOW.read_text(encoding="utf-8")
updater_text = WORKFLOW.read_text(encoding="utf-8") updater_text = WORKFLOW.read_text(encoding="utf-8")
self.assertEqual(workflow_mapping_keys(prepare_text, "jobs"), ["prepare"])
self.assertEqual( self.assertEqual(
workflow_mapping_keys(prepare_text, "jobs"), workflow_mapping_keys(prepare_text, "on"),
["prepare", "update_thirdparty_scheduled"], ["push", "pull_request", "workflow_dispatch"],
) )
self.assertEqual( self.assertEqual(
workflow_mapping_keys(updater_text, "on"), workflow_mapping_keys(updater_text, "on"),
["workflow_call", "workflow_run"], ["schedule", "workflow_run"],
) )
self.assertIn(" needs: prepare", prepare_text) self.assertNotIn("update_thirdparty_scheduled", prepare_text)
self.assertIn(" if: ${{ github.event_name == 'schedule' }}", prepare_text) self.assertNotIn("uses: ./.gitea/workflows/", prepare_text)
self.assertIn( self.assertNotIn(" schedule:", prepare_text)
" uses: ./.gitea/workflows/update-thirdparty-skills.yml", self.assertNotIn(" workflow_call:", updater_text)
prepare_text, self.assertIn(' - cron: "0 22 * * *"', updater_text)
) self.assertIn("github.event_name == 'schedule' ||", updater_text)
self.assertIn(" source_branch: ${{ github.ref_name }}", prepare_text) self.assertNotIn("github.event.workflow_run.event != 'schedule'", updater_text)
self.assertIn(" source_sha: ${{ github.sha }}", prepare_text) self.assertIn('SOURCE_BRANCH="$GITHUB_REF_NAME"', updater_text)
self.assertIn(" WORKFLOW: ${{ secrets.WORKFLOW }}", prepare_text) self.assertIn('SOURCE_SHA="$GITHUB_SHA"', updater_text)
self.assertIn(" workflow_call:", updater_text) self.assertIn('SOURCE_BRANCH="${{ github.event.workflow_run.head_branch }}"', updater_text)
self.assertIn(" source_branch:", updater_text) self.assertIn('SOURCE_SHA="${{ github.event.workflow_run.head_sha }}"', updater_text)
self.assertIn(" source_sha:", updater_text) self.assertIn('echo "SOURCE_BRANCH=$SOURCE_BRANCH" >> "$GITHUB_ENV"', updater_text)
self.assertIn(" WORKFLOW:", updater_text) self.assertIn('echo "SOURCE_SHA=$SOURCE_SHA" >> "$GITHUB_ENV"', updater_text)
self.assertIn(" required: true", updater_text) self.assertIn('HEAD_SHA="$SOURCE_SHA"', updater_text)
self.assertIn( self.assertIn('checkout --detach --force "${SOURCE_SHA:-}"', updater_text)
"SOURCE_BRANCH: ${{ inputs.source_branch || " self.assertIn('git init --bare "$REPOSITORY_DIR"', updater_text)
"github.event.workflow_run.head_branch }}", self.assertIn("'+refs/heads/*:refs/remotes/origin/*'", updater_text)
updater_text,
)
self.assertIn(
"SOURCE_SHA: ${{ inputs.source_sha || "
"github.event.workflow_run.head_sha }}",
updater_text,
)
self.assertIn("github.event_name == 'workflow_call' ||", updater_text)
self.assertIn("github.event.workflow_run.event != 'schedule'", updater_text)
def test_inline_workflow_exposes_manifest_and_publish_contract(self): def test_inline_workflow_exposes_manifest_and_publish_contract(self):
text = WORKFLOW.read_text(encoding="utf-8") text = WORKFLOW.read_text(encoding="utf-8")