···1-This report is automatically generated by the `check-cherry-picks` CI workflow.
23-Some of the commits in this PR have not been cherry-picked exactly and require the author's and reviewer's attention.
45-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.
067-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.
00
···1+This report is automatically generated by the `PR / Check / cherry-pick` CI workflow.
23+Some of the commits in this PR require the author's and reviewer's attention.
45+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.
6+This requires changes to the unstable `master` and `staging` branches first, before backporting them.
78+Occasionally, it is not possible to cherry-pick exactly the same patch.
9+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.
10+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
ci/github-script/commits.js
···1-module.exports = async function ({ github, context, core }) {
2 const { execFileSync } = require('node:child_process')
3- const { readFile, writeFile } = require('node:fs/promises')
4 const { join } = require('node:path')
5 const { classify } = require('../supportedBranches.js')
6 const withRateLimit = require('./withRateLimit.js')
···8 await withRateLimit({ github, core }, async (stats) => {
9 stats.prs = 1
100011 const job_url =
12 context.runId &&
13 (
14- await github.rest.actions.listJobsForWorkflowRun({
15 ...context.repo,
16 run_id: context.runId,
017 })
18- ).data.jobs[0].html_url +
19 '?pr=' +
20- context.payload.pull_request.number
2122 async function handle({ sha, commit }) {
23 // Using the last line with "cherry" + hash, because a chained backport
···70 __dirname,
71 'range-diff',
72 '--no-color',
073 '--no-notes',
074 '--creation-factor=100',
75 `${original_sha}~..${original_sha}`,
76 `${sha}~..${sha}`,
···113114 const commits = await github.paginate(github.rest.pulls.listCommits, {
115 ...context.repo,
116- pull_number: context.payload.pull_request.number,
117 })
118119 const results = await Promise.all(commits.map(handle))
120121- // Log all results without truncation and with better highlighting to the job log.
122 results.forEach(({ sha, commit, severity, message, colored_diff }) => {
123 core.startGroup(`Commit ${sha}`)
124 core.info(`Author: ${commit.author.name} ${commit.author.email}`)
···129 })
130131 // Only create step summary below in case of warnings or errors.
132- if (results.every(({ severity }) => severity == 'info')) return
133- else process.exitCode = 1
00000000000000000000000000000000000000134135 core.summary.addRaw(
136 await readFile(join(__dirname, 'check-cherry-picks.md'), 'utf-8'),
···177 }
178179 core.summary.addRaw('<details><summary>Show diff</summary>')
180- core.summary.addRaw('\n\n```diff', true)
181- core.summary.addRaw(truncated.join('\n'), true)
182- core.summary.addRaw('```', true)
00000183 core.summary.addRaw('</details>')
184 }
185···191 `\n\n_Hint: The full diffs are also available in the [runner logs](${job_url}) with slightly better highlighting._`,
192 )
193194- // Write to disk temporarily for next step in GHA.
195- await writeFile('review.md', core.summary.stringify())
196197- core.summary.write()
000000000000000000000000000000000000000198 })
199}
···1+module.exports = async function ({ github, context, core, dry }) {
2 const { execFileSync } = require('node:child_process')
3+ const { readFile } = require('node:fs/promises')
4 const { join } = require('node:path')
5 const { classify } = require('../supportedBranches.js')
6 const withRateLimit = require('./withRateLimit.js')
···8 await withRateLimit({ github, core }, async (stats) => {
9 stats.prs = 1
1011+ const pull_number = context.payload.pull_request.number
12+13 const job_url =
14 context.runId &&
15 (
16+ await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
17 ...context.repo,
18 run_id: context.runId,
19+ per_page: 100,
20 })
21+ ).find(({ name }) => name == 'Check / cherry-pick').html_url +
22 '?pr=' +
23+ pull_number
2425 async function handle({ sha, commit }) {
26 // Using the last line with "cherry" + hash, because a chained backport
···73 __dirname,
74 'range-diff',
75 '--no-color',
76+ '--ignore-all-space',
77 '--no-notes',
78+ // 100 means "any change will be reported"; 0 means "no change will be reported"
79 '--creation-factor=100',
80 `${original_sha}~..${original_sha}`,
81 `${sha}~..${sha}`,
···118119 const commits = await github.paginate(github.rest.pulls.listCommits, {
120 ...context.repo,
121+ pull_number,
122 })
123124 const results = await Promise.all(commits.map(handle))
125126+ // Log all results without truncation, with better highlighting and all whitespace changes to the job log.
127 results.forEach(({ sha, commit, severity, message, colored_diff }) => {
128 core.startGroup(`Commit ${sha}`)
129 core.info(`Author: ${commit.author.name} ${commit.author.email}`)
···134 })
135136 // Only create step summary below in case of warnings or errors.
137+ // Also clean up older reviews, when all checks are good now.
138+ if (results.every(({ severity }) => severity == 'info')) {
139+ if (!dry) {
140+ await Promise.all(
141+ (
142+ await github.paginate(github.rest.pulls.listReviews, {
143+ ...context.repo,
144+ pull_number,
145+ })
146+ )
147+ .filter((review) => review.user.login == 'github-actions[bot]')
148+ .map(async (review) => {
149+ if (review.state == 'CHANGES_REQUESTED') {
150+ await github.rest.pulls.dismissReview({
151+ ...context.repo,
152+ pull_number,
153+ review_id: review.id,
154+ message: 'All cherry-picks are good now, thank you!',
155+ })
156+ }
157+ await github.graphql(
158+ `mutation($node_id:ID!) {
159+ minimizeComment(input: {
160+ classifier: RESOLVED,
161+ subjectId: $node_id
162+ })
163+ { clientMutationId }
164+ }`,
165+ { node_id: review.node_id },
166+ )
167+ }),
168+ )
169+ }
170+ return
171+ }
172+173+ // In the case of "error" severity, we also fail the job.
174+ // Those should be considered blocking and not be dismissable via review.
175+ if (results.some(({ severity }) => severity == 'error'))
176+ process.exitCode = 1
177178 core.summary.addRaw(
179 await readFile(join(__dirname, 'check-cherry-picks.md'), 'utf-8'),
···220 }
221222 core.summary.addRaw('<details><summary>Show diff</summary>')
223+ core.summary.addCodeBlock(
224+ truncated
225+ .join('\n')
226+ .replace(/&/g, '&')
227+ .replace(/</g, '<')
228+ .replace(/>/g, '>'),
229+ 'diff',
230+ )
231 core.summary.addRaw('</details>')
232 }
233···239 `\n\n_Hint: The full diffs are also available in the [runner logs](${job_url}) with slightly better highlighting._`,
240 )
241242+ const body = core.summary.stringify()
243+ core.summary.write()
244245+ const pendingReview = (
246+ await github.paginate(github.rest.pulls.listReviews, {
247+ ...context.repo,
248+ pull_number,
249+ })
250+ ).find(
251+ (review) =>
252+ review.user.login == 'github-actions[bot]' &&
253+ // If a review is still pending, we can just update this instead
254+ // of posting a new one.
255+ (review.state == 'CHANGES_REQUESTED' ||
256+ // No need to post a new review, if an older one with the exact
257+ // same content had already been dismissed.
258+ review.body == body),
259+ )
260+261+ if (dry) {
262+ if (pendingReview)
263+ core.info('pending review found: ' + pendingReview.html_url)
264+ else core.info('no pending review found')
265+ } else {
266+ // Either of those two requests could fail for very long comments. This can only happen
267+ // with multiple commits all hitting the truncation limit for the diff. If you ever hit
268+ // this case, consider just splitting up those commits into multiple PRs.
269+ if (pendingReview) {
270+ await github.rest.pulls.updateReview({
271+ ...context.repo,
272+ pull_number,
273+ review_id: pendingReview.id,
274+ body,
275+ })
276+ } else {
277+ await github.rest.pulls.createReview({
278+ ...context.repo,
279+ pull_number,
280+ event: 'REQUEST_CHANGES',
281+ body,
282+ })
283+ }
284+ }
285 })
286}