diff --git a/.gitea/workflows/ci-pipeline.yml b/.gitea/workflows/ci-pipeline.yml index 7ee8c2920..fb51baa41 100755 --- a/.gitea/workflows/ci-pipeline.yml +++ b/.gitea/workflows/ci-pipeline.yml @@ -24,6 +24,54 @@ concurrency: group: ci-pipeline-${{ gitea.ref }} cancel-in-progress: true jobs: + dedupe-check: + name: Dedup Check - skip PR tests when covered by push pipeline + runs-on: ci-l1 + timeout-minutes: 3 + outputs: + skip_tests: ${{ steps.dedupe.outputs.skip_tests }} + reason: ${{ steps.dedupe.outputs.reason }} + steps: + - name: Decide test dedup + id: dedupe + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + HEAD_SHA: ${{ github.sha }} + EVENT_NAME: ${{ github.event_name }} + run: | + set -eu + if [ "$EVENT_NAME" != "pull_request" ]; then + echo "skip_tests=false" >> $GITHUB_OUTPUT + echo "reason=push-event-tests-required" >> $GITHUB_OUTPUT + echo "push 事件:测试照跑(部署链路门禁必需)" + exit 0 + fi + # 情形1:PR 已合并(合并瞬间/合并后触发的 PR run)-> 全量测试由 push 流水线承接 + MERGED=$(curl -sfH "Authorization: token $GITHUB_TOKEN" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \ + | python3 -c "import json,sys; d=json.load(sys.stdin); print('true' if d.get('merged') else 'false')" || echo false) + if [ "$MERGED" = "true" ]; then + echo "skip_tests=true" >> $GITHUB_OUTPUT + echo "reason=pr-merged-push-pipeline-covers" >> $GITHUB_OUTPUT + echo "::warning::PR #${PR_NUMBER} 已合并,测试由合并后 push 流水线承接,PR 侧测试类 job 跳过" + exit 0 + fi + # 情形2:同一 head_sha 已有在跑/排队的 push 流水线(rebase/ff 合并竞态) + DUP=$(curl -sfH "Authorization: token $GITHUB_TOKEN" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/actions/runs?head_sha=${HEAD_SHA}&per_page=30" \ + | python3 -c "import json,sys; d=json.load(sys.stdin); runs=d if isinstance(d,list) else d.get('workflow_runs',d.get('runs',[])); hit=[r for r in runs if r.get('event')=='push' and r.get('status') in ('in_progress','queued','waiting','pending')]; print('true' if hit else 'false')" || echo false) + if [ "$DUP" = "true" ]; then + echo "skip_tests=true" >> $GITHUB_OUTPUT + echo "reason=duplicate-push-run-active" >> $GITHUB_OUTPUT + echo "::warning::同一 head_sha ${HEAD_SHA:0:8} 已有 push 流水线在跑,PR 侧测试类 job 跳过" + exit 0 + fi + echo "skip_tests=false" >> $GITHUB_OUTPUT + echo "reason=no-duplicate" >> $GITHUB_OUTPUT + echo "无重复 push 流水线,PR 侧测试照跑" + check-frontend-only: name: Check if frontend-only change runs-on: ci-l2 @@ -79,6 +127,8 @@ jobs: python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true validate-code-quality: + needs: dedupe-check + if: always() && needs.dedupe-check.outputs.skip_tests != 'true' name: Validate - Code Quality runs-on: ci-l2 timeout-minutes: 8 @@ -180,6 +230,8 @@ jobs: python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true validate-type-check: + needs: dedupe-check + if: always() && needs.dedupe-check.outputs.skip_tests != 'true' name: Validate - Type Check (mypy) runs-on: ci-l2 timeout-minutes: 8 @@ -255,6 +307,8 @@ jobs: python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true validate-migration: + needs: dedupe-check + if: always() && needs.dedupe-check.outputs.skip_tests != 'true' name: Validate - Migration (alembic) runs-on: ci-l2 timeout-minutes: 8 @@ -334,8 +388,8 @@ jobs: python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true unit-tests: - needs: check-frontend-only - if: always() && needs.check-frontend-only.outputs.skip_backend != 'true' + needs: [check-frontend-only, dedupe-check] + if: always() && needs.dedupe-check.outputs.skip_tests != 'true' && needs.check-frontend-only.outputs.skip_backend != 'true' name: Unit Tests runs-on: ci-l2 timeout-minutes: 8 @@ -412,9 +466,10 @@ jobs: name: Integration Tests runs-on: ci-l2 timeout-minutes: 30 - if: always() && needs.check-frontend-only.outputs.skip_backend != 'true' + if: always() && needs.dedupe-check.outputs.skip_tests != 'true' && needs.check-frontend-only.outputs.skip_backend != 'true' needs: - check-frontend-only + - dedupe-check - validate-code-quality - validate-type-check - validate-migration @@ -478,8 +533,8 @@ jobs: name: Frontend Lint runs-on: ci-l2 timeout-minutes: 10 - needs: check-frontend-only - if: needs.check-frontend-only.outputs.skip_frontend != 'true' + needs: [check-frontend-only, dedupe-check] + if: needs.dedupe-check.outputs.skip_tests != 'true' && needs.check-frontend-only.outputs.skip_frontend != 'true' steps: - name: Checkout code shell: sh @@ -540,8 +595,8 @@ jobs: name: Frontend Unit Tests runs-on: ci-l2 timeout-minutes: 15 - needs: check-frontend-only - if: always() && needs.check-frontend-only.outputs.skip_frontend != 'true' + needs: [check-frontend-only, dedupe-check] + if: always() && needs.dedupe-check.outputs.skip_tests != 'true' && needs.check-frontend-only.outputs.skip_frontend != 'true' steps: - name: Checkout code shell: sh