diff --git a/.gitea/workflows/pr-automation.yml b/.gitea/workflows/pr-automation.yml old mode 100755 new mode 100644 index 663576e21..ad33e1627 --- a/.gitea/workflows/pr-automation.yml +++ b/.gitea/workflows/pr-automation.yml @@ -8,6 +8,11 @@ on: permissions: contents: read + +concurrency: + group: pr-automation-${{ gitea.event.pull_request.number }} + cancel-in-progress: true + jobs: auto-approve: name: Auto Approve on CI Green @@ -56,7 +61,7 @@ jobs: name: Auto Merge on CI Green + Approved runs-on: ci-check if: github.event_name == 'pull_request' && !github.event.pull_request.draft && github.event.pull_request.base.ref == 'develop' - timeout-minutes: 45 # 长等待模式:等CI全绿后自动合并,不遗漏任何PR + timeout-minutes: 3 # 短作业模式:检查一次,不满足就退出,由pr-auto-scan每5分钟定时兜底 steps: - name: Checkout code shell: sh diff --git a/scripts/ci/auto_merge.sh b/scripts/ci/auto_merge.sh old mode 100644 new mode 100755 index acb8dcdef..bcf7747ab --- a/scripts/ci/auto_merge.sh +++ b/scripts/ci/auto_merge.sh @@ -1,12 +1,13 @@ #!/usr/bin/env bash # 自动合并:CI全绿+已审批后自动squash merge PR到develop +# 短作业模式:只检查一次,不满足条件就退出,由pr-auto-scan定时兜底 # 环境变量:GITHUB_TOKEN, MERGE_TOKEN, PR_NUMBER, PR_HEAD_SHA, BASE_REF, GITHUB_API_URL, GITHUB_REPOSITORY set -eu -set -eu - echo "PR #${PR_NUMBER} - 检查CI状态+审批并自动合并到${BASE_REF}" echo +echo "模式: 短作业(只检查一次,不满足则退出,由pr-auto-scan定时兜底)" +echo # 只合develop分支 if [ "$BASE_REF" != "develop" ]; then @@ -22,6 +23,7 @@ TOTAL=$(echo "$FILES" | grep -cv '^$' || true) FRONTEND_COUNT=$(echo "$FILES" | grep -c '^apps/web/' || true) BACKEND_COUNT=$((TOTAL - FRONTEND_COUNT)) echo "变更文件: ${TOTAL} 个 (前端: ${FRONTEND_COUNT}, 后端/公共: ${BACKEND_COUNT})" +echo # 使用统一的CI Gate门禁(单一检查点,自动处理前端/后端/全栈跳过逻辑) CONTEXTS=( @@ -30,21 +32,20 @@ CONTEXTS=( echo "检查CI Gate统一门禁" echo -# 初始等待30秒,给CI启动写status的时间 -echo "等待30秒让CI启动..." -sleep 30 +# 等待60秒,给CI启动写status的时间 +echo "等待60秒让CI启动..." +sleep 60 -# 405连续计数器 +# 405计数器(单次运行内重试) MERGE_405_COUNT=0 -MAX_405_RETRIES=10 +MAX_405_RETRIES=3 -# 轮询等待,最多30分钟(180次x10秒) -for attempt in $(seq 1 90); do # 最多等45分钟(90次x30秒),确保等得到Worker构建完成 +check_and_merge() { ALL_SUCCESS=true ANY_FAILED=false ANY_PENDING=false - echo "--- 第${attempt}次检查 ($(date '+%H:%M:%S')) ---" + echo "--- 检查CI状态 ($(date '+%H:%M:%S')) ---" # 检查CI状态 for ctx in "${CONTEXTS[@]}"; do @@ -61,77 +62,87 @@ for attempt in $(seq 1 90); do # 最多等45分钟(90次x30秒),确保等得 fi done - - # CI全绿 → 合并 - if [ "$ALL_SUCCESS" = "true" ]; then - echo - echo "CI全绿,执行自动合并" - echo "等待60秒冷却,给Gitea内部状态同步时间..." - sleep 60 - - # 幂等检查:PR是否还是open - PR_STATE=$(curl -s -H "Authorization: token ${MERGE_TOKEN}" \ - "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \ - | python3 -c "import sys,json; print(json.load(sys.stdin).get('state',''))") - - if [ "$PR_STATE" != "open" ]; then - echo "PR状态为 ${PR_STATE},无需合并" - exit 0 - fi - - # 执行squash merge - HTTP_CODE=$(curl -s -o /tmp/merge_resp.json -w "%{http_code}" \ - -X POST \ - -H "Authorization: token ${MERGE_TOKEN}" \ - -H "Content-Type: application/json" \ - -d '{"do":"squash","merge_title_field":"","merge_message_field":"","delete_branch_after_merge":true,"force_merge":false}' \ - "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/merge") - - echo "合并API HTTP状态: $HTTP_CODE" - - if [ "$HTTP_CODE" = "200" ]; then - echo "自动合并成功" - exit 0 - elif [ "$HTTP_CODE" = "405" ]; then - MERGE_405_COUNT=$((MERGE_405_COUNT + 1)) - echo "⚠️ 合并返回405(第${MERGE_405_COUNT}次),可能CI状态尚未同步或有未解决的门禁,继续等待重试..." - cat /tmp/merge_resp.json 2>/dev/null || true - echo - if [ "$MERGE_405_COUNT" -ge "$MAX_405_RETRIES" ]; then - echo "⚠️ 连续${MAX_405_RETRIES}次合并返回405,放弃自动合并(需人工确认,非代码问题)" - curl -s -X POST \ - -H "Authorization: token ${MERGE_TOKEN}" \ - -H "Content-Type: application/json" \ - -d '{"body": "Auto merge skipped after multiple 405 errors: PR may have conflicts or unresolved checks. Please review manually. This is not a CI failure."}' \ - "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" > /dev/null 2>&1 || true - exit 0 - fi - sleep 30 - continue - else - echo "自动合并失败 (HTTP $HTTP_CODE)" - cat /tmp/merge_resp.json 2>/dev/null || true - curl -s -X POST \ - -H "Authorization: token ${MERGE_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "{\"body\": \"Auto merge failed (HTTP ${HTTP_CODE}), please check manually.\"}" \ - "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" > /dev/null 2>&1 || true - exit 1 - fi - else - # 本轮不满足合并条件,重置405计数器 - MERGE_405_COUNT=0 - fi - + # CI有失败 → 不合并,直接退出 if [ "$ANY_FAILED" = "true" ]; then echo - echo "CI有失败项,不自动合并" + echo "❌ CI有失败项,不自动合并" exit 0 fi + # CI未全绿(pending中)→ 退出,等下次触发 + if [ "$ALL_SUCCESS" != "true" ]; then + echo + echo "⏳ CI尚未全绿(仍有pending),退出等待下次触发" + echo " (pr-auto-scan每5分钟扫描一次,CI通过后会自动合并)" + exit 0 + fi + + # CI全绿 → 合并 + echo + echo "✅ CI全绿,执行自动合并" + echo "等待30秒冷却,给Gitea内部状态同步时间..." sleep 30 + + # 幂等检查:PR是否还是open + PR_STATE=$(curl -s -H "Authorization: token ${MERGE_TOKEN}" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin).get('state',''))") + + if [ "$PR_STATE" != "open" ]; then + echo "PR状态为 ${PR_STATE},无需合并" + exit 0 + fi + + # 执行squash merge + HTTP_CODE=$(curl -s -o /tmp/merge_resp.json -w "%{http_code}" \ + -X POST \ + -H "Authorization: token ${MERGE_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"do":"squash","merge_title_field":"","merge_message_field":"","delete_branch_after_merge":true,"force_merge":false}' \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/merge") + + echo "合并API HTTP状态: $HTTP_CODE" + + if [ "$HTTP_CODE" = "200" ]; then + echo "✅ 自动合并成功" + exit 0 + elif [ "$HTTP_CODE" = "405" ]; then + MERGE_405_COUNT=$((MERGE_405_COUNT + 1)) + echo "⚠️ 合并返回405(第${MERGE_405_COUNT}次),可能CI状态尚未同步或有未解决的门禁" + cat /tmp/merge_resp.json 2>/dev/null || true + echo + if [ "$MERGE_405_COUNT" -ge "$MAX_405_RETRIES" ]; then + echo "⚠️ 连续${MAX_405_RETRIES}次合并返回405,放弃本次自动合并" + echo " (pr-auto-scan会继续尝试,需人工确认是否有冲突或门禁问题)" + curl -s -X POST \ + -H "Authorization: token ${MERGE_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"body": "Auto merge skipped after multiple 405 errors: PR may have conflicts or unresolved checks. Please review manually. This is not a CI failure."}' \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" > /dev/null 2>&1 || true + exit 0 + fi + echo "30秒后重试..." + sleep 30 + return 1 # 重试 + else + echo "❌ 自动合并失败 (HTTP $HTTP_CODE)" + cat /tmp/merge_resp.json 2>/dev/null || true + curl -s -X POST \ + -H "Authorization: token ${MERGE_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"body\": \"Auto merge failed (HTTP ${HTTP_CODE}), please check manually.\"}" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" > /dev/null 2>&1 || true + exit 1 + fi +} + +# 最多重试3次(用于405重试,非CI轮询) +for i in 1 2 3; do + if check_and_merge; then + exit 0 + fi done echo -echo "快速检查超时(3分钟),CI尚未全绿或无审批,退出等待下次触发" +echo "本次检查未满足合并条件,退出。pr-auto-scan每5分钟会继续扫描。" exit 0 diff --git a/scripts/ci/auto_merge.sh.bak b/scripts/ci/auto_merge.sh.bak new file mode 100644 index 000000000..acb8dcdef --- /dev/null +++ b/scripts/ci/auto_merge.sh.bak @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +# 自动合并:CI全绿+已审批后自动squash merge PR到develop +# 环境变量:GITHUB_TOKEN, MERGE_TOKEN, PR_NUMBER, PR_HEAD_SHA, BASE_REF, GITHUB_API_URL, GITHUB_REPOSITORY +set -eu + +set -eu + +echo "PR #${PR_NUMBER} - 检查CI状态+审批并自动合并到${BASE_REF}" +echo + +# 只合develop分支 +if [ "$BASE_REF" != "develop" ]; then + echo "Skip: 目标分支不是develop" + exit 0 +fi + +# 判断是否纯前端改动 +FILES=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files?limit=300" \ + | python3 -c "import sys,json; [print(f['filename']) for f in json.load(sys.stdin)]") +TOTAL=$(echo "$FILES" | grep -cv '^$' || true) +FRONTEND_COUNT=$(echo "$FILES" | grep -c '^apps/web/' || true) +BACKEND_COUNT=$((TOTAL - FRONTEND_COUNT)) +echo "变更文件: ${TOTAL} 个 (前端: ${FRONTEND_COUNT}, 后端/公共: ${BACKEND_COUNT})" + +# 使用统一的CI Gate门禁(单一检查点,自动处理前端/后端/全栈跳过逻辑) +CONTEXTS=( + "CI/CD Pipeline / CI Gate (pull_request)" +) +echo "检查CI Gate统一门禁" +echo + +# 初始等待30秒,给CI启动写status的时间 +echo "等待30秒让CI启动..." +sleep 30 + +# 405连续计数器 +MERGE_405_COUNT=0 +MAX_405_RETRIES=10 + +# 轮询等待,最多30分钟(180次x10秒) +for attempt in $(seq 1 90); do # 最多等45分钟(90次x30秒),确保等得到Worker构建完成 + ALL_SUCCESS=true + ANY_FAILED=false + ANY_PENDING=false + + echo "--- 第${attempt}次检查 ($(date '+%H:%M:%S')) ---" + + # 检查CI状态 + for ctx in "${CONTEXTS[@]}"; do + STATE=$(python3 scripts/check_ci_status.py "$GITHUB_TOKEN" "$GITHUB_REPOSITORY" "$PR_HEAD_SHA" "$ctx") + echo " CI: ${ctx##*/}: $STATE" + if [ "$STATE" != "success" ]; then + ALL_SUCCESS=false + fi + if [ "$STATE" = "failure" ] || [ "$STATE" = "error" ]; then + ANY_FAILED=true + fi + if [ "$STATE" = "pending" ]; then + ANY_PENDING=true + fi + done + + + # CI全绿 → 合并 + if [ "$ALL_SUCCESS" = "true" ]; then + echo + echo "CI全绿,执行自动合并" + echo "等待60秒冷却,给Gitea内部状态同步时间..." + sleep 60 + + # 幂等检查:PR是否还是open + PR_STATE=$(curl -s -H "Authorization: token ${MERGE_TOKEN}" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin).get('state',''))") + + if [ "$PR_STATE" != "open" ]; then + echo "PR状态为 ${PR_STATE},无需合并" + exit 0 + fi + + # 执行squash merge + HTTP_CODE=$(curl -s -o /tmp/merge_resp.json -w "%{http_code}" \ + -X POST \ + -H "Authorization: token ${MERGE_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"do":"squash","merge_title_field":"","merge_message_field":"","delete_branch_after_merge":true,"force_merge":false}' \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/merge") + + echo "合并API HTTP状态: $HTTP_CODE" + + if [ "$HTTP_CODE" = "200" ]; then + echo "自动合并成功" + exit 0 + elif [ "$HTTP_CODE" = "405" ]; then + MERGE_405_COUNT=$((MERGE_405_COUNT + 1)) + echo "⚠️ 合并返回405(第${MERGE_405_COUNT}次),可能CI状态尚未同步或有未解决的门禁,继续等待重试..." + cat /tmp/merge_resp.json 2>/dev/null || true + echo + if [ "$MERGE_405_COUNT" -ge "$MAX_405_RETRIES" ]; then + echo "⚠️ 连续${MAX_405_RETRIES}次合并返回405,放弃自动合并(需人工确认,非代码问题)" + curl -s -X POST \ + -H "Authorization: token ${MERGE_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"body": "Auto merge skipped after multiple 405 errors: PR may have conflicts or unresolved checks. Please review manually. This is not a CI failure."}' \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" > /dev/null 2>&1 || true + exit 0 + fi + sleep 30 + continue + else + echo "自动合并失败 (HTTP $HTTP_CODE)" + cat /tmp/merge_resp.json 2>/dev/null || true + curl -s -X POST \ + -H "Authorization: token ${MERGE_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"body\": \"Auto merge failed (HTTP ${HTTP_CODE}), please check manually.\"}" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" > /dev/null 2>&1 || true + exit 1 + fi + else + # 本轮不满足合并条件,重置405计数器 + MERGE_405_COUNT=0 + fi + + if [ "$ANY_FAILED" = "true" ]; then + echo + echo "CI有失败项,不自动合并" + exit 0 + fi + + sleep 30 +done + +echo +echo "快速检查超时(3分钟),CI尚未全绿或无审批,退出等待下次触发" +exit 0