ffd02a3ec8
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 16s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Failing after 27s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 54s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 38s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m16s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m5s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m17s
AI Code Review / AI Code Review (pull_request) Successful in 5m51s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 5m37s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 7m21s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 3m58s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Failing after 57s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 18s
问题:auto-merge job 和 auto-approve job 并行执行,auto-merge 在审批完成前就检查审批状态,发现无审批后直接退出。等审批完成时 merge job 已结束,无人再触发合并,PR 卡住无法自动合并。 实核:PR #1602 CI 全绿 + 已审批,但 PR 至今未合并。 auto-merge 在 6m43s 退出,auto-approve 在 8m8s 才完成。 修复: 1. pr-automation.yml: auto-merge 添加 needs: [auto-approve], 确保审批完成后再尝试合并 2. pr-automation.yml: auto-approve timeout 从 3min 增至 10min, auto-merge timeout 从 3min 增至 15min 3. auto_merge.sh: 将单次检查改为轮询模式(30s初始等待 + 60次×10s 轮询 = 最多10分钟),CI pending 时 return 1 继续重试而非 exit 0 直接退出
164 lines
5.6 KiB
Bash
Executable File
164 lines
5.6 KiB
Bash
Executable File
#!/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
|