pr-target-labeler.yml

  1name: PR Target Labeler
  2
  3# Decides where a PR is destined and reflects it with labels:
  4#   (no label)    -> master only (default)
  5#   backport/v1   -> merge to master AND backport to release/v1 ("both")
  6#   target/v1     -> the PR already targets release/v1 directly (v1 only)
  7#
  8# Maintainers steer routing with comment commands on the PR:
  9#   /backport v1      add the backport/v1 label
 10#   /no-backport v1   remove it
 11# The actual cherry-pick is performed by backport.yml once the PR is merged.
 12
 13on:
 14  pull_request_target:
 15    types: [opened, edited, reopened, synchronize]
 16  issue_comment:
 17    types: [created]
 18
 19permissions:
 20  contents: read
 21  pull-requests: write
 22  issues: write
 23
 24jobs:
 25  # Auto-label based on the PR's base branch and an explicit "backport" hint
 26  # in the title/body. Never removes backport/v1 (a maintainer may have set it).
 27  auto:
 28    if: github.event_name == 'pull_request_target'
 29    runs-on: ubuntu-latest
 30    steps:
 31      - uses: actions/github-script@v9
 32        with:
 33          github-token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
 34          script: |
 35            const pr = context.payload.pull_request;
 36            const { owner, repo } = context.repo;
 37            const issue_number = pr.number;
 38            const base = pr.base.ref;
 39            const have = new Set(pr.labels.map(l => l.name));
 40            const add = [];
 41
 42            if (base === 'release/v1') {
 43              if (!have.has('target/v1')) add.push('target/v1');
 44            } else {
 45              const text = `${pr.title}\n${pr.body || ''}`;
 46              if (/\bback[- ]?port\b/i.test(text) && !have.has('backport/v1')) {
 47                add.push('backport/v1');
 48              }
 49            }
 50
 51            if (add.length) {
 52              await github.rest.issues.addLabels({ owner, repo, issue_number, labels: add });
 53              console.log(`added: ${add.join(', ')}`);
 54            }
 55
 56  # Comment commands. Restricted to users with write access to the repo.
 57  command:
 58    if: >
 59      github.event_name == 'issue_comment'
 60      && github.event.issue.pull_request
 61      && (startsWith(github.event.comment.body, '/backport')
 62          || startsWith(github.event.comment.body, '/no-backport'))
 63    runs-on: ubuntu-latest
 64    steps:
 65      - uses: actions/github-script@v9
 66        with:
 67          github-token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
 68          script: |
 69            const { owner, repo } = context.repo;
 70            const issue_number = context.issue.number;
 71            const body = context.payload.comment.body.trim();
 72            const assoc = context.payload.comment.author_association;
 73            const commenter = context.payload.comment.user.login;
 74
 75            if (!['OWNER', 'MEMBER', 'COLLABORATOR'].includes(assoc)) {
 76              await github.rest.issues.createComment({
 77                owner, repo, issue_number,
 78                body: `Sorry @${commenter}, only maintainers can change backport routing.`,
 79              });
 80              return;
 81            }
 82
 83            // Only the v1 target is supported for now.
 84            if (!/\bv1\b/.test(body)) {
 85              await github.rest.issues.createComment({
 86                owner, repo, issue_number,
 87                body: `Usage: \`/backport v1\` or \`/no-backport v1\`.`,
 88              });
 89              return;
 90            }
 91
 92            const remove = body.startsWith('/no-backport');
 93            if (remove) {
 94              try {
 95                await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'backport/v1' });
 96              } catch (e) {
 97                if (e.status !== 404) throw e;
 98              }
 99            } else {
100              await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['backport/v1'] });
101            }
102
103            await github.rest.reactions.createForIssueComment({
104              owner, repo, comment_id: context.payload.comment.id, content: 'rocket',
105            });