Revert "feat: Implement automated PR merging with comprehensive quality gates"
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
This reverts commit 7c9bb67fcd.
This commit is contained in:
@@ -1,339 +0,0 @@
|
|||||||
# Woodpecker CI - Enhanced Quality Gates + Auto-Merge
|
|
||||||
# Features:
|
|
||||||
# - Strict quality gates (all checks must pass)
|
|
||||||
# - Security scanning (SAST, dependency audit, secrets)
|
|
||||||
# - Test coverage enforcement (≥85%)
|
|
||||||
# - Automated PR merging when all checks pass
|
|
||||||
|
|
||||||
when:
|
|
||||||
- event: [push, pull_request, manual]
|
|
||||||
|
|
||||||
variables:
|
|
||||||
- &node_image "node:20-alpine"
|
|
||||||
- &install_deps |
|
|
||||||
corepack enable
|
|
||||||
pnpm install --frozen-lockfile
|
|
||||||
- &use_deps |
|
|
||||||
corepack enable
|
|
||||||
- &kaniko_setup |
|
|
||||||
mkdir -p /kaniko/.docker
|
|
||||||
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# ======================
|
|
||||||
# PHASE 1: Setup
|
|
||||||
# ======================
|
|
||||||
install:
|
|
||||||
image: *node_image
|
|
||||||
commands:
|
|
||||||
- *install_deps
|
|
||||||
|
|
||||||
prisma-generate:
|
|
||||||
image: *node_image
|
|
||||||
environment:
|
|
||||||
SKIP_ENV_VALIDATION: "true"
|
|
||||||
commands:
|
|
||||||
- *use_deps
|
|
||||||
- pnpm --filter "@mosaic/api" prisma:generate
|
|
||||||
depends_on:
|
|
||||||
- install
|
|
||||||
|
|
||||||
# ======================
|
|
||||||
# PHASE 2: Security Review
|
|
||||||
# ======================
|
|
||||||
security-audit-deps:
|
|
||||||
image: *node_image
|
|
||||||
commands:
|
|
||||||
- *use_deps
|
|
||||||
- echo "=== Dependency Security Audit ==="
|
|
||||||
- pnpm audit --audit-level=high
|
|
||||||
depends_on:
|
|
||||||
- install
|
|
||||||
failure: fail # STRICT: Block on security vulnerabilities
|
|
||||||
|
|
||||||
security-scan-secrets:
|
|
||||||
image: alpine/git:latest
|
|
||||||
commands:
|
|
||||||
- echo "=== Secret Scanning ==="
|
|
||||||
- apk add --no-cache bash
|
|
||||||
- |
|
|
||||||
# Check for common secrets patterns
|
|
||||||
echo "Scanning for hardcoded secrets..."
|
|
||||||
if git grep -E "(password|secret|api_key|private_key)\s*=\s*['\"]" -- '*.ts' '*.tsx' '*.js' '*.jsx' ':!*test*' ':!*spec*'; then
|
|
||||||
echo "❌ Found hardcoded secrets!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- echo "✅ No hardcoded secrets detected"
|
|
||||||
depends_on:
|
|
||||||
- install
|
|
||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
failure: fail # STRICT: Block on secret detection
|
|
||||||
|
|
||||||
security-scan-sast:
|
|
||||||
image: returntocorp/semgrep
|
|
||||||
commands:
|
|
||||||
- echo "=== SAST Security Scanning ==="
|
|
||||||
- |
|
|
||||||
semgrep scan \
|
|
||||||
--config=auto \
|
|
||||||
--error \
|
|
||||||
--exclude='node_modules' \
|
|
||||||
--exclude='dist' \
|
|
||||||
--exclude='*.test.ts' \
|
|
||||||
--exclude='*.spec.ts' \
|
|
||||||
--metrics=off \
|
|
||||||
--quiet \
|
|
||||||
|| true # TODO: Make strict after baseline cleanup
|
|
||||||
- echo "✅ SAST scan complete"
|
|
||||||
depends_on:
|
|
||||||
- install
|
|
||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
failure: ignore # TODO: Change to 'fail' after fixing baseline issues
|
|
||||||
|
|
||||||
# ======================
|
|
||||||
# PHASE 3: Code Review
|
|
||||||
# ======================
|
|
||||||
lint:
|
|
||||||
image: *node_image
|
|
||||||
environment:
|
|
||||||
SKIP_ENV_VALIDATION: "true"
|
|
||||||
commands:
|
|
||||||
- *use_deps
|
|
||||||
- echo "=== Lint Check ==="
|
|
||||||
- pnpm lint
|
|
||||||
depends_on:
|
|
||||||
- install
|
|
||||||
when:
|
|
||||||
- evaluate: 'CI_PIPELINE_EVENT != "pull_request" || CI_COMMIT_BRANCH != "main"'
|
|
||||||
failure: fail # STRICT: Block on lint errors
|
|
||||||
|
|
||||||
typecheck:
|
|
||||||
image: *node_image
|
|
||||||
environment:
|
|
||||||
SKIP_ENV_VALIDATION: "true"
|
|
||||||
commands:
|
|
||||||
- *use_deps
|
|
||||||
- echo "=== TypeScript Type Check ==="
|
|
||||||
- pnpm typecheck
|
|
||||||
depends_on:
|
|
||||||
- prisma-generate
|
|
||||||
failure: fail # STRICT: Block on type errors
|
|
||||||
|
|
||||||
# ======================
|
|
||||||
# PHASE 4: QA
|
|
||||||
# ======================
|
|
||||||
test-unit:
|
|
||||||
image: *node_image
|
|
||||||
environment:
|
|
||||||
SKIP_ENV_VALIDATION: "true"
|
|
||||||
commands:
|
|
||||||
- *use_deps
|
|
||||||
- echo "=== Unit Tests ==="
|
|
||||||
- pnpm test
|
|
||||||
depends_on:
|
|
||||||
- prisma-generate
|
|
||||||
failure: fail # STRICT: Block on test failures
|
|
||||||
|
|
||||||
test-coverage:
|
|
||||||
image: *node_image
|
|
||||||
environment:
|
|
||||||
SKIP_ENV_VALIDATION: "true"
|
|
||||||
commands:
|
|
||||||
- *use_deps
|
|
||||||
- echo "=== Test Coverage Check ==="
|
|
||||||
- |
|
|
||||||
pnpm test:coverage --reporter=json --reporter=text > coverage-output.txt 2>&1 || true
|
|
||||||
cat coverage-output.txt
|
|
||||||
# TODO: Parse coverage report and enforce ≥85% threshold
|
|
||||||
echo "⚠️ Coverage enforcement not yet implemented"
|
|
||||||
depends_on:
|
|
||||||
- prisma-generate
|
|
||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
failure: ignore # TODO: Change to 'fail' after implementing coverage parser
|
|
||||||
|
|
||||||
# ======================
|
|
||||||
# PHASE 5: Build Verification
|
|
||||||
# ======================
|
|
||||||
build:
|
|
||||||
image: *node_image
|
|
||||||
environment:
|
|
||||||
SKIP_ENV_VALIDATION: "true"
|
|
||||||
NODE_ENV: "production"
|
|
||||||
commands:
|
|
||||||
- *use_deps
|
|
||||||
- echo "=== Production Build ==="
|
|
||||||
- pnpm build
|
|
||||||
depends_on:
|
|
||||||
- typecheck
|
|
||||||
- security-audit-deps
|
|
||||||
- prisma-generate
|
|
||||||
failure: fail # STRICT: Block on build failures
|
|
||||||
|
|
||||||
# ======================
|
|
||||||
# PHASE 6: Auto-Merge (PR only)
|
|
||||||
# ======================
|
|
||||||
pr-auto-merge:
|
|
||||||
image: alpine:latest
|
|
||||||
secrets:
|
|
||||||
- gitea_token
|
|
||||||
commands:
|
|
||||||
- echo "=== PR Auto-Merge Check ==="
|
|
||||||
- apk add --no-cache curl jq
|
|
||||||
- |
|
|
||||||
# Only run for PRs targeting develop
|
|
||||||
if [ "$CI_PIPELINE_EVENT" != "pull_request" ]; then
|
|
||||||
echo "⏭️ Skipping: Not a pull request"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract PR number from CI environment
|
|
||||||
PR_NUMBER=$(echo "$CI_COMMIT_REF" | grep -oP 'pull/\K\d+' || echo "")
|
|
||||||
if [ -z "$PR_NUMBER" ]; then
|
|
||||||
echo "⏭️ Skipping: Cannot determine PR number"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "📋 Checking PR #$PR_NUMBER for auto-merge eligibility..."
|
|
||||||
|
|
||||||
# Get PR details
|
|
||||||
PR_DATA=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
|
|
||||||
"https://git.mosaicstack.dev/api/v1/repos/mosaic/stack/pulls/$PR_NUMBER")
|
|
||||||
|
|
||||||
# Check if PR is mergeable
|
|
||||||
IS_MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false')
|
|
||||||
BASE_BRANCH=$(echo "$PR_DATA" | jq -r '.base.ref // ""')
|
|
||||||
PR_STATE=$(echo "$PR_DATA" | jq -r '.state // ""')
|
|
||||||
|
|
||||||
if [ "$PR_STATE" != "open" ]; then
|
|
||||||
echo "⏭️ Skipping: PR is not open (state: $PR_STATE)"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$BASE_BRANCH" != "develop" ]; then
|
|
||||||
echo "⏭️ Skipping: PR does not target develop (targets: $BASE_BRANCH)"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$IS_MERGEABLE" != "true" ]; then
|
|
||||||
echo "❌ PR is not mergeable (conflicts or other issues)"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# All checks passed - merge the PR
|
|
||||||
echo "✅ All quality gates passed - attempting auto-merge..."
|
|
||||||
MERGE_RESULT=$(curl -s -X POST \
|
|
||||||
-H "Authorization: token $GITEA_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"Do":"merge","MergeMessageField":"","MergeTitleField":"","delete_branch_after_merge":true,"force_merge":false,"merge_when_checks_succeed":false}' \
|
|
||||||
"https://git.mosaicstack.dev/api/v1/repos/mosaic/stack/pulls/$PR_NUMBER/merge")
|
|
||||||
|
|
||||||
if echo "$MERGE_RESULT" | jq -e '.merged' > /dev/null 2>&1; then
|
|
||||||
echo "🎉 PR #$PR_NUMBER successfully merged to develop!"
|
|
||||||
else
|
|
||||||
ERROR_MSG=$(echo "$MERGE_RESULT" | jq -r '.message // "Unknown error"')
|
|
||||||
echo "❌ Failed to merge PR: $ERROR_MSG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
depends_on:
|
|
||||||
- build
|
|
||||||
- test-unit
|
|
||||||
- lint
|
|
||||||
- typecheck
|
|
||||||
- security-audit-deps
|
|
||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
evaluate: 'CI_COMMIT_TARGET_BRANCH == "develop"'
|
|
||||||
failure: ignore # Don't fail pipeline if auto-merge fails
|
|
||||||
|
|
||||||
# ======================
|
|
||||||
# PHASE 7: Docker Build & Push (develop/main only)
|
|
||||||
# ======================
|
|
||||||
docker-build-api:
|
|
||||||
image: gcr.io/kaniko-project/executor:debug
|
|
||||||
environment:
|
|
||||||
GITEA_USER:
|
|
||||||
from_secret: gitea_username
|
|
||||||
GITEA_TOKEN:
|
|
||||||
from_secret: gitea_token
|
|
||||||
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
||||||
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
|
||||||
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
|
||||||
commands:
|
|
||||||
- *kaniko_setup
|
|
||||||
- |
|
|
||||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/api:${CI_COMMIT_SHA:0:8}"
|
|
||||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/api:latest"
|
|
||||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/api:dev"
|
|
||||||
fi
|
|
||||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/api:$CI_COMMIT_TAG"
|
|
||||||
fi
|
|
||||||
/kaniko/executor --context . --dockerfile apps/api/Dockerfile $DESTINATIONS
|
|
||||||
when:
|
|
||||||
- branch: [main, develop]
|
|
||||||
event: [push, manual, tag]
|
|
||||||
depends_on:
|
|
||||||
- build
|
|
||||||
|
|
||||||
docker-build-web:
|
|
||||||
image: gcr.io/kaniko-project/executor:debug
|
|
||||||
environment:
|
|
||||||
GITEA_USER:
|
|
||||||
from_secret: gitea_username
|
|
||||||
GITEA_TOKEN:
|
|
||||||
from_secret: gitea_token
|
|
||||||
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
||||||
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
|
||||||
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
|
||||||
commands:
|
|
||||||
- *kaniko_setup
|
|
||||||
- |
|
|
||||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/web:${CI_COMMIT_SHA:0:8}"
|
|
||||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/web:latest"
|
|
||||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/web:dev"
|
|
||||||
fi
|
|
||||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/web:$CI_COMMIT_TAG"
|
|
||||||
fi
|
|
||||||
/kaniko/executor --context . --dockerfile apps/web/Dockerfile --build-arg NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev $DESTINATIONS
|
|
||||||
when:
|
|
||||||
- branch: [main, develop]
|
|
||||||
event: [push, manual, tag]
|
|
||||||
depends_on:
|
|
||||||
- build
|
|
||||||
|
|
||||||
docker-build-postgres:
|
|
||||||
image: gcr.io/kaniko-project/executor:debug
|
|
||||||
environment:
|
|
||||||
GITEA_USER:
|
|
||||||
from_secret: gitea_username
|
|
||||||
GITEA_TOKEN:
|
|
||||||
from_secret: gitea_token
|
|
||||||
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
||||||
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
|
||||||
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
|
||||||
commands:
|
|
||||||
- *kaniko_setup
|
|
||||||
- |
|
|
||||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/postgres:${CI_COMMIT_SHA:0:8}"
|
|
||||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/postgres:latest"
|
|
||||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/postgres:dev"
|
|
||||||
fi
|
|
||||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
||||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/postgres:$CI_COMMIT_TAG"
|
|
||||||
fi
|
|
||||||
/kaniko/executor --context docker/postgres --dockerfile docker/postgres/Dockerfile $DESTINATIONS
|
|
||||||
when:
|
|
||||||
- branch: [main, develop]
|
|
||||||
event: [push, manual, tag]
|
|
||||||
depends_on:
|
|
||||||
- build
|
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
# Automated PR Merge System
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Mosaic Stack automated PR merge system ensures all pull requests meet strict quality, security, and testing standards before being merged to `develop`. PRs are automatically merged when all quality gates pass, eliminating manual intervention while maintaining high code quality.
|
|
||||||
|
|
||||||
## Quality Gates
|
|
||||||
|
|
||||||
All quality gates must pass before a PR can be auto-merged:
|
|
||||||
|
|
||||||
### 1. Code Review ✅
|
|
||||||
|
|
||||||
- **Lint:** ESLint with strict rules, no warnings allowed
|
|
||||||
- **Type Safety:** TypeScript strict mode, no type errors
|
|
||||||
- **Build:** Production build must succeed
|
|
||||||
- **Pre-commit:** Automated via lint-staged (already strict)
|
|
||||||
|
|
||||||
### 2. Security Review 🔒
|
|
||||||
|
|
||||||
- **Dependency Audit:** `pnpm audit` with high severity threshold
|
|
||||||
- **Secret Scanning:** Detects hardcoded passwords, API keys, tokens
|
|
||||||
- **SAST:** Static analysis security testing (Semgrep)
|
|
||||||
- **License Compliance:** (Planned)
|
|
||||||
|
|
||||||
### 3. Quality Assurance 🧪
|
|
||||||
|
|
||||||
- **Unit Tests:** All tests must pass
|
|
||||||
- **Test Coverage:** ≥85% coverage requirement (enforced)
|
|
||||||
- **Integration Tests:** (Planned)
|
|
||||||
- **E2E Tests:** (Planned)
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
### 1. Developer Creates PR
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create feature branch
|
|
||||||
git checkout -b feature/my-feature develop
|
|
||||||
|
|
||||||
# Make changes, commit
|
|
||||||
git add .
|
|
||||||
git commit -m "feat: add new feature"
|
|
||||||
|
|
||||||
# Push and create PR
|
|
||||||
git push -u origin feature/my-feature
|
|
||||||
tea pr create --base develop --title "feat: add new feature"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. CI Pipeline Runs
|
|
||||||
|
|
||||||
Woodpecker CI automatically runs all quality gates:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[PR Created] --> B[Install Dependencies]
|
|
||||||
B --> C[Security Audit]
|
|
||||||
B --> D[Secret Scanning]
|
|
||||||
B --> E[SAST Scanning]
|
|
||||||
B --> F[Lint Check]
|
|
||||||
B --> G[Type Check]
|
|
||||||
B --> H[Unit Tests]
|
|
||||||
H --> I[Coverage Check]
|
|
||||||
C --> J{All Checks Pass?}
|
|
||||||
D --> J
|
|
||||||
E --> J
|
|
||||||
F --> J
|
|
||||||
G --> J
|
|
||||||
I --> J
|
|
||||||
J -->|Yes| K[Build Verification]
|
|
||||||
K --> L[Auto-Merge to develop]
|
|
||||||
J -->|No| M[Block Merge]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Automatic Merge
|
|
||||||
|
|
||||||
If all checks pass:
|
|
||||||
|
|
||||||
- ✅ PR automatically merges to `develop`
|
|
||||||
- ✅ Feature branch automatically deleted
|
|
||||||
- ✅ Developer notified of successful merge
|
|
||||||
|
|
||||||
If any check fails:
|
|
||||||
|
|
||||||
- ❌ PR blocked from merging
|
|
||||||
- ❌ Developer notified of failure
|
|
||||||
- ❌ Must fix issues before retry
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Woodpecker CI
|
|
||||||
|
|
||||||
The enhanced Woodpecker CI configuration is in `.woodpecker.enhanced.yml`. Key features:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Strict quality gates (all must pass)
|
|
||||||
lint:
|
|
||||||
failure: fail # Block on any lint error/warning
|
|
||||||
|
|
||||||
typecheck:
|
|
||||||
failure: fail # Block on any type error
|
|
||||||
|
|
||||||
test-unit:
|
|
||||||
failure: fail # Block on any test failure
|
|
||||||
|
|
||||||
# Security scanning
|
|
||||||
security-audit-deps:
|
|
||||||
failure: fail # Block on high/critical vulnerabilities
|
|
||||||
|
|
||||||
security-scan-secrets:
|
|
||||||
failure: fail # Block on hardcoded secrets
|
|
||||||
|
|
||||||
# Auto-merge step (runs after all checks pass)
|
|
||||||
pr-auto-merge:
|
|
||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
evaluate: 'CI_COMMIT_TARGET_BRANCH == "develop"'
|
|
||||||
depends_on:
|
|
||||||
- build
|
|
||||||
- test-unit
|
|
||||||
- lint
|
|
||||||
- typecheck
|
|
||||||
- security-audit-deps
|
|
||||||
```
|
|
||||||
|
|
||||||
### Required Secrets
|
|
||||||
|
|
||||||
Configure these in Gitea settings → Secrets:
|
|
||||||
|
|
||||||
- `gitea_token` - API token with repo write access
|
|
||||||
- `gitea_username` - Gitea username for Docker registry
|
|
||||||
|
|
||||||
### Branch Protection
|
|
||||||
|
|
||||||
Recommended Gitea branch protection for `develop`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"branch_name": "develop",
|
|
||||||
"enable_push": false,
|
|
||||||
"enable_push_whitelist": false,
|
|
||||||
"enable_merge_whitelist": false,
|
|
||||||
"enable_status_check": true,
|
|
||||||
"required_status_checks": [
|
|
||||||
"ci/woodpecker/pr/lint",
|
|
||||||
"ci/woodpecker/pr/typecheck",
|
|
||||||
"ci/woodpecker/pr/test-unit",
|
|
||||||
"ci/woodpecker/pr/security-audit-deps",
|
|
||||||
"ci/woodpecker/pr/build"
|
|
||||||
],
|
|
||||||
"enable_approvals_whitelist": false,
|
|
||||||
"required_approvals": 0
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Manual Auto-Merge
|
|
||||||
|
|
||||||
You can manually trigger auto-merge for a specific PR:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Set Gitea API token
|
|
||||||
export GITEA_TOKEN="your-token-here"
|
|
||||||
|
|
||||||
# Merge PR #123 to develop
|
|
||||||
./scripts/ci/auto-merge-pr.sh 123
|
|
||||||
|
|
||||||
# Dry run (check without merging)
|
|
||||||
DRY_RUN=true ./scripts/ci/auto-merge-pr.sh 123
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quality Gate Strictness Levels
|
|
||||||
|
|
||||||
### Current (Enhanced) Configuration
|
|
||||||
|
|
||||||
| Check | Status | Blocking | Notes |
|
|
||||||
| ---------------- | --------- | -------- | ---------------------------------------- |
|
|
||||||
| Dependency Audit | ✅ Active | Yes | Blocks on high+ vulnerabilities |
|
|
||||||
| Secret Scanning | ✅ Active | Yes | Blocks on hardcoded secrets |
|
|
||||||
| SAST (Semgrep) | ⚠️ Active | No\* | \*TODO: Enable after baseline cleanup |
|
|
||||||
| Lint | ✅ Active | Yes | Zero warnings/errors |
|
|
||||||
| TypeScript | ✅ Active | Yes | Strict mode, no errors |
|
|
||||||
| Unit Tests | ✅ Active | Yes | All tests must pass |
|
|
||||||
| Test Coverage | ⚠️ Active | No\* | \*TODO: Enable after implementing parser |
|
|
||||||
| Build | ✅ Active | Yes | Production build must succeed |
|
|
||||||
|
|
||||||
### Migration Plan
|
|
||||||
|
|
||||||
**Phase 1: Current State**
|
|
||||||
|
|
||||||
- Lint and tests non-blocking (`|| true`)
|
|
||||||
- Basic security audit
|
|
||||||
- Manual PR merging
|
|
||||||
|
|
||||||
**Phase 2: Enhanced (This PR)** ← WE ARE HERE
|
|
||||||
|
|
||||||
- All checks strict and blocking
|
|
||||||
- Security scanning (deps, secrets, SAST)
|
|
||||||
- Auto-merge enabled for clean PRs
|
|
||||||
|
|
||||||
**Phase 3: Future Enhancements**
|
|
||||||
|
|
||||||
- SAST fully enforced (after baseline cleanup)
|
|
||||||
- Test coverage threshold enforced (≥85%)
|
|
||||||
- Integration and E2E tests
|
|
||||||
- License compliance checking
|
|
||||||
- Performance regression testing
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### PR Not Auto-Merging
|
|
||||||
|
|
||||||
Check these common issues:
|
|
||||||
|
|
||||||
1. **Merge Conflicts**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Rebase on develop
|
|
||||||
git fetch origin develop
|
|
||||||
git rebase origin/develop
|
|
||||||
git push --force-with-lease
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Failed Quality Gates**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check CI logs
|
|
||||||
woodpecker pipeline ls mosaic/stack
|
|
||||||
woodpecker log show mosaic/stack <pipeline-number>
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Missing Status Checks**
|
|
||||||
- Ensure all required checks are configured
|
|
||||||
- Verify Woodpecker CI is running
|
|
||||||
- Check webhook configuration
|
|
||||||
|
|
||||||
### Bypassing Auto-Merge
|
|
||||||
|
|
||||||
In rare cases where manual merge is needed:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Manually merge via CLI (requires admin access)
|
|
||||||
tea pr merge <pr-number> --style merge
|
|
||||||
```
|
|
||||||
|
|
||||||
**⚠️ WARNING:** Manual merges bypass quality gates. Only use in emergencies.
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### For Developers
|
|
||||||
|
|
||||||
1. **Run checks locally before pushing:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm lint
|
|
||||||
pnpm typecheck
|
|
||||||
pnpm test
|
|
||||||
pnpm build
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Keep PRs focused:**
|
|
||||||
- One feature/fix per PR
|
|
||||||
- Smaller PRs merge faster
|
|
||||||
- Easier to review and debug
|
|
||||||
|
|
||||||
3. **Write tests first (TDD):**
|
|
||||||
- Tests before implementation
|
|
||||||
- Maintains ≥85% coverage
|
|
||||||
- Catches issues early
|
|
||||||
|
|
||||||
4. **Check CI status:**
|
|
||||||
- Monitor pipeline progress
|
|
||||||
- Fix failures immediately
|
|
||||||
- Don't stack PRs on failing ones
|
|
||||||
|
|
||||||
### For Reviewers
|
|
||||||
|
|
||||||
1. **Trust the automation:**
|
|
||||||
- If CI passes, code meets standards
|
|
||||||
- Focus on architecture and design
|
|
||||||
- Don't duplicate automated checks
|
|
||||||
|
|
||||||
2. **Review promptly:**
|
|
||||||
- PRs auto-merge when checks pass
|
|
||||||
- Review before auto-merge if needed
|
|
||||||
- Use Gitea's review features
|
|
||||||
|
|
||||||
3. **Provide constructive feedback:**
|
|
||||||
- Suggest improvements
|
|
||||||
- Link to documentation
|
|
||||||
- Explain reasoning
|
|
||||||
|
|
||||||
## Metrics & Monitoring
|
|
||||||
|
|
||||||
Track these metrics to measure effectiveness:
|
|
||||||
|
|
||||||
- **Auto-merge rate:** % of PRs merged automatically
|
|
||||||
- **Average time to merge:** From PR creation to merge
|
|
||||||
- **Quality gate failures:** Which checks fail most often
|
|
||||||
- **Rollback rate:** % of merges that need revert
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [Quality Rails Status](docs/quality-rails-status.md)
|
|
||||||
- [Woodpecker CI Documentation](https://woodpecker-ci.org/docs)
|
|
||||||
- [Gitea API Documentation](https://docs.gitea.io/en-us/api-usage/)
|
|
||||||
- [Design Principles](docs/DESIGN-PRINCIPLES.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Questions?** Contact the platform team or create an issue.
|
|
||||||
@@ -1,366 +0,0 @@
|
|||||||
# Migration Guide: Enhanced CI/CD with Auto-Merge
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This guide walks through migrating from the current Woodpecker CI configuration to the enhanced version with strict quality gates and automated PR merging.
|
|
||||||
|
|
||||||
## Pre-Migration Checklist
|
|
||||||
|
|
||||||
Before activating the enhanced pipeline, ensure:
|
|
||||||
|
|
||||||
- [ ] **All existing PRs are merged or closed**
|
|
||||||
- Enhanced pipeline has strict gates that may block old PRs
|
|
||||||
- Review and clean up pending PRs first
|
|
||||||
|
|
||||||
- [ ] **Baseline quality metrics recorded**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run on clean develop branch
|
|
||||||
pnpm lint 2>&1 | tee baseline-lint.txt
|
|
||||||
pnpm typecheck 2>&1 | tee baseline-typecheck.txt
|
|
||||||
pnpm test 2>&1 | tee baseline-tests.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Gitea API token created**
|
|
||||||
- Go to Settings → Applications → Generate New Token
|
|
||||||
- Scopes: `repo` (full control)
|
|
||||||
- Save token securely
|
|
||||||
|
|
||||||
- [ ] **Woodpecker secrets configured**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Add gitea_token secret
|
|
||||||
woodpecker secret add \
|
|
||||||
--repository mosaic/stack \
|
|
||||||
--name gitea_token \
|
|
||||||
--value "your-token-here"
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Team notified of change**
|
|
||||||
- Announce strict quality gates
|
|
||||||
- Share this migration guide
|
|
||||||
- Schedule migration during low-activity period
|
|
||||||
|
|
||||||
## Migration Steps
|
|
||||||
|
|
||||||
### Step 1: Backup Current Configuration
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backup current .woodpecker.yml
|
|
||||||
cp .woodpecker.yml .woodpecker.yml.backup
|
|
||||||
|
|
||||||
# Backup current git state
|
|
||||||
git branch backup-pre-migration
|
|
||||||
git push origin backup-pre-migration
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Activate Enhanced Configuration
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Replace .woodpecker.yml with enhanced version
|
|
||||||
cp .woodpecker.enhanced.yml .woodpecker.yml
|
|
||||||
|
|
||||||
# Review changes
|
|
||||||
git diff .woodpecker.yml.backup .woodpecker.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
Key changes:
|
|
||||||
|
|
||||||
- ✅ Removed `|| true` from lint step (now strict)
|
|
||||||
- ✅ Removed `|| true` from test step (now strict)
|
|
||||||
- ✅ Added security scanning steps
|
|
||||||
- ✅ Added test coverage step
|
|
||||||
- ✅ Added pr-auto-merge step
|
|
||||||
|
|
||||||
### Step 3: Test with a Dry-Run PR
|
|
||||||
|
|
||||||
Create a test PR to verify the enhanced pipeline:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create test branch
|
|
||||||
git checkout -b test/enhanced-ci develop
|
|
||||||
|
|
||||||
# Make a trivial change
|
|
||||||
echo "# CI Test" >> README.md
|
|
||||||
git add README.md
|
|
||||||
git commit -m "test: verify enhanced CI pipeline"
|
|
||||||
|
|
||||||
# Push and create PR
|
|
||||||
git push -u origin test/enhanced-ci
|
|
||||||
tea pr create \
|
|
||||||
--base develop \
|
|
||||||
--title "test: Verify enhanced CI pipeline" \
|
|
||||||
--description "Test PR to verify all quality gates work correctly"
|
|
||||||
```
|
|
||||||
|
|
||||||
Monitor the pipeline:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Watch pipeline status
|
|
||||||
woodpecker pipeline ls mosaic/stack
|
|
||||||
|
|
||||||
# View logs if needed
|
|
||||||
woodpecker log show mosaic/stack <pipeline-number>
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected behavior:
|
|
||||||
|
|
||||||
- ✅ All quality gates run
|
|
||||||
- ✅ Security scans complete
|
|
||||||
- ✅ Tests and coverage checks run
|
|
||||||
- ✅ PR auto-merges if all checks pass
|
|
||||||
|
|
||||||
### Step 4: Configure Branch Protection
|
|
||||||
|
|
||||||
Set up branch protection for `develop`:
|
|
||||||
|
|
||||||
**Option A: Via Gitea Web UI**
|
|
||||||
|
|
||||||
1. Go to Settings → Branches
|
|
||||||
2. Add branch protection rule for `develop`
|
|
||||||
3. Enable: "Enable Status Check"
|
|
||||||
4. Add required checks:
|
|
||||||
- `ci/woodpecker/pr/lint`
|
|
||||||
- `ci/woodpecker/pr/typecheck`
|
|
||||||
- `ci/woodpecker/pr/test-unit`
|
|
||||||
- `ci/woodpecker/pr/security-audit-deps`
|
|
||||||
- `ci/woodpecker/pr/build`
|
|
||||||
|
|
||||||
**Option B: Via API**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST "https://git.mosaicstack.dev/api/v1/repos/mosaic/stack/branch_protections" \
|
|
||||||
-H "Authorization: token $GITEA_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"branch_name": "develop",
|
|
||||||
"enable_push": false,
|
|
||||||
"enable_status_check": true,
|
|
||||||
"status_check_contexts": [
|
|
||||||
"ci/woodpecker/pr/lint",
|
|
||||||
"ci/woodpecker/pr/typecheck",
|
|
||||||
"ci/woodpecker/pr/test-unit",
|
|
||||||
"ci/woodpecker/pr/security-audit-deps",
|
|
||||||
"ci/woodpecker/pr/build"
|
|
||||||
]
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Gradual Rollout
|
|
||||||
|
|
||||||
**Phase 1: Monitor (Week 1)**
|
|
||||||
|
|
||||||
- Enhanced CI active, auto-merge disabled
|
|
||||||
- Monitor quality gate failures
|
|
||||||
- Collect metrics on pass/fail rates
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# In .woodpecker.yml, set auto-merge to dry-run:
|
|
||||||
pr-auto-merge:
|
|
||||||
commands:
|
|
||||||
- export DRY_RUN=true
|
|
||||||
- ./scripts/ci/auto-merge-pr.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
**Phase 2: Enable Auto-Merge (Week 2)**
|
|
||||||
|
|
||||||
- Remove DRY_RUN flag
|
|
||||||
- Enable auto-merge for clean PRs
|
|
||||||
- Monitor merge success rate
|
|
||||||
|
|
||||||
**Phase 3: Enforce Coverage (Week 3)**
|
|
||||||
|
|
||||||
- Enable test coverage threshold
|
|
||||||
- Set minimum to 85%
|
|
||||||
- Block PRs below threshold
|
|
||||||
|
|
||||||
**Phase 4: Full Enforcement (Week 4)**
|
|
||||||
|
|
||||||
- Enable SAST as blocking
|
|
||||||
- Enforce all quality gates
|
|
||||||
- Remove any remaining fallbacks
|
|
||||||
|
|
||||||
### Step 6: Cleanup
|
|
||||||
|
|
||||||
After successful migration:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Remove backup files
|
|
||||||
rm .woodpecker.yml.backup
|
|
||||||
git branch -D backup-pre-migration
|
|
||||||
git push origin --delete backup-pre-migration
|
|
||||||
|
|
||||||
# Remove old test PR
|
|
||||||
tea pr close <test-pr-number>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rollback Plan
|
|
||||||
|
|
||||||
If issues arise during migration:
|
|
||||||
|
|
||||||
### Immediate Rollback
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Restore original configuration
|
|
||||||
cp .woodpecker.yml.backup .woodpecker.yml
|
|
||||||
git add .woodpecker.yml
|
|
||||||
git commit -m "rollback: Restore original CI configuration"
|
|
||||||
git push origin develop
|
|
||||||
```
|
|
||||||
|
|
||||||
### Partial Rollback
|
|
||||||
|
|
||||||
If only specific gates are problematic:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Make specific checks non-blocking temporarily
|
|
||||||
lint:
|
|
||||||
commands:
|
|
||||||
- pnpm lint || true # Non-blocking during stabilization
|
|
||||||
failure: ignore
|
|
||||||
```
|
|
||||||
|
|
||||||
## Post-Migration Verification
|
|
||||||
|
|
||||||
After migration, verify:
|
|
||||||
|
|
||||||
- [ ] **All quality gates run on PRs**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check recent PR pipelines
|
|
||||||
tea pr list --state all --limit 10
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Auto-merge works correctly**
|
|
||||||
- Create test PR with passing checks
|
|
||||||
- Verify auto-merge occurs
|
|
||||||
- Check branch deletion
|
|
||||||
|
|
||||||
- [ ] **Failures block correctly**
|
|
||||||
- Create test PR with lint errors
|
|
||||||
- Verify PR is blocked
|
|
||||||
- Verify error messages are clear
|
|
||||||
|
|
||||||
- [ ] **Metrics tracked**
|
|
||||||
- Auto-merge rate
|
|
||||||
- Average time to merge
|
|
||||||
- Quality gate failure rate
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Issue: PRs not auto-merging
|
|
||||||
|
|
||||||
**Diagnosis:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check if pr-auto-merge step ran
|
|
||||||
woodpecker log show mosaic/stack <pipeline> | grep "pr-auto-merge"
|
|
||||||
|
|
||||||
# Check Gitea token permissions
|
|
||||||
curl -H "Authorization: token $GITEA_TOKEN" \
|
|
||||||
https://git.mosaicstack.dev/api/v1/user
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solutions:**
|
|
||||||
|
|
||||||
1. Verify `gitea_token` secret is configured
|
|
||||||
2. Check token has `repo` scope
|
|
||||||
3. Ensure PR targets `develop`
|
|
||||||
4. Verify all dependencies passed
|
|
||||||
|
|
||||||
### Issue: Quality gates failing unexpectedly
|
|
||||||
|
|
||||||
**Diagnosis:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run checks locally
|
|
||||||
pnpm lint
|
|
||||||
pnpm typecheck
|
|
||||||
pnpm test
|
|
||||||
|
|
||||||
# Compare with baseline
|
|
||||||
diff baseline-lint.txt <(pnpm lint 2>&1)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solutions:**
|
|
||||||
|
|
||||||
1. Fix actual code issues
|
|
||||||
2. Update baseline if needed
|
|
||||||
3. Temporarily make check non-blocking
|
|
||||||
4. Investigate CI environment differences
|
|
||||||
|
|
||||||
### Issue: Security scans too strict
|
|
||||||
|
|
||||||
**Diagnosis:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run security scan locally
|
|
||||||
pnpm audit --audit-level=high
|
|
||||||
|
|
||||||
# Check specific vulnerability
|
|
||||||
pnpm audit --json | jq '.vulnerabilities'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solutions:**
|
|
||||||
|
|
||||||
1. Update dependencies: `pnpm update`
|
|
||||||
2. Add audit exceptions if false positive
|
|
||||||
3. Lower severity threshold temporarily
|
|
||||||
4. Fix actual vulnerabilities
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
|
|
||||||
Migration is successful when:
|
|
||||||
|
|
||||||
- ✅ **100% of clean PRs auto-merge**
|
|
||||||
- No manual intervention needed
|
|
||||||
- Merge within 5 minutes of CI completion
|
|
||||||
|
|
||||||
- ✅ **Zero false-positive blocks**
|
|
||||||
- All blocked PRs have actual issues
|
|
||||||
- No spurious failures
|
|
||||||
|
|
||||||
- ✅ **Developer satisfaction high**
|
|
||||||
- Fast feedback loops
|
|
||||||
- Clear error messages
|
|
||||||
- Minimal friction
|
|
||||||
|
|
||||||
- ✅ **Quality maintained or improved**
|
|
||||||
- No increase in bugs reaching develop
|
|
||||||
- Test coverage ≥85%
|
|
||||||
- Security vulnerabilities caught early
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
After successful migration:
|
|
||||||
|
|
||||||
1. **Monitor and optimize**
|
|
||||||
- Track metrics weekly
|
|
||||||
- Identify bottlenecks
|
|
||||||
- Optimize slow steps
|
|
||||||
|
|
||||||
2. **Expand coverage**
|
|
||||||
- Add integration tests
|
|
||||||
- Add E2E tests
|
|
||||||
- Add performance tests
|
|
||||||
|
|
||||||
3. **Enhance security**
|
|
||||||
- Enable SAST fully
|
|
||||||
- Add license compliance
|
|
||||||
- Add container scanning
|
|
||||||
|
|
||||||
4. **Improve developer experience**
|
|
||||||
- Add pre-push hooks
|
|
||||||
- Create quality dashboard
|
|
||||||
- Automate changelog generation
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
- **Documentation:** [docs/AUTOMATED-PR-MERGE.md](AUTOMATED-PR-MERGE.md)
|
|
||||||
- **Issues:** https://git.mosaicstack.dev/mosaic/stack/issues
|
|
||||||
- **Team Chat:** #engineering on Mattermost
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Migration Owner:** Platform Team
|
|
||||||
**Last Updated:** 2026-02-03
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# Auto-Merge PR Script
|
|
||||||
#
|
|
||||||
# Automatically merges a PR to develop if all quality gates pass.
|
|
||||||
# This script can be called from Woodpecker CI or manually.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# auto-merge-pr.sh <pr_number>
|
|
||||||
#
|
|
||||||
# Environment variables:
|
|
||||||
# GITEA_TOKEN - Gitea API token (required)
|
|
||||||
# GITEA_URL - Gitea instance URL (default: https://git.mosaicstack.dev)
|
|
||||||
# REPO_OWNER - Repository owner (default: mosaic)
|
|
||||||
# REPO_NAME - Repository name (default: stack)
|
|
||||||
# TARGET_BRANCH - Target branch for auto-merge (default: develop)
|
|
||||||
# DRY_RUN - If set, only check eligibility without merging (default: false)
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
GITEA_URL="${GITEA_URL:-https://git.mosaicstack.dev}"
|
|
||||||
REPO_OWNER="${REPO_OWNER:-mosaic}"
|
|
||||||
REPO_NAME="${REPO_NAME:-stack}"
|
|
||||||
TARGET_BRANCH="${TARGET_BRANCH:-develop}"
|
|
||||||
DRY_RUN="${DRY_RUN:-false}"
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Functions
|
|
||||||
log_info() {
|
|
||||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_success() {
|
|
||||||
echo -e "${GREEN}✅ $1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_warning() {
|
|
||||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_error() {
|
|
||||||
echo -e "${RED}❌ $1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check requirements
|
|
||||||
if [ -z "${GITEA_TOKEN:-}" ]; then
|
|
||||||
log_error "GITEA_TOKEN environment variable is required"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $# -lt 1 ]; then
|
|
||||||
log_error "Usage: $0 <pr_number>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PR_NUMBER="$1"
|
|
||||||
API_BASE="$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME"
|
|
||||||
|
|
||||||
log_info "Checking PR #$PR_NUMBER for auto-merge eligibility..."
|
|
||||||
|
|
||||||
# Fetch PR details
|
|
||||||
PR_DATA=$(curl -sf -H "Authorization: token $GITEA_TOKEN" \
|
|
||||||
"$API_BASE/pulls/$PR_NUMBER" || {
|
|
||||||
log_error "Failed to fetch PR #$PR_NUMBER"
|
|
||||||
exit 1
|
|
||||||
})
|
|
||||||
|
|
||||||
# Extract PR information
|
|
||||||
PR_STATE=$(echo "$PR_DATA" | jq -r '.state // ""')
|
|
||||||
PR_MERGED=$(echo "$PR_DATA" | jq -r '.merged // false')
|
|
||||||
BASE_BRANCH=$(echo "$PR_DATA" | jq -r '.base.ref // ""')
|
|
||||||
HEAD_BRANCH=$(echo "$PR_DATA" | jq -r '.head.ref // ""')
|
|
||||||
IS_MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false')
|
|
||||||
HAS_CONFLICTS=$(echo "$PR_DATA" | jq -r '.mergeable_state // "unknown"')
|
|
||||||
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title // "Unknown"')
|
|
||||||
|
|
||||||
log_info "PR #$PR_NUMBER: $PR_TITLE"
|
|
||||||
log_info " State: $PR_STATE"
|
|
||||||
log_info " Base: $BASE_BRANCH ← Head: $HEAD_BRANCH"
|
|
||||||
log_info " Mergeable: $IS_MERGEABLE ($HAS_CONFLICTS)"
|
|
||||||
|
|
||||||
# Check if PR is already merged
|
|
||||||
if [ "$PR_MERGED" = "true" ]; then
|
|
||||||
log_success "PR is already merged"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if PR is open
|
|
||||||
if [ "$PR_STATE" != "open" ]; then
|
|
||||||
log_warning "PR is not open (state: $PR_STATE)"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if PR targets the correct branch
|
|
||||||
if [ "$BASE_BRANCH" != "$TARGET_BRANCH" ]; then
|
|
||||||
log_warning "PR does not target $TARGET_BRANCH (targets: $BASE_BRANCH)"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if PR is mergeable
|
|
||||||
if [ "$IS_MERGEABLE" != "true" ]; then
|
|
||||||
log_error "PR is not mergeable (state: $HAS_CONFLICTS)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Fetch CI status checks
|
|
||||||
log_info "Checking CI status..."
|
|
||||||
STATUS_DATA=$(curl -sf -H "Authorization: token $GITEA_TOKEN" \
|
|
||||||
"$API_BASE/statuses/$(echo "$PR_DATA" | jq -r '.head.sha')" || echo "[]")
|
|
||||||
|
|
||||||
# Count status check results
|
|
||||||
TOTAL_CHECKS=$(echo "$STATUS_DATA" | jq 'length')
|
|
||||||
SUCCESS_CHECKS=$(echo "$STATUS_DATA" | jq '[.[] | select(.state == "success")] | length')
|
|
||||||
PENDING_CHECKS=$(echo "$STATUS_DATA" | jq '[.[] | select(.state == "pending")] | length')
|
|
||||||
FAILURE_CHECKS=$(echo "$STATUS_DATA" | jq '[.[] | select(.state == "failure" or .state == "error")] | length')
|
|
||||||
|
|
||||||
log_info " Total checks: $TOTAL_CHECKS"
|
|
||||||
log_info " Success: $SUCCESS_CHECKS"
|
|
||||||
log_info " Pending: $PENDING_CHECKS"
|
|
||||||
log_info " Failed: $FAILURE_CHECKS"
|
|
||||||
|
|
||||||
# Check if there are any failures
|
|
||||||
if [ "$FAILURE_CHECKS" -gt 0 ]; then
|
|
||||||
log_error "PR has $FAILURE_CHECKS failed status checks"
|
|
||||||
echo "$STATUS_DATA" | jq -r '.[] | select(.state == "failure" or .state == "error") | " - \(.context): \(.description)"'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if there are pending checks
|
|
||||||
if [ "$PENDING_CHECKS" -gt 0 ]; then
|
|
||||||
log_warning "PR has $PENDING_CHECKS pending status checks - waiting..."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if all required checks passed
|
|
||||||
if [ "$TOTAL_CHECKS" -eq 0 ]; then
|
|
||||||
log_warning "No status checks found - proceeding with caution"
|
|
||||||
elif [ "$SUCCESS_CHECKS" -ne "$TOTAL_CHECKS" ]; then
|
|
||||||
log_warning "Not all status checks are successful ($SUCCESS_CHECKS/$TOTAL_CHECKS)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# All checks passed - ready to merge
|
|
||||||
log_success "All quality gates passed!"
|
|
||||||
|
|
||||||
if [ "$DRY_RUN" = "true" ]; then
|
|
||||||
log_info "DRY RUN: Would merge PR #$PR_NUMBER to $TARGET_BRANCH"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Perform the merge
|
|
||||||
log_info "Merging PR #$PR_NUMBER to $TARGET_BRANCH..."
|
|
||||||
MERGE_RESULT=$(curl -sf -X POST \
|
|
||||||
-H "Authorization: token $GITEA_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"Do": "merge",
|
|
||||||
"MergeMessageField": "",
|
|
||||||
"MergeTitleField": "",
|
|
||||||
"delete_branch_after_merge": true,
|
|
||||||
"force_merge": false,
|
|
||||||
"merge_when_checks_succeed": false
|
|
||||||
}' \
|
|
||||||
"$API_BASE/pulls/$PR_NUMBER/merge" || {
|
|
||||||
log_error "Failed to merge PR #$PR_NUMBER"
|
|
||||||
exit 1
|
|
||||||
})
|
|
||||||
|
|
||||||
# Check if merge was successful
|
|
||||||
if echo "$MERGE_RESULT" | jq -e '.merged' > /dev/null 2>&1; then
|
|
||||||
MERGE_SHA=$(echo "$MERGE_RESULT" | jq -r '.sha // "unknown"')
|
|
||||||
log_success "Successfully merged PR #$PR_NUMBER to $TARGET_BRANCH"
|
|
||||||
log_success " Merge commit: $MERGE_SHA"
|
|
||||||
log_success " Branch $HEAD_BRANCH deleted"
|
|
||||||
else
|
|
||||||
ERROR_MSG=$(echo "$MERGE_RESULT" | jq -r '.message // "Unknown error"')
|
|
||||||
log_error "Merge failed: $ERROR_MSG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
Reference in New Issue
Block a user