name: Prevent Invalid Merges on: pull_request: types: [opened, reopened, synchronize, edited] jobs: validate-merge-flow: runs-on: ubuntu-latest steps: - name: Validate Branch Flow uses: actions/github-script@v7 with: script: | const base = context.payload.pull_request.base.ref; const head = context.payload.pull_request.head.ref; core.info(`Checking Merge Flow: ${head} -> ${base}`); // 1. Parse module name from base branch // Expected formats: dev_X, preprod_X, master_X const tiers = ["dev", "preprod", "master"]; function parseBranch(branchName) { for (const tier of tiers) { if (branchName.startsWith(tier + "_")) { return { tier: tier, module: branchName.substring(tier.length + 1) }; } } return null; // Not a standard environment branch (maybe feature/fix) } const baseInfo = parseBranch(base); const headInfo = parseBranch(head); // If base is not a protected tier (dev/preprod/master), allow merge (feature -> feature) if (!baseInfo) { core.info("Base branch is not a protected environment tier. Merge allowed."); return; } // Logic for Protected Base Branches // ❌ Rule: Cannot merge directly into master from anywhere except preprod (of same module) if (baseInfo.tier === "master") { if (!headInfo || headInfo.tier !== "preprod" || headInfo.module !== baseInfo.module) { core.setFailed(`❌ Forbidden: You can ONLY merge into 'master_${baseInfo.module}' from 'preprod_${baseInfo.module}'. Detected: ${head}`); return; } } // ❌ Rule: Cannot merge directly into preprod from anywhere except dev (of same module) if (baseInfo.tier === "preprod") { if (!headInfo || headInfo.tier !== "dev" || headInfo.module !== baseInfo.module) { core.setFailed(`❌ Forbidden: You can ONLY merge into 'preprod_${baseInfo.module}' from 'dev_${baseInfo.module}'. Detected: ${head}`); return; } } // ❌ Rule: Cannot merge directly into dev from master or preprod (reverse flow) // (Optional: You might allow hotfixes, but strictly strictly dev<-feature is best) if (baseInfo.tier === "dev") { // Allow feature branches to merge into dev // Block upstream branches if (headInfo && (headInfo.tier === "master" || headInfo.tier === "preprod")) { core.setFailed(`❌ Forbidden: Cannot merge upstream (${head}) back into dev.`); return; } } core.info("✅ Merge flow validation passed.");