|
|
@@ -0,0 +1,94 @@
|
|
|
+name: Milestone Sync
|
|
|
+on:
|
|
|
+ pull_request_target:
|
|
|
+ types:
|
|
|
+ - opened
|
|
|
+ - reopened
|
|
|
+ - ready_for_review
|
|
|
+ - edited
|
|
|
+ - milestoned
|
|
|
+ - closed
|
|
|
+ branches:
|
|
|
+ - develop
|
|
|
+
|
|
|
+permissions:
|
|
|
+ issues: write
|
|
|
+ pull-requests: write
|
|
|
+
|
|
|
+concurrency:
|
|
|
+ group: milestone-sync-${{ github.event.pull_request.number }}
|
|
|
+
|
|
|
+jobs:
|
|
|
+ milestone-sync:
|
|
|
+ if: github.repository_owner == 'GameServerManagers'
|
|
|
+ runs-on: ubuntu-latest
|
|
|
+ steps:
|
|
|
+ - name: Set PR milestone and update linked issues
|
|
|
+ uses: actions/github-script@v9
|
|
|
+ with:
|
|
|
+ script: |
|
|
|
+ const { owner, repo } = context.repo;
|
|
|
+ const pr = context.payload.pull_request;
|
|
|
+ const action = context.payload.action;
|
|
|
+ const fixedLabel = 'status: fixed in develop';
|
|
|
+
|
|
|
+ if (action === 'closed' && !pr.merged) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let milestone = pr.milestone;
|
|
|
+ if (!milestone && ['opened', 'reopened', 'ready_for_review'].includes(action)) {
|
|
|
+ const open = await github.paginate(github.rest.issues.listMilestones, { owner, repo, state: 'open', per_page: 100 });
|
|
|
+ open.sort((a, b) => {
|
|
|
+ if (a.due_on && b.due_on) return a.due_on.localeCompare(b.due_on);
|
|
|
+ if (a.due_on) return -1;
|
|
|
+ if (b.due_on) return 1;
|
|
|
+ return a.number - b.number;
|
|
|
+ });
|
|
|
+ if (open.length === 0) {
|
|
|
+ core.warning('No open milestone to assign.');
|
|
|
+ } else {
|
|
|
+ milestone = open[0];
|
|
|
+ await github.rest.issues.update({ owner, repo, issue_number: pr.number, milestone: milestone.number });
|
|
|
+ core.info(`Set PR #${pr.number} milestone to ${milestone.title}.`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const body = (pr.body || '').replace(/<!--[\s\S]*?-->/g, '');
|
|
|
+ const issueRef = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?|related to):?\s+(?:([\w.-]+)\/([\w.-]+))?#(\d+)/gi;
|
|
|
+ const issues = new Map();
|
|
|
+ for (const m of body.matchAll(issueRef)) {
|
|
|
+ if (m[2] && (m[2].toLowerCase() !== owner.toLowerCase() || m[3].toLowerCase() !== repo.toLowerCase())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ const number = Number(m[4]);
|
|
|
+ const closing = !/^related to$/i.test(m[1]);
|
|
|
+ issues.set(number, issues.get(number) || closing);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (const [number, closing] of issues) {
|
|
|
+ let issue;
|
|
|
+ try {
|
|
|
+ ({ data: issue } = await github.rest.issues.get({ owner, repo, issue_number: number }));
|
|
|
+ } catch (error) {
|
|
|
+ core.warning(`Could not read #${number}: ${error.message}`);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (issue.pull_request) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (milestone && issue.milestone?.number !== milestone.number) {
|
|
|
+ if (issue.milestone && !pr.merged) {
|
|
|
+ core.info(`#${number} is in ${issue.milestone.title}; it will move to ${milestone.title} when this PR merges.`);
|
|
|
+ } else {
|
|
|
+ await github.rest.issues.update({ owner, repo, issue_number: number, milestone: milestone.number });
|
|
|
+ core.info(`Set #${number} milestone to ${milestone.title}.`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (pr.merged && closing && issue.state === 'open' && !issue.labels.some((l) => (l.name || l) === fixedLabel)) {
|
|
|
+ await github.rest.issues.addLabels({ owner, repo, issue_number: number, labels: [fixedLabel] });
|
|
|
+ core.info(`Labelled #${number} "${fixedLabel}".`);
|
|
|
+ }
|
|
|
+ }
|