diff --git a/.github/workflows/dismissed-review.yml b/.github/workflows/dismissed-review.yml new file mode 100644 index 0000000000000..256627af66167 --- /dev/null +++ b/.github/workflows/dismissed-review.yml @@ -0,0 +1,65 @@ +name: Dismissed review + +on: + workflow_run: + workflows: + - Review dismissed + types: [completed] + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + pull-requests: write + +defaults: + run: + shell: bash + +jobs: + # The `check-cherry-picks` workflow creates review comments which reviewers + # are encouraged to manually dismiss if they're not relevant. + # When a CI-generated review is dismissed, this job automatically minimizes + # it, preventing it from cluttering the PR. + minimize: + name: Minimize as resolved + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + // PRs from forks don't have any PRs associated by default. + // Thus, we request the PR number with an API call *to* the fork's repo. + // Multiple pull requests can be open from the same head commit, either via + // different base branches or head branches. + const { head_repository, head_sha, repository } = context.payload.workflow_run + await Promise.all( + (await github.paginate(github.rest.repos.listPullRequestsAssociatedWithCommit, { + owner: head_repository.owner.login, + repo: head_repository.name, + commit_sha: head_sha + })) + .filter(pull_request => pull_request.base.repo.id == repository.id) + .map(async (pull_request) => + Promise.all( + (await github.paginate(github.rest.pulls.listReviews, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pull_request.number + })).filter(review => + review.user.login == 'github-actions[bot]' && + review.state == 'DISMISSED' + ).map(review => github.graphql(` + mutation($node_id:ID!) { + minimizeComment(input: { + classifier: RESOLVED, + subjectId: $node_id + }) + { clientMutationId } + }`, + { node_id: review.node_id } + )) + ) + ) + ) diff --git a/.github/workflows/eval.yml b/.github/workflows/eval.yml index bb99c0dfbc68a..98535776a98d0 100644 --- a/.github/workflows/eval.yml +++ b/.github/workflows/eval.yml @@ -213,46 +213,6 @@ jobs: name: comparison path: comparison/* - - name: Labelling pull request - if: ${{ github.event_name == 'pull_request_target' }} - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - with: - script: | - const { readFile } = require('node:fs/promises') - - const pr = { - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.pull_request.number - } - - // Get all currently set labels that we manage - const before = - (await github.paginate(github.rest.issues.listLabelsOnIssue, pr)) - .map(({ name }) => name) - .filter(name => name.startsWith('10.rebuild') || name == '11.by: package-maintainer') - - // And the labels that should be there - const after = JSON.parse(await readFile('comparison/changed-paths.json', 'utf-8')).labels - - // Remove the ones not needed anymore - await Promise.all( - before.filter(name => !after.includes(name)) - .map(name => github.rest.issues.removeLabel({ - ...pr, - name - })) - ) - - // And add the ones that aren't set already - const added = after.filter(name => !before.includes(name)) - if (added.length > 0) { - await github.rest.issues.addLabels({ - ...pr, - labels: added - }) - } - - name: Add eval summary to commit statuses if: ${{ github.event_name == 'pull_request_target' }} uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 @@ -285,6 +245,16 @@ jobs: target_url }) + labels: + name: Labels + needs: [ compare ] + uses: ./.github/workflows/labels.yml + permissions: + issues: write + pull-requests: write + with: + caller: ${{ github.workflow }} + reviewers: name: Reviewers # No dependency on "compare", so that it can start at the same time. diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 5cdec70b62cea..6aa8ecd1b8221 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -6,14 +6,23 @@ name: "Label PR" on: - pull_request_target: + workflow_call: + inputs: + caller: + description: Name of the calling workflow. + required: true + type: string + workflow_run: + workflows: + - Review dismissed + - Review submitted + types: [completed] concurrency: - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.run_id }} + group: ${{ inputs.caller }}-${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true permissions: - contents: read issues: write # needed to create *new* labels pull-requests: write @@ -23,8 +32,113 @@ jobs: runs-on: ubuntu-24.04-arm if: "!contains(github.event.pull_request.title, '[skip treewide]')" steps: + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + id: eval + with: + script: | + const run_id = (await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'eval.yml', + event: 'pull_request_target', + head_sha: context.payload.pull_request?.head.sha ?? context.payload.workflow_run.head_sha + })).data.workflow_runs[0]?.id + core.setOutput('run-id', run_id) + + - name: Download the comparison results + if: steps.eval.outputs.run-id + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + run-id: ${{ steps.eval.outputs.run-id }} + github-token: ${{ github.token }} + pattern: comparison + path: comparison + merge-multiple: true + + - name: Labels from eval + if: steps.eval.outputs.run-id && github.event_name != 'pull_request' + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const { readFile } = require('node:fs/promises') + + let pull_requests + if (context.payload.workflow_run) { + // PRs from forks don't have any PRs associated by default. + // Thus, we request the PR number with an API call *to* the fork's repo. + // Multiple pull requests can be open from the same head commit, either via + // different base branches or head branches. + const { head_repository, head_sha, repository } = context.payload.workflow_run + pull_requests = (await github.paginate(github.rest.repos.listPullRequestsAssociatedWithCommit, { + owner: head_repository.owner.login, + repo: head_repository.name, + commit_sha: head_sha + })).filter(pull_request => pull_request.base.repo.id == repository.id) + } else { + pull_requests = [ context.payload.pull_request ] + } + + await Promise.all( + pull_requests.map(async (pull_request) => { + const pr = { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pull_request.number + } + + // Get all currently set labels that we manage + const before = + (await github.paginate(github.rest.issues.listLabelsOnIssue, pr)) + .map(({ name }) => name) + .filter(name => + name.startsWith('10.rebuild') || + name == '11.by: package-maintainer' || + name.startsWith('12.approvals:') || + name == '12.approved-by: package-maintainer' + ) + + const approvals = + (await github.paginate(github.rest.pulls.listReviews, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pull_request.number + })) + .filter(review => review.state == 'APPROVED') + .map(review => review.user.id) + + const maintainers = Object.keys( + JSON.parse(await readFile('comparison/maintainers.json', 'utf-8')) + ) + + // And the labels that should be there + const after = JSON.parse(await readFile('comparison/changed-paths.json', 'utf-8')).labels + if (approvals.length > 0) after.push(`12.approvals: ${approvals.length > 2 ? '3+' : approvals.length}`) + if (maintainers.some(id => approvals.includes(id))) after.push('12.approved-by: package-maintainer') + + // Remove the ones not needed anymore + await Promise.all( + before.filter(name => !after.includes(name)) + .map(name => github.rest.issues.removeLabel({ + ...pr, + name + })) + ) + + // And add the ones that aren't set already + const added = after.filter(name => !before.includes(name)) + if (added.length > 0) { + await github.rest.issues.addLabels({ + ...pr, + labels: added + }) + } + }) + ) + - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 + name: Labels from touched files if: | + github.event_name != 'workflow_run' && github.event.pull_request.head.repo.owner.login != 'NixOS' || !( github.head_ref == 'haskell-updates' || github.head_ref == 'python-updates' || @@ -35,8 +149,11 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} configuration-path: .github/labeler.yml # default sync-labels: true + - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 + name: Labels from touched files (no sync) if: | + github.event_name != 'workflow_run' && github.event.pull_request.head.repo.owner.login != 'NixOS' || !( github.head_ref == 'haskell-updates' || github.head_ref == 'python-updates' || @@ -47,11 +164,14 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} configuration-path: .github/labeler-no-sync.yml sync-labels: false + - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 + name: Labels from touched files (development branches) # Development branches like staging-next, haskell-updates and python-updates get special labels. # This is to avoid the mass of labels there, which is mostly useless - and really annoying for # the backport labels. if: | + github.event_name != 'workflow_run' && github.event.pull_request.head.repo.owner.login == 'NixOS' && ( github.head_ref == 'haskell-updates' || github.head_ref == 'python-updates' || diff --git a/.github/workflows/review-dismissed.yml b/.github/workflows/review-dismissed.yml new file mode 100644 index 0000000000000..988b4a47df14b --- /dev/null +++ b/.github/workflows/review-dismissed.yml @@ -0,0 +1,17 @@ +name: Review dismissed + +on: + pull_request_review: + types: [dismissed] + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + trigger: + runs-on: ubuntu-24.04-arm + steps: + - run: echo This is a no-op only used as a trigger for workflow_run. diff --git a/.github/workflows/review-submitted.yml b/.github/workflows/review-submitted.yml new file mode 100644 index 0000000000000..69663054f15b0 --- /dev/null +++ b/.github/workflows/review-submitted.yml @@ -0,0 +1,17 @@ +name: Review submitted + +on: + pull_request_review: + types: [submitted] + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + trigger: + runs-on: ubuntu-24.04-arm + steps: + - run: echo This is a no-op only used as a trigger for workflow_run.