Unverified Commit fa0ef8a6 authored by Wolfgang Walther's avatar Wolfgang Walther Committed by GitHub
Browse files

ci/github-script/commits: various fixes and improvements (#425789)

parents a9bb81c6 486756d8
Loading
Loading
Loading
Loading
+1 −77
Original line number Diff line number Diff line
@@ -55,7 +55,6 @@ jobs:

      - name: Check cherry-picks
        id: check
        continue-on-error: true
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          script: |
@@ -63,84 +62,9 @@ jobs:
              github,
              context,
              core,
              dry: context.eventName == 'pull_request',
            })

      - name: Request changes
        if: ${{ github.event_name == 'pull_request_target' && steps.check.outcome == 'failure' }}
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          script: |
            const { readFile } = require('node:fs/promises')
            const body = await readFile('review.md', 'utf-8')

            const pendingReview = (await github.paginate(github.rest.pulls.listReviews, {
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.payload.pull_request.number
            })).find(review =>
              review.user.login == 'github-actions[bot]' && (
                // If a review is still pending, we can just update this instead
                // of posting a new one.
                review.state == 'CHANGES_REQUESTED' ||
                // No need to post a new review, if an older one with the exact
                // same content had already been dismissed.
                review.body == body
              )
            )

            // Either of those two requests could fail for very long comments. This can only happen
            // with multiple commits all hitting the truncation limit for the diff. If you ever hit
            // this case, consider just splitting up those commits into multiple PRs.
            if (pendingReview) {
              await github.rest.pulls.updateReview({
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: context.payload.pull_request.number,
                review_id: pendingReview.id,
                body
              })
            } else {
              await github.rest.pulls.createReview({
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: context.payload.pull_request.number,
                event: 'REQUEST_CHANGES',
                body
              })
            }

      - name: Dismiss old reviews
        if: ${{ github.event_name == 'pull_request_target' && steps.check.outcome == 'success' }}
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          script: |
            await Promise.all(
              (await github.paginate(github.rest.pulls.listReviews, {
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: context.payload.pull_request.number
              })).filter(review =>
                review.user.login == 'github-actions[bot]'
              ).map(async (review) => {
                if (review.state == 'CHANGES_REQUESTED') {
                  await github.rest.pulls.dismissReview({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    pull_number: context.payload.pull_request.number,
                    review_id: review.id,
                    message: 'All cherry-picks are good now, thank you!'
                  })
                }
                await github.graphql(`mutation($node_id:ID!) {
                  minimizeComment(input: {
                    classifier: RESOLVED,
                    subjectId: $node_id
                  })
                  { clientMutationId }
                }`, { node_id: review.node_id })
              })
            )

      - name: Log current API rate limits
        env:
          GH_TOKEN: ${{ github.token }}
+7 −4
Original line number Diff line number Diff line
This report is automatically generated by the `check-cherry-picks` CI workflow.
This report is automatically generated by the `PR / Check / cherry-pick` CI workflow.

Some of the commits in this PR have not been cherry-picked exactly and require the author's and reviewer's attention.
Some of the commits in this PR require the author's and reviewer's attention.

Please make sure to follow the [backporting guidelines](https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md#how-to-backport-pull-requests) and cherry-pick with the `-x` flag. This requires changes to go to the unstable branches (`master` / `staging`) first, before backporting them.
Please follow the [backporting guidelines](https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md#how-to-backport-pull-requests) and cherry-pick with the `-x` flag.
This requires changes to the unstable `master` and `staging` branches first, before backporting them.

Occasionally, it is not possible to cherry-pick exactly the same patch. This most frequently happens when resolving merge conflicts while cherry-picking or when updating minor versions of packages which have already advanced to the next major on unstable. If you need to merge this PR despite the warnings, please [dismiss](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/dismissing-a-pull-request-review) this review.
Occasionally, it is not possible to cherry-pick exactly the same patch.
This most frequently happens when resolving merge conflicts or when updating minor versions of packages which have already advanced to the next major on unstable.
If you need to merge this PR despite the warnings, please [dismiss](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/dismissing-a-pull-request-review) this review shortly before merging.
+102 −15
Original line number Diff line number Diff line
module.exports = async function ({ github, context, core }) {
module.exports = async function ({ github, context, core, dry }) {
  const { execFileSync } = require('node:child_process')
  const { readFile, writeFile } = require('node:fs/promises')
  const { readFile } = require('node:fs/promises')
  const { join } = require('node:path')
  const { classify } = require('../supportedBranches.js')
  const withRateLimit = require('./withRateLimit.js')
@@ -8,16 +8,19 @@ module.exports = async function ({ github, context, core }) {
  await withRateLimit({ github, core }, async (stats) => {
    stats.prs = 1

    const pull_number = context.payload.pull_request.number

    const job_url =
      context.runId &&
      (
        await github.rest.actions.listJobsForWorkflowRun({
        await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
          ...context.repo,
          run_id: context.runId,
          per_page: 100,
        })
      ).data.jobs[0].html_url +
      ).find(({ name }) => name == 'Check / cherry-pick').html_url +
        '?pr=' +
        context.payload.pull_request.number
        pull_number

    async function handle({ sha, commit }) {
      // Using the last line with "cherry" + hash, because a chained backport
@@ -70,7 +73,9 @@ module.exports = async function ({ github, context, core }) {
        __dirname,
        'range-diff',
        '--no-color',
        '--ignore-all-space',
        '--no-notes',
        // 100 means "any change will be reported"; 0 means "no change will be reported"
        '--creation-factor=100',
        `${original_sha}~..${original_sha}`,
        `${sha}~..${sha}`,
@@ -113,12 +118,12 @@ module.exports = async function ({ github, context, core }) {

    const commits = await github.paginate(github.rest.pulls.listCommits, {
      ...context.repo,
      pull_number: context.payload.pull_request.number,
      pull_number,
    })

    const results = await Promise.all(commits.map(handle))

    // Log all results without truncation and with better highlighting to the job log.
    // Log all results without truncation, with better highlighting and all whitespace changes to the job log.
    results.forEach(({ sha, commit, severity, message, colored_diff }) => {
      core.startGroup(`Commit ${sha}`)
      core.info(`Author: ${commit.author.name} ${commit.author.email}`)
@@ -129,8 +134,46 @@ module.exports = async function ({ github, context, core }) {
    })

    // Only create step summary below in case of warnings or errors.
    if (results.every(({ severity }) => severity == 'info')) return
    else process.exitCode = 1
    // Also clean up older reviews, when all checks are good now.
    if (results.every(({ severity }) => severity == 'info')) {
      if (!dry) {
        await Promise.all(
          (
            await github.paginate(github.rest.pulls.listReviews, {
              ...context.repo,
              pull_number,
            })
          )
            .filter((review) => review.user.login == 'github-actions[bot]')
            .map(async (review) => {
              if (review.state == 'CHANGES_REQUESTED') {
                await github.rest.pulls.dismissReview({
                  ...context.repo,
                  pull_number,
                  review_id: review.id,
                  message: 'All cherry-picks are good now, thank you!',
                })
              }
              await github.graphql(
                `mutation($node_id:ID!) {
                  minimizeComment(input: {
                    classifier: RESOLVED,
                    subjectId: $node_id
                  })
                  { clientMutationId }
                }`,
                { node_id: review.node_id },
              )
            }),
        )
      }
      return
    }

    // In the case of "error" severity, we also fail the job.
    // Those should be considered blocking and not be dismissable via review.
    if (results.some(({ severity }) => severity == 'error'))
      process.exitCode = 1

    core.summary.addRaw(
      await readFile(join(__dirname, 'check-cherry-picks.md'), 'utf-8'),
@@ -177,9 +220,14 @@ module.exports = async function ({ github, context, core }) {
        }

        core.summary.addRaw('<details><summary>Show diff</summary>')
        core.summary.addRaw('\n\n```diff', true)
        core.summary.addRaw(truncated.join('\n'), true)
        core.summary.addRaw('```', true)
        core.summary.addCodeBlock(
          truncated
            .join('\n')
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;'),
          'diff',
        )
        core.summary.addRaw('</details>')
      }

@@ -191,9 +239,48 @@ module.exports = async function ({ github, context, core }) {
        `\n\n_Hint: The full diffs are also available in the [runner logs](${job_url}) with slightly better highlighting._`,
      )

    // Write to disk temporarily for next step in GHA.
    await writeFile('review.md', core.summary.stringify())

    const body = core.summary.stringify()
    core.summary.write()

    const pendingReview = (
      await github.paginate(github.rest.pulls.listReviews, {
        ...context.repo,
        pull_number,
      })
    ).find(
      (review) =>
        review.user.login == 'github-actions[bot]' &&
        // If a review is still pending, we can just update this instead
        // of posting a new one.
        (review.state == 'CHANGES_REQUESTED' ||
          // No need to post a new review, if an older one with the exact
          // same content had already been dismissed.
          review.body == body),
    )

    if (dry) {
      if (pendingReview)
        core.info('pending review found: ' + pendingReview.html_url)
      else core.info('no pending review found')
    } else {
      // Either of those two requests could fail for very long comments. This can only happen
      // with multiple commits all hitting the truncation limit for the diff. If you ever hit
      // this case, consider just splitting up those commits into multiple PRs.
      if (pendingReview) {
        await github.rest.pulls.updateReview({
          ...context.repo,
          pull_number,
          review_id: pendingReview.id,
          body,
        })
      } else {
        await github.rest.pulls.createReview({
          ...context.repo,
          pull_number,
          event: 'REQUEST_CHANGES',
          body,
        })
      }
    }
  })
}