#!/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 echo "PR #${PR_NUMBER} - 检查CI状态+审批并自动合并到${BASE_REF}" echo echo "模式: 短作业(只检查一次,不满足则退出,由pr-auto-scan定时兜底)" 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})" echo # 使用统一的CI Gate门禁(单一检查点,自动处理前端/后端/全栈跳过逻辑) CONTEXTS=( "CI/CD Pipeline / CI Gate (pull_request)" ) echo "检查CI Gate统一门禁" echo # 等待30秒后开始轮询,最多10分钟 echo "等待30秒让CI启动..." sleep 30 # 405计数器(单次运行内重试) MERGE_405_COUNT=0 MAX_405_RETRIES=3 check_and_merge() { ALL_SUCCESS=true ANY_FAILED=false ANY_PENDING=false echo "--- 检查CI状态 ($(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 [ "$ANY_FAILED" = "true" ]; then echo echo "❌ CI有失败项,不自动合并" exit 0 fi # CI未全绿(pending中)→ 退出,等下次触发 if [ "$ALL_SUCCESS" != "true" ]; then echo echo "⏳ CI尚未全绿(仍有pending),等待重试..." echo " (当前第${attempt}次轮询,最多${MAX_ATTEMPTS}次)" return 1 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 } # 轮询等待CI就绪+审批完成,最多10分钟(60次x10秒) MAX_ATTEMPTS=60 for attempt in $(seq 1 $MAX_ATTEMPTS); do if check_and_merge; then exit 0 fi # 检查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',''))" 2>/dev/null || echo "?") if [ "$PR_STATE" != "open" ]; then echo "PR状态为 ${PR_STATE},无需继续等待" exit 0 fi if [ $attempt -lt $MAX_ATTEMPTS ]; then sleep 10 fi done echo echo "⏰ 等待10分钟后仍未满足合并条件,退出。pr-auto-scan定时扫描会继续重试。" exit 0