···55555656 - name: Check cherry-picks
5757 id: check
5858- continue-on-error: true
5958 uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
6059 with:
6160 script: |
···6362 github,
6463 context,
6564 core,
6565+ dry: context.eventName == 'pull_request',
6666 })
6767-6868- - name: Request changes
6969- if: ${{ github.event_name == 'pull_request_target' && steps.check.outcome == 'failure' }}
7070- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
7171- with:
7272- script: |
7373- const { readFile } = require('node:fs/promises')
7474- const body = await readFile('review.md', 'utf-8')
7575-7676- const pendingReview = (await github.paginate(github.rest.pulls.listReviews, {
7777- owner: context.repo.owner,
7878- repo: context.repo.repo,
7979- pull_number: context.payload.pull_request.number
8080- })).find(review =>
8181- review.user.login == 'github-actions[bot]' && (
8282- // If a review is still pending, we can just update this instead
8383- // of posting a new one.
8484- review.state == 'CHANGES_REQUESTED' ||
8585- // No need to post a new review, if an older one with the exact
8686- // same content had already been dismissed.
8787- review.body == body
8888- )
8989- )
9090-9191- // Either of those two requests could fail for very long comments. This can only happen
9292- // with multiple commits all hitting the truncation limit for the diff. If you ever hit
9393- // this case, consider just splitting up those commits into multiple PRs.
9494- if (pendingReview) {
9595- await github.rest.pulls.updateReview({
9696- owner: context.repo.owner,
9797- repo: context.repo.repo,
9898- pull_number: context.payload.pull_request.number,
9999- review_id: pendingReview.id,
100100- body
101101- })
102102- } else {
103103- await github.rest.pulls.createReview({
104104- owner: context.repo.owner,
105105- repo: context.repo.repo,
106106- pull_number: context.payload.pull_request.number,
107107- event: 'REQUEST_CHANGES',
108108- body
109109- })
110110- }
111111-112112- - name: Dismiss old reviews
113113- if: ${{ github.event_name == 'pull_request_target' && steps.check.outcome == 'success' }}
114114- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
115115- with:
116116- script: |
117117- await Promise.all(
118118- (await github.paginate(github.rest.pulls.listReviews, {
119119- owner: context.repo.owner,
120120- repo: context.repo.repo,
121121- pull_number: context.payload.pull_request.number
122122- })).filter(review =>
123123- review.user.login == 'github-actions[bot]'
124124- ).map(async (review) => {
125125- if (review.state == 'CHANGES_REQUESTED') {
126126- await github.rest.pulls.dismissReview({
127127- owner: context.repo.owner,
128128- repo: context.repo.repo,
129129- pull_number: context.payload.pull_request.number,
130130- review_id: review.id,
131131- message: 'All cherry-picks are good now, thank you!'
132132- })
133133- }
134134- await github.graphql(`mutation($node_id:ID!) {
135135- minimizeComment(input: {
136136- classifier: RESOLVED,
137137- subjectId: $node_id
138138- })
139139- { clientMutationId }
140140- }`, { node_id: review.node_id })
141141- })
142142- )
1436714468 - name: Log current API rate limits
14569 env:
+7-4
ci/github-script/check-cherry-picks.md
···11-This report is automatically generated by the `check-cherry-picks` CI workflow.
11+This report is automatically generated by the `PR / Check / cherry-pick` CI workflow.
2233-Some of the commits in this PR have not been cherry-picked exactly and require the author's and reviewer's attention.
33+Some of the commits in this PR require the author's and reviewer's attention.
4455-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.
55+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.
66+This requires changes to the unstable `master` and `staging` branches first, before backporting them.
6777-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.
88+Occasionally, it is not possible to cherry-pick exactly the same patch.
99+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.
1010+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
···11-module.exports = async function ({ github, context, core }) {
11+module.exports = async function ({ github, context, core, dry }) {
22 const { execFileSync } = require('node:child_process')
33- const { readFile, writeFile } = require('node:fs/promises')
33+ const { readFile } = require('node:fs/promises')
44 const { join } = require('node:path')
55 const { classify } = require('../supportedBranches.js')
66 const withRateLimit = require('./withRateLimit.js')
···88 await withRateLimit({ github, core }, async (stats) => {
99 stats.prs = 1
10101111+ const pull_number = context.payload.pull_request.number
1212+1113 const job_url =
1214 context.runId &&
1315 (
1414- await github.rest.actions.listJobsForWorkflowRun({
1616+ await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
1517 ...context.repo,
1618 run_id: context.runId,
1919+ per_page: 100,
1720 })
1818- ).data.jobs[0].html_url +
2121+ ).find(({ name }) => name == 'Check / cherry-pick').html_url +
1922 '?pr=' +
2020- context.payload.pull_request.number
2323+ pull_number
21242225 async function handle({ sha, commit }) {
2326 // Using the last line with "cherry" + hash, because a chained backport
···7073 __dirname,
7174 'range-diff',
7275 '--no-color',
7676+ '--ignore-all-space',
7377 '--no-notes',
7878+ // 100 means "any change will be reported"; 0 means "no change will be reported"
7479 '--creation-factor=100',
7580 `${original_sha}~..${original_sha}`,
7681 `${sha}~..${sha}`,
···113118114119 const commits = await github.paginate(github.rest.pulls.listCommits, {
115120 ...context.repo,
116116- pull_number: context.payload.pull_request.number,
121121+ pull_number,
117122 })
118123119124 const results = await Promise.all(commits.map(handle))
120125121121- // Log all results without truncation and with better highlighting to the job log.
126126+ // Log all results without truncation, with better highlighting and all whitespace changes to the job log.
122127 results.forEach(({ sha, commit, severity, message, colored_diff }) => {
123128 core.startGroup(`Commit ${sha}`)
124129 core.info(`Author: ${commit.author.name} ${commit.author.email}`)
···129134 })
130135131136 // Only create step summary below in case of warnings or errors.
132132- if (results.every(({ severity }) => severity == 'info')) return
133133- else process.exitCode = 1
137137+ // Also clean up older reviews, when all checks are good now.
138138+ if (results.every(({ severity }) => severity == 'info')) {
139139+ if (!dry) {
140140+ await Promise.all(
141141+ (
142142+ await github.paginate(github.rest.pulls.listReviews, {
143143+ ...context.repo,
144144+ pull_number,
145145+ })
146146+ )
147147+ .filter((review) => review.user.login == 'github-actions[bot]')
148148+ .map(async (review) => {
149149+ if (review.state == 'CHANGES_REQUESTED') {
150150+ await github.rest.pulls.dismissReview({
151151+ ...context.repo,
152152+ pull_number,
153153+ review_id: review.id,
154154+ message: 'All cherry-picks are good now, thank you!',
155155+ })
156156+ }
157157+ await github.graphql(
158158+ `mutation($node_id:ID!) {
159159+ minimizeComment(input: {
160160+ classifier: RESOLVED,
161161+ subjectId: $node_id
162162+ })
163163+ { clientMutationId }
164164+ }`,
165165+ { node_id: review.node_id },
166166+ )
167167+ }),
168168+ )
169169+ }
170170+ return
171171+ }
172172+173173+ // In the case of "error" severity, we also fail the job.
174174+ // Those should be considered blocking and not be dismissable via review.
175175+ if (results.some(({ severity }) => severity == 'error'))
176176+ process.exitCode = 1
134177135178 core.summary.addRaw(
136179 await readFile(join(__dirname, 'check-cherry-picks.md'), 'utf-8'),
···177220 }
178221179222 core.summary.addRaw('<details><summary>Show diff</summary>')
180180- core.summary.addRaw('\n\n```diff', true)
181181- core.summary.addRaw(truncated.join('\n'), true)
182182- core.summary.addRaw('```', true)
223223+ core.summary.addCodeBlock(
224224+ truncated
225225+ .join('\n')
226226+ .replace(/&/g, '&')
227227+ .replace(/</g, '<')
228228+ .replace(/>/g, '>'),
229229+ 'diff',
230230+ )
183231 core.summary.addRaw('</details>')
184232 }
185233···191239 `\n\n_Hint: The full diffs are also available in the [runner logs](${job_url}) with slightly better highlighting._`,
192240 )
193241194194- // Write to disk temporarily for next step in GHA.
195195- await writeFile('review.md', core.summary.stringify())
242242+ const body = core.summary.stringify()
243243+ core.summary.write()
196244197197- core.summary.write()
245245+ const pendingReview = (
246246+ await github.paginate(github.rest.pulls.listReviews, {
247247+ ...context.repo,
248248+ pull_number,
249249+ })
250250+ ).find(
251251+ (review) =>
252252+ review.user.login == 'github-actions[bot]' &&
253253+ // If a review is still pending, we can just update this instead
254254+ // of posting a new one.
255255+ (review.state == 'CHANGES_REQUESTED' ||
256256+ // No need to post a new review, if an older one with the exact
257257+ // same content had already been dismissed.
258258+ review.body == body),
259259+ )
260260+261261+ if (dry) {
262262+ if (pendingReview)
263263+ core.info('pending review found: ' + pendingReview.html_url)
264264+ else core.info('no pending review found')
265265+ } else {
266266+ // Either of those two requests could fail for very long comments. This can only happen
267267+ // with multiple commits all hitting the truncation limit for the diff. If you ever hit
268268+ // this case, consider just splitting up those commits into multiple PRs.
269269+ if (pendingReview) {
270270+ await github.rest.pulls.updateReview({
271271+ ...context.repo,
272272+ pull_number,
273273+ review_id: pendingReview.id,
274274+ body,
275275+ })
276276+ } else {
277277+ await github.rest.pulls.createReview({
278278+ ...context.repo,
279279+ pull_number,
280280+ event: 'REQUEST_CHANGES',
281281+ body,
282282+ })
283283+ }
284284+ }
198285 })
199286}