From 02586e727ea4352eb1764097e0fc343ebf2d3210 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Wed, 26 Feb 2025 10:52:13 +0100 Subject: [PATCH] Introduce a CI to check milestones and branches --- .github/workflows/check-valid-milestone.yml | 100 ++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 .github/workflows/check-valid-milestone.yml diff --git a/.github/workflows/check-valid-milestone.yml b/.github/workflows/check-valid-milestone.yml new file mode 100644 index 000000000..0e9357050 --- /dev/null +++ b/.github/workflows/check-valid-milestone.yml @@ -0,0 +1,100 @@ +name: PR Milestone Check + +on: + pull_request: + types: [opened, reopened, edited, synchronize, milestoned, demilestoned] + branches: + - "main" + - "release-v*.*.*" + +jobs: + check-milestone: + name: Check PR Milestone + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Validate PR milestone + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + // Get PR number directly from the event payload + const prNumber = context.payload.pull_request.number; + + // Get PR details + const { data: prData } = await github.rest.pulls.get({ + owner: 'meilisearch', + repo: 'meilisearch', + pull_number: prNumber + }); + + // Get base branch name + const baseBranch = prData.base.ref; + console.log(`Base branch: ${baseBranch}`); + + // Get PR milestone + const prMilestone = prData.milestone; + if (!prMilestone) { + core.setFailed('PR must have a milestone assigned'); + return; + } + console.log(`PR milestone: ${prMilestone.title}`); + + // Validate milestone format: vx.y.z + const milestoneRegex = /^v\d+\.\d+\.\d+$/; + if (!milestoneRegex.test(prMilestone.title)) { + core.setFailed(`Milestone "${prMilestone.title}" does not follow the required format vx.y.z`); + return; + } + + // For main branch PRs, check if the milestone is the highest one + if (baseBranch === 'main') { + // Get all milestones + const { data: milestones } = await github.rest.issues.listMilestones({ + owner: 'meilisearch', + repo: 'meilisearch', + state: 'open', + sort: 'due_on', + direction: 'desc' + }); + + // Sort milestones by version number (vx.y.z) + const sortedMilestones = milestones + .filter(m => milestoneRegex.test(m.title)) + .sort((a, b) => { + const versionA = a.title.substring(1).split('.').map(Number); + const versionB = b.title.substring(1).split('.').map(Number); + + // Compare major version + if (versionA[0] !== versionB[0]) return versionB[0] - versionA[0]; + // Compare minor version + if (versionA[1] !== versionB[1]) return versionB[1] - versionA[1]; + // Compare patch version + return versionB[2] - versionA[2]; + }); + + if (sortedMilestones.length === 0) { + core.setFailed('No valid milestones found in the repository. Please create at least one milestone with the format vx.y.z'); + return; + } + + const highestMilestone = sortedMilestones[0]; + console.log(`Highest milestone: ${highestMilestone.title}`); + + if (prMilestone.title !== highestMilestone.title) { + core.setFailed(`PRs targeting the main branch must use the highest milestone (${highestMilestone.title}), but this PR uses ${prMilestone.title}`); + return; + } + } else { + // For release branches, the milestone should match the branch version + const branchVersion = baseBranch.substring(8); // remove 'release-' + if (prMilestone.title !== branchVersion) { + core.setFailed(`PRs targeting release branch "${baseBranch}" must use the matching milestone "${branchVersion}", but this PR uses "${prMilestone.title}"`); + return; + } + } + + console.log('PR milestone validation passed!');