Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dfb2feef8a | |||
| b3ef7bb041 | |||
| a1a272b833 | |||
| 728db0faf8 | |||
| 7e5e412f7f | |||
| df08161630 | |||
| 0c9375ff32 | |||
| ef344e9ffc | |||
| 0d4904433e | |||
| 708662394f | |||
| 9b034764ad | |||
| 8748b43070 | |||
| d213a055a1 | |||
| 2371860f82 | |||
| dbd956fc6e | |||
| 1d59ee5336 |
@@ -0,0 +1,165 @@
|
|||||||
|
name: Auto Approve CI PRs
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [synchronize, opened, ready_for_review]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
auto-approve:
|
||||||
|
name: Auto Approve on CI Green
|
||||||
|
runs-on: ci-l1
|
||||||
|
if: github.event_name == 'pull_request' && !github.event.pull_request.draft
|
||||||
|
timeout-minutes: 20
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Auto approve when CI passes
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
REVIEW_TOKEN: ${{ secrets.REVIEW_GITEA_TOKEN }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
echo "PR #${PR_NUMBER} - 检查CI状态并自动审批"
|
||||||
|
|
||||||
|
# 检查是否纯前端改动
|
||||||
|
API_URL="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files?limit=300"
|
||||||
|
FILES=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" "$API_URL" | python3 -c "import sys,json; [print(f['filename']) for f in json.load(sys.stdin)]")
|
||||||
|
FRONTEND_COUNT=$(echo "$FILES" | grep -c '^apps/web/' || true)
|
||||||
|
BACKEND_COUNT=$(echo "$FILES" | grep -cv '^apps/web/' || true)
|
||||||
|
TOTAL=$(echo "$FILES" | grep -cv '^$' || true)
|
||||||
|
echo "变更文件: ${TOTAL} 个 (前端: ${FRONTEND_COUNT}, 后端/公共: ${BACKEND_COUNT})"
|
||||||
|
|
||||||
|
if [ "$BACKEND_COUNT" = "0" ] && [ "$FRONTEND_COUNT" -gt "0" ]; then
|
||||||
|
SKIP_BACKEND=true
|
||||||
|
echo "✅ 纯前端改动,只检查Frontend Lint"
|
||||||
|
else
|
||||||
|
SKIP_BACKEND=false
|
||||||
|
echo "🔧 包含后端/公共变更,检查全部CI"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 定义需要检查的context
|
||||||
|
# 根据目标分支决定检查哪些门禁
|
||||||
|
TARGET_BRANCH="${GITHUB_BASE_REF}"
|
||||||
|
echo "目标分支: ${TARGET_BRANCH}"
|
||||||
|
|
||||||
|
if [ "$SKIP_BACKEND" = "true" ]; then
|
||||||
|
CONTEXTS=("CI/CD Pipeline / Frontend Lint (pull_request)")
|
||||||
|
elif [ "$TARGET_BRANCH" = "main" ]; then
|
||||||
|
# main分支只检查required statuses: Validate + Frontend Lint
|
||||||
|
# 不检查Tests/test(不是required门禁)
|
||||||
|
CONTEXTS=(
|
||||||
|
"CI/CD Pipeline / Validate Code Quality And Tests (pull_request)"
|
||||||
|
"CI/CD Pipeline / Frontend Lint (pull_request)"
|
||||||
|
"Tests / test (pull_request)"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
CONTEXTS=(
|
||||||
|
"CI/CD Pipeline / Validate Code Quality And Tests (pull_request)"
|
||||||
|
"CI/CD Pipeline / Unit Tests (pull_request)"
|
||||||
|
"CI/CD Pipeline / Frontend Lint (pull_request)"
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "需要通过的CI检查: ${#CONTEXTS[@]} 项"
|
||||||
|
for ctx in "${CONTEXTS[@]}"; do
|
||||||
|
echo " - $ctx"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 轮询等待,最多20分钟(120次x10秒)
|
||||||
|
for attempt in $(seq 1 120); do
|
||||||
|
ALL_SUCCESS=true
|
||||||
|
ANY_FAILED=false
|
||||||
|
|
||||||
|
echo "--- 第${attempt}次检查 ($(date '+%H:%M:%S')) ---"
|
||||||
|
|
||||||
|
# 调用辅助脚本检查每个context状态
|
||||||
|
for ctx in "${CONTEXTS[@]}"; do
|
||||||
|
STATE=$(python3 scripts/check_ci_status.py "$GITHUB_TOKEN" "$GITHUB_REPOSITORY" "$GITHUB_SHA" "$ctx")
|
||||||
|
echo " $ctx: $STATE"
|
||||||
|
|
||||||
|
if [ "$STATE" != "success" ]; then
|
||||||
|
ALL_SUCCESS=false
|
||||||
|
fi
|
||||||
|
if [ "$STATE" = "failure" ] || [ "$STATE" = "error" ]; then
|
||||||
|
ANY_FAILED=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$ALL_SUCCESS" = "true" ]; then
|
||||||
|
echo
|
||||||
|
echo "✅ 所有CI检查通过,自动审批 PR #${PR_NUMBER}"
|
||||||
|
|
||||||
|
# 检查是否已有审批(任何用户的APPROVED都算,避免重复审批)
|
||||||
|
EXISTING=$(curl -s -H "Authorization: token ${REVIEW_TOKEN}" \
|
||||||
|
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/reviews" \
|
||||||
|
| python3 -c "import sys,json; reviews=json.load(sys.stdin); print('yes' if any(r.get('state')=='APPROVED' for r in reviews) else 'no')")
|
||||||
|
|
||||||
|
if [ "$EXISTING" = "yes" ]; then
|
||||||
|
echo "ℹ️ PR #${PR_NUMBER} 已有审批,跳过"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 第一步:创建PENDING review(Gitea API需要先创建再提交)
|
||||||
|
echo "创建review..."
|
||||||
|
REVIEW_CREATE=$(curl -s -X POST \
|
||||||
|
-H "Authorization: token ${REVIEW_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"event": "PENDING", "body": "CI全绿,自动审批通过。"}' \
|
||||||
|
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/reviews")
|
||||||
|
|
||||||
|
REVIEW_ID=$(echo "$REVIEW_CREATE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))")
|
||||||
|
REVIEW_STATE=$(echo "$REVIEW_CREATE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('state',''))")
|
||||||
|
echo "创建结果: id=$REVIEW_ID state=$REVIEW_STATE"
|
||||||
|
|
||||||
|
if [ -z "$REVIEW_ID" ]; then
|
||||||
|
echo "❌ 创建review失败"
|
||||||
|
echo "$REVIEW_CREATE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 如果已经是APPROVED就不用再submit了(兼容不同Gitea版本)
|
||||||
|
if [ "$REVIEW_STATE" = "APPROVED" ]; then
|
||||||
|
echo "✅ 自动审批成功(直接创建为APPROVED)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 第二步:submit review为APPROVED
|
||||||
|
echo "提交review审批..."
|
||||||
|
SUBMIT_CODE=$(curl -s -o /tmp/submit_resp.json -w "%{http_code}" \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${REVIEW_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"event": "APPROVED", "body": "CI全绿,自动审批通过。"}' \
|
||||||
|
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/reviews/${REVIEW_ID}")
|
||||||
|
|
||||||
|
echo "提交API HTTP状态: $SUBMIT_CODE"
|
||||||
|
cat /tmp/submit_resp.json 2>/dev/null || true
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [ "$SUBMIT_CODE" = "200" ] || [ "$SUBMIT_CODE" = "201" ]; then
|
||||||
|
FINAL_STATE=$(python3 -c "import json; print(json.load(open('/tmp/submit_resp.json')).get('state',''))" 2>/dev/null || echo "?")
|
||||||
|
echo "✅ 自动审批成功 (state: $FINAL_STATE)"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "❌ 提交审批失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ANY_FAILED" = "true" ]; then
|
||||||
|
echo
|
||||||
|
echo "❌ CI检查有失败项,不自动审批"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "⏰ 等待超时(20分钟),CI尚未全部完成"
|
||||||
|
exit 0
|
||||||
+122
-12
@@ -1,21 +1,131 @@
|
|||||||
name: Auto Merge PRs
|
name: Auto Merge PRs (main)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
pull_request:
|
||||||
- cron: '0 */6 * * *'
|
types: [synchronize, opened, ready_for_review, review_requested]
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
auto-merge:
|
auto-merge:
|
||||||
|
name: Auto Merge on CI Green + Approved (main)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'pull_request' && !github.event.pull_request.draft && github.event.pull_request.base.ref == 'main'
|
||||||
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Auto merge develop PRs
|
- name: Auto merge when CI passes and approved
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
MERGE_TOKEN: ${{ secrets.REVIEW_GITEA_TOKEN }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||||||
run: |
|
run: |
|
||||||
bash scripts/auto_merge_prs.sh develop
|
set -eu
|
||||||
|
|
||||||
- name: Auto merge main PRs (release only)
|
echo "PR #${PR_NUMBER} - 检查CI状态+审批并自动合并到${BASE_REF}"
|
||||||
run: |
|
echo
|
||||||
bash scripts/auto_merge_prs.sh main
|
|
||||||
|
# 只合main分支
|
||||||
|
if [ "$BASE_REF" != "main" ]; then
|
||||||
|
echo "Skip: 目标分支不是main"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# main分支门禁:Validate + Frontend Lint
|
||||||
|
CONTEXTS=(
|
||||||
|
"CI/CD Pipeline / Validate Code Quality And Tests (pull_request)"
|
||||||
|
"Tests / test (pull_request)"
|
||||||
|
"CI/CD Pipeline / Frontend Lint (pull_request)"
|
||||||
|
)
|
||||||
|
echo "检查门禁: ${#CONTEXTS[@]} 项"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 轮询等待,最多30分钟(180次x10秒)
|
||||||
|
for attempt in $(seq 1 180); do
|
||||||
|
ALL_SUCCESS=true
|
||||||
|
ANY_FAILED=false
|
||||||
|
|
||||||
|
echo "--- 第${attempt}次检查 ($(date '+%H:%M:%S')) ---"
|
||||||
|
|
||||||
|
# 检查CI状态
|
||||||
|
for ctx in "${CONTEXTS[@]}"; do
|
||||||
|
STATE=$(python3 scripts/check_ci_status.py "$GITHUB_TOKEN" "$GITHUB_REPOSITORY" "$GITHUB_SHA" "$ctx")
|
||||||
|
echo " CI: ${ctx##*/}: $STATE"
|
||||||
|
if [ "$STATE" != "success" ]; then
|
||||||
|
ALL_SUCCESS=false
|
||||||
|
fi
|
||||||
|
if [ "$STATE" = "failure" ] || [ "$STATE" = "error" ]; then
|
||||||
|
ANY_FAILED=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 检查审批状态
|
||||||
|
APPROVAL_RESULT=$(python3 scripts/check_pr_approval.py "$MERGE_TOKEN" "$GITHUB_REPOSITORY" "$PR_NUMBER" 1)
|
||||||
|
echo " 审批: $APPROVAL_RESULT"
|
||||||
|
HAS_APPROVAL=false
|
||||||
|
if echo "$APPROVAL_RESULT" | grep -q '^approved'; then
|
||||||
|
HAS_APPROVAL=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 全部满足 → 合并
|
||||||
|
if [ "$ALL_SUCCESS" = "true" ] && [ "$HAS_APPROVAL" = "true" ]; then
|
||||||
|
echo
|
||||||
|
echo "CI全绿 + 审批通过,执行自动合并"
|
||||||
|
|
||||||
|
# 幂等检查: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
|
||||||
|
|
||||||
|
# 执行merge(main分支用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":"merge","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
|
||||||
|
echo "合并失败(405),可能有冲突或门禁未通过"
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: token ${MERGE_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"body": "Auto merge failed: PR may have conflicts or unresolved checks. Please review manually."}' \
|
||||||
|
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" > /dev/null 2>&1 || true
|
||||||
|
exit 0
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ANY_FAILED" = "true" ]; then
|
||||||
|
echo
|
||||||
|
echo "CI有失败项,不自动合并"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "等待超时(30分钟)"
|
||||||
|
exit 0
|
||||||
|
|||||||
@@ -128,6 +128,27 @@ jobs:
|
|||||||
grep -q "Running upgrade" /tmp/alembic-upgrade.sql
|
grep -q "Running upgrade" /tmp/alembic-upgrade.sql
|
||||||
python3 scripts/check_schema_metadata.py
|
python3 scripts/check_schema_metadata.py
|
||||||
|
|
||||||
|
- name: Start Redis for unit tests
|
||||||
|
shell: sh
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
docker rm -f ci-redis-validate 2>/dev/null || true
|
||||||
|
docker run -d --name ci-redis-validate \
|
||||||
|
-p 6379:6379 \
|
||||||
|
--health-cmd "redis-cli ping" \
|
||||||
|
--health-interval 2s \
|
||||||
|
--health-timeout 2s \
|
||||||
|
--health-retries 15 \
|
||||||
|
redis:7-alpine
|
||||||
|
for i in $(seq 1 20); do
|
||||||
|
if docker inspect --format='{{.State.Health.Status}}' ci-redis-validate 2>/dev/null | grep -q healthy; then
|
||||||
|
echo "Redis is ready"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Waiting for Redis... ($i/20)"
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
docker inspect --format='{{.State.Health.Status}}' ci-redis-validate | grep -q healthy
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
shell: sh
|
shell: sh
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
name: Test SSH Secret
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [develop]
|
|
||||||
paths:
|
|
||||||
- '.gitea/workflows/test-ssh-secret.yml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-ssh:
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Install SSH client
|
|
||||||
run: |
|
|
||||||
which ssh || (apt-get update && apt-get install -y openssh-client)
|
|
||||||
ssh -V
|
|
||||||
|
|
||||||
- name: Debug environment
|
|
||||||
run: |
|
|
||||||
echo "=== Environment ==="
|
|
||||||
echo "Runner hostname: $(hostname)"
|
|
||||||
echo "Runner IP: $(hostname -i || echo 'unknown')"
|
|
||||||
echo "Current user: $(whoami)"
|
|
||||||
echo "=== Secrets check ==="
|
|
||||||
if [ -n "$STAGING_SSH_HOST" ]; then
|
|
||||||
echo "STAGING_SSH_HOST: [SET] value_length=${#STAGING_SSH_HOST}"
|
|
||||||
else
|
|
||||||
echo "STAGING_SSH_HOST: [EMPTY]"
|
|
||||||
fi
|
|
||||||
if [ -n "$STAGING_SSH_USER" ]; then
|
|
||||||
echo "STAGING_SSH_USER: [SET] value_length=${#STAGING_SSH_USER}"
|
|
||||||
else
|
|
||||||
echo "STAGING_SSH_USER: [EMPTY]"
|
|
||||||
fi
|
|
||||||
if [ -n "$STAGING_SSH_KEY" ]; then
|
|
||||||
echo "STAGING_SSH_KEY: [SET] value_length=${#STAGING_SSH_KEY}"
|
|
||||||
else
|
|
||||||
echo "STAGING_SSH_KEY: [EMPTY]"
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
STAGING_SSH_HOST: ${{ secrets.STAGING_SSH_HOST }}
|
|
||||||
STAGING_SSH_USER: ${{ secrets.STAGING_SSH_USER }}
|
|
||||||
STAGING_SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
|
|
||||||
|
|
||||||
- name: Setup SSH key
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
chmod 700 ~/.ssh
|
|
||||||
echo "$STAGING_SSH_KEY" > ~/.ssh/id_ed25519
|
|
||||||
chmod 600 ~/.ssh/id_ed25519
|
|
||||||
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub 2>/dev/null || echo "No public key generated"
|
|
||||||
echo "=== SSH Key fingerprint ==="
|
|
||||||
ssh-keygen -lf ~/.ssh/id_ed25519 || echo "Key fingerprint failed"
|
|
||||||
env:
|
|
||||||
STAGING_SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
|
|
||||||
|
|
||||||
- name: Test SSH connection
|
|
||||||
run: |
|
|
||||||
echo "Attempting SSH connection to $STAGING_SSH_HOST..."
|
|
||||||
ssh -i ~/.ssh/id_ed25519 \
|
|
||||||
-o StrictHostKeyChecking=no \
|
|
||||||
-o UserKnownHostsFile=/dev/null \
|
|
||||||
-o ConnectTimeout=10 \
|
|
||||||
-o BatchMode=yes \
|
|
||||||
-v \
|
|
||||||
$STAGING_SSH_USER@$STAGING_SSH_HOST "echo 'SSH_CONNECTION_SUCCESS' && hostname && whoami"
|
|
||||||
echo "=== SSH Test Complete ==="
|
|
||||||
env:
|
|
||||||
STAGING_SSH_HOST: ${{ secrets.STAGING_SSH_HOST }}
|
|
||||||
STAGING_SSH_USER: ${{ secrets.STAGING_SSH_USER }}
|
|
||||||
@@ -11,9 +11,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
shell: sh
|
shell: sh
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
python - <<'PY'
|
python3 - <<'PY'
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import tarfile
|
import tarfile
|
||||||
@@ -93,9 +95,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
shell: sh
|
shell: sh
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
python - <<'PY'
|
python3 - <<'PY'
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import tarfile
|
import tarfile
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""检查指定commit的CI status状态。
|
||||||
|
|
||||||
|
用法: python3 check_ci_status.py <token> <repo> <sha> <context>
|
||||||
|
返回: 打印状态 (success/failure/pending/error)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 5:
|
||||||
|
print("pending")
|
||||||
|
return
|
||||||
|
|
||||||
|
token = sys.argv[1]
|
||||||
|
repo = sys.argv[2]
|
||||||
|
sha = sys.argv[3]
|
||||||
|
target_context = sys.argv[4]
|
||||||
|
|
||||||
|
api_url = f"https://git.xiaoxiajianji.com/api/v1/repos/{repo}/commits/{sha}/statuses?per_page=100"
|
||||||
|
req = urllib.request.Request(api_url, headers={"Authorization": f"token {token}"})
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||||
|
statuses = json.loads(resp.read().decode())
|
||||||
|
except Exception:
|
||||||
|
print("pending")
|
||||||
|
return
|
||||||
|
|
||||||
|
# API返回按时间倒序,第一个就是最新的
|
||||||
|
for s in statuses:
|
||||||
|
if s.get("context") == target_context:
|
||||||
|
print(s.get("status", "pending"))
|
||||||
|
return
|
||||||
|
|
||||||
|
print("pending")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""检查PR是否有至少N个APPROVED审批。
|
||||||
|
|
||||||
|
用法: python3 check_pr_approval.py <token> <repo> <pr_number> <min_approval>
|
||||||
|
返回: 打印 "approved" 或 "pending"
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 5:
|
||||||
|
print("pending")
|
||||||
|
return
|
||||||
|
|
||||||
|
token = sys.argv[1]
|
||||||
|
repo = sys.argv[2]
|
||||||
|
pr_number = sys.argv[3]
|
||||||
|
min_approval = int(sys.argv[4])
|
||||||
|
|
||||||
|
api_url = f"https://git.xiaoxiajianji.com/api/v1/repos/{repo}/pulls/{pr_number}/reviews"
|
||||||
|
req = urllib.request.Request(api_url, headers={"Authorization": f"token {token}"})
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||||
|
reviews = json.loads(resp.read().decode())
|
||||||
|
except Exception:
|
||||||
|
print("pending")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 统计APPROVED的人数(去重,同一人多次审批只算一次)
|
||||||
|
approvers = set()
|
||||||
|
for r in reviews:
|
||||||
|
if r.get("state") == "APPROVED":
|
||||||
|
approvers.add(r.get("user", {}).get("login", ""))
|
||||||
|
|
||||||
|
if len(approvers) >= min_approval:
|
||||||
|
print(f"approved ({len(approvers)})")
|
||||||
|
else:
|
||||||
|
print(f"pending ({len(approvers)})")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user