Merge release-26.05 into staging-next-26.05

This commit is contained in:
nixpkgs-ci[bot]
2026-09-27 04:04:46 +00:00
committed by GitHub
20 changed files with 214 additions and 153 deletions

View File

@@ -130,7 +130,7 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { default: checkCommitMessages } = await import('${{ github.workspace }}/trusted/ci/github-script/lint-commits.js')
const { default: checkCommitMessages } = await import('${{ github.workspace }}/trusted/ci/github-script/lint-commits.ts')
await checkCommitMessages({
github,

View File

@@ -38,8 +38,8 @@ jobs:
TARGET_SHA: ${{ inputs.targetSha }}
with:
script: |
const { classify } = await import('${{ github.workspace }}/ci/github-script/supportedBranches.js')
const { default: supportedSystems } = await import('${{ github.workspace }}/ci/github-script/supportedSystems.js')
const { classify } = await import('${{ github.workspace }}/ci/github-script/supportedBranches.ts')
const { default: supportedSystems } = await import('${{ github.workspace }}/ci/github-script/supportedSystems.ts')
const baseBranch = (
context.payload.merge_group?.base_ref ??

View File

@@ -64,8 +64,8 @@ jobs:
'.github/workflows/test.yml',
'ci/github-script/package.json',
'ci/github-script/package-lock.json',
'ci/github-script/supportedBranches.js',
'ci/github-script/supportedSystems.js',
'ci/github-script/supportedBranches.ts',
'ci/github-script/supportedSystems.ts',
'ci/pinned.json',
'pkgs/top-level/release-supported-systems.json',
].includes(file))) core.setOutput('merge-group', true)
@@ -82,8 +82,8 @@ jobs:
'ci/github-script/bot.js',
'ci/github-script/check-target-branch.ts',
'ci/github-script/commits.ts',
'ci/github-script/get-pr-commit-details.js',
'ci/github-script/lint-commits.js',
'ci/github-script/get-pr-commit-details.ts',
'ci/github-script/lint-commits.ts',
'ci/github-script/manual-file-edits.ts',
'ci/github-script/merge.js',
'ci/github-script/package.json',
@@ -91,9 +91,9 @@ jobs:
'ci/github-script/prepare.js',
'ci/github-script/reminders.ts',
'ci/github-script/reviewers.js',
'ci/github-script/reviews.js',
'ci/github-script/supportedBranches.js',
'ci/github-script/supportedSystems.js',
'ci/github-script/reviews.ts',
'ci/github-script/supportedBranches.ts',
'ci/github-script/supportedSystems.ts',
'ci/github-script/withRateLimit.js',
'ci/pinned.json',
'pkgs/top-level/release-supported-systems.json',

View File

@@ -104,7 +104,7 @@ For the purposes of CI, branches in the NixOS/nixpkgs repository are classified
Some branches also have a version component, which is either `unstable` or `YY.MM`.
`ci/github-script/supportedBranches.js` is a script imported by CI to classify the base and head branches of a Pull Request.
`ci/github-script/supportedBranches.ts` is a script imported by CI to classify the base and head branches of a Pull Request.
This classification will then be used to skip certain jobs.
This script can also be run locally to print basic test cases.

View File

@@ -4,7 +4,7 @@ import path from 'node:path'
import { DefaultArtifactClient } from '@actions/artifact'
import { handleMerge } from './merge.js'
import { handleReviewers } from './reviewers.js'
import { classify } from './supportedBranches.js'
import { classify } from './supportedBranches.ts'
import withRateLimit from './withRateLimit.js'
export default async ({ github, context, core, dry }) => {

View File

@@ -1,4 +1,4 @@
import { classify, split } from './supportedBranches.js'
import { classify, split } from './supportedBranches.ts'
type TargetBranchPolicyFacts = {
base: string

View File

@@ -6,8 +6,8 @@ import {
evaluateTargetBranchPolicy,
getTargetBranchPolicy,
} from './check-target-branch-policy.ts'
import { dismissReviews, postReview } from './reviews.js'
import { split } from './supportedBranches.js'
import { dismissReviews, postReview } from './reviews.ts'
import { split } from './supportedBranches.ts'
// TODO: should this be combined with the branch checks in prepare.js?
// They do seem quite similar, but this needs to run after eval,

View File

@@ -2,8 +2,8 @@ import { execFileSync } from 'node:child_process'
import type * as actionsCore from '@actions/core'
import type { context as actionsContext } from '@actions/github'
import type { GitHub } from '@actions/github/lib/utils'
import { dismissReviews, postReview } from './reviews.js'
import { classify } from './supportedBranches.js'
import { dismissReviews, postReview } from './reviews.ts'
import { classify } from './supportedBranches.ts'
import withRateLimit from './withRateLimit.js'
const dirname = import.meta.dirname

View File

@@ -3,26 +3,23 @@ import { promisify } from 'node:util'
const execFile = promisify(nodeExecFile)
/**
* @typedef {{
* subject: string,
* sha: string,
* author: { name: string, email: string },
* committer: { name: string, email: string}
* changedPaths: string[],
* changedPathSegments: Set<string>,
* }} Commit
*/
export type Commit = {
subject: string
sha: string
author: { name: string; email: string }
committer: { name: string; email: string }
changedPaths: string[]
changedPathSegments: Set<string>
}
/**
* @param {{
* args: string[]
* core: typeof import('@actions/core'),
* quiet?: boolean,
* repoPath?: string,
* }} RunGitProps
*/
async function runGit({ args, repoPath, core, quiet }) {
interface RunGitProps {
args: string[]
core: typeof import('@actions/core')
quiet?: boolean
repoPath?: string
}
async function runGit({ args, repoPath, core, quiet }: RunGitProps) {
if (repoPath) {
args = ['-C', repoPath, ...args]
}
@@ -34,21 +31,29 @@ async function runGit({ args, repoPath, core, quiet }) {
return await execFile('git', args)
}
interface GetCommitMessagesForPRProps {
core: typeof import('@actions/core')
pr: Awaited<
ReturnType<
InstanceType<
typeof import('@actions/github/lib/utils').GitHub
>['rest']['pulls']['get']
>
>['data']
repoPath?: string
}
/**
* Gets the SHA, subject and changed files for each commit in the given PR.
*
* Don't use GitHub API at all: the "list commits on PR" endpoint has a limit
* of 250 commits and doesn't return the changed files.
*
* @param {{
* core: typeof import('@actions/core'),
* pr: Awaited<ReturnType<InstanceType<typeof import('@actions/github/lib/utils').GitHub>["rest"]["pulls"]["get"]>>["data"]
* repoPath?: string,
* }} GetCommitMessagesForPRProps
*
* @returns {Promise<Commit[]>}
*/
export async function getCommitDetailsForPR({ core, pr, repoPath }) {
export async function getCommitDetailsForPR({
core,
pr,
repoPath,
}: GetCommitMessagesForPRProps): Promise<Commit[]> {
await runGit({
args: ['fetch', `--depth=1`, 'origin', pr.base.sha],
repoPath,

View File

@@ -1,17 +1,23 @@
import { getCommitDetailsForPR } from './get-pr-commit-details.js'
import { classify } from './supportedBranches.js'
import { type Commit, getCommitDetailsForPR } from './get-pr-commit-details.ts'
import { classify } from './supportedBranches.ts'
/** @typedef {import('./get-pr-commit-details.js').Commit} Commit */
type GitHub = InstanceType<typeof import('@actions/github/lib/utils').GitHub>
type Context = typeof import('@actions/github').context
type Core = typeof import('@actions/core')
/**
* @param {{
* github: InstanceType<typeof import('@actions/github/lib/utils').GitHub>,
* context: typeof import('@actions/github').context,
* core: typeof import('@actions/core'),
* repoPath?: string,
* }} LintCommitsProps
*/
export default async function lintCommits({ github, context, core, repoPath }) {
interface LintCommitsProps {
github: GitHub
context: Context
core: Core
repoPath?: string
}
export default async function lintCommits({
github,
context,
core,
repoPath,
}: LintCommitsProps) {
// This check should only be run when we have the pull_request context.
const pull_number = context.payload.pull_request?.number
if (!pull_number) {
@@ -53,13 +59,15 @@ export default async function lintCommits({ github, context, core, repoPath }) {
await checkCommitMetadata({ commits, core })
}
/**
* @param {{
* commits: Commit[],
* core: typeof import('@actions/core'),
* }} CheckCommitMessagesProps
*/
async function checkCommitMessages({ commits, core }) {
interface CheckCommitMessagesProps {
commits: Commit[]
core: Core
}
async function checkCommitMessages({
commits,
core,
}: CheckCommitMessagesProps) {
const failures = new Set()
const conventionalCommitTypes = [
@@ -80,10 +88,13 @@ async function checkCommitMessages({ commits, core }) {
]
/**
* @param {string[]} types e.g. ["fix", "feat"]
* @param {string?} sha commit hash
* @param types e.g. ["fix", "feat"]
* @param sha commit hash
*/
function makeConventionalCommitRegex(types, sha = null) {
function makeConventionalCommitRegex(
types: string[],
sha: string | null = null,
) {
core.info(
`${
sha
@@ -166,17 +177,15 @@ async function checkCommitMessages({ commits, core }) {
}
}
/**
* @param {{
* commits: Commit[],
* core: typeof import('@actions/core'),
* }} CheckGitFieldsProps
*/
async function checkCommitMetadata({ commits, core }) {
interface CheckGitFieldsProps {
commits: Commit[]
core: Core
}
async function checkCommitMetadata({ commits, core }: CheckGitFieldsProps) {
const failures = new Set()
/** @type {(s: string) => boolean} */
const isEmail = (s) => /^.+@.*$/.test(s)
const isEmail = (s: string) => /^.+@.*$/.test(s)
for (const commit of commits) {
if (!commit.author.name) {

View File

@@ -1,6 +1,6 @@
import { getCommitDetailsForPR } from './get-pr-commit-details.js'
import { dismissReviews, postReview } from './reviews.js'
import { classify } from './supportedBranches.js'
import { getCommitDetailsForPR } from './get-pr-commit-details.ts'
import { dismissReviews, postReview } from './reviews.ts'
import { classify } from './supportedBranches.ts'
export default async function checkManualFileEdits({
github,

View File

@@ -1,5 +1,5 @@
// @ts-nocheck
import { classify } from './supportedBranches.js'
import { classify } from './supportedBranches.ts'
function runChecklist({
committers,

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import { dismissReviews, postReview } from './reviews.js'
import { classify } from './supportedBranches.js'
import supportedSystems from './supportedSystems.js'
import { dismissReviews, postReview } from './reviews.ts'
import { classify } from './supportedBranches.ts'
import supportedSystems from './supportedSystems.ts'
const reviewKey = 'prepare'
@@ -66,7 +66,7 @@ export default async ({ github, context, core, dry }) => {
// commits between that base and head is the real base. We can query for this via GitHub's
// REST API. There can be multiple candidates for the real base with the same number of
// commits. In this case we pick the "best" candidate by a fixed ordering of branches,
// as defined in ci/github-script/supportedBranches.js.
// as defined in ci/github-script/supportedBranches.ts.
//
// These requests take a while, when comparing against the wrong release - they need
// to look at way more than 10k commits in that case. Thus, we try to minimize the

View File

@@ -3,9 +3,9 @@ import path from 'node:path'
import type * as actionsCore from '@actions/core'
import type { context as actionsContext } from '@actions/github'
import type { GitHub } from '@actions/github/lib/utils'
import { getCommitDetailsForPR } from './get-pr-commit-details.js'
import { dismissReviews, postReview } from './reviews.js'
import { classify } from './supportedBranches.js'
import { getCommitDetailsForPR } from './get-pr-commit-details.ts'
import { dismissReviews, postReview } from './reviews.ts'
import { classify } from './supportedBranches.ts'
/**
* Reminders to post as a non-blocking review when a pull request touches

View File

@@ -13,30 +13,28 @@ const reviewUsers = [
'manual-edit',
]
/**
* @typedef {InstanceType<typeof import('@actions/github/lib/utils').GitHub>} GitHub
* @typedef {typeof import('@actions/github').context} Context
*
* @typedef {Awaited<ReturnType<GitHub['rest']['pulls']['listReviews']>>['data'][number]} Review
* @typedef {Review & { user: NonNullable<Review['user']> }} ReviewWithNonNullUser
*/
type GitHub = InstanceType<typeof import('@actions/github/lib/utils').GitHub>
type Context = typeof import('@actions/github').context
type Review = Awaited<
ReturnType<GitHub['rest']['pulls']['listReviews']>
>['data'][number]
type ReviewWithNonNullUser = Review & { user: NonNullable<Review['user']> }
interface DismissReviewsProps {
github: GitHub
context: Context
core: typeof import('@actions/core')
dry: boolean
reviewKey?: string
}
/**
* @param {{
* github: GitHub,
* context: Context,
* core: typeof import('@actions/core'),
* dry: boolean,
* reviewKey?: string,
* }} DismissReviewsProps
*/
export async function dismissReviews({
github,
context,
core,
dry,
reviewKey,
}) {
}: DismissReviewsProps) {
const pull_number = context.payload.pull_request?.number
if (!pull_number) {
core.warning('dismissReviews called outside of pull_request context')
@@ -47,23 +45,29 @@ export async function dismissReviews({
return
}
const allReviews = await github.paginate(github.rest.pulls.listReviews, {
...context.repo,
pull_number,
})
const allReviews: Review[] = await github.paginate(
github.rest.pulls.listReviews,
{
...context.repo,
pull_number,
},
)
const reviews = /** @type {ReviewWithNonNullUser[]} */ (
allReviews.filter(
const reviews = allReviews
.filter((review): review is ReviewWithNonNullUser => !!review.user)
.filter(
(review) =>
review.user &&
review.state !== 'DISMISSED' &&
review.user.login.endsWith('[bot]') &&
reviewUsers.some((substr) => review.user?.login.includes(substr)),
)
)
const reviewsByUser = reviews.reduce(
(prev, curr) => {
if (!curr.user) {
return prev
}
if (!(curr.user.login in prev)) {
prev[curr.user.login] = []
}
@@ -72,7 +76,7 @@ export async function dismissReviews({
return prev
},
/** @type {Record<string, ReviewWithNonNullUser[]> } */ ({}),
{} as Record<string, ReviewWithNonNullUser[]>,
)
const commentRegex = new RegExp(
@@ -86,8 +90,8 @@ export async function dismissReviews({
)
let reviewsToMinimize = reviews
const /** @type {ReviewWithNonNullUser[]} */ reviewsToDismiss = []
const /** @type {ReviewWithNonNullUser[]} */ reviewsToResolve = []
const reviewsToDismiss: ReviewWithNonNullUser[] = []
const reviewsToResolve: ReviewWithNonNullUser[] = []
if (reviewKey && reviews.every((review) => commentRegex.test(review.body))) {
reviewsToMinimize = reviews.filter((review) =>
@@ -165,17 +169,16 @@ export async function dismissReviews({
])
}
/**
* @param {{
* github: GitHub,
* context: Context,
* core: typeof import('@actions/core'),
* dry: boolean,
* body: string,
* event: keyof typeof eventToState,
* reviewKey: string,
* }} PostReviewProps
*/
interface PostReviewProps {
github: GitHub
context: Context
core: typeof import('@actions/core')
dry: boolean
body: string
event: keyof typeof eventToState
reviewKey: string
}
export async function postReview({
github,
context,
@@ -184,7 +187,7 @@ export async function postReview({
body,
event = 'REQUEST_CHANGES',
reviewKey,
}) {
}: PostReviewProps) {
const pull_number = context.payload.pull_request?.number
if (!pull_number) {
core.warning('postReview called outside of pull_request context')
@@ -210,8 +213,7 @@ export async function postReview({
reviewUsers.some((substr) => review.user?.login.includes(substr)),
)
/** @type {null | Review} */
let pendingReview
let pendingReview: null | Review
const matchingReviews = reviews.filter((review) =>
reviewKeyRegex.test(review.body),
)

View File

@@ -101,7 +101,7 @@ program
.argument('<repo>', 'Name of the GitHub repository to run on (Example: nixpkgs)')
.argument('<pr>', 'Number of the Pull Request to run on')
.action(async (owner, repo, pr, options) => {
const checkCommitMessages = (await import('./lint-commits.js')).default
const checkCommitMessages = (await import('./lint-commits.ts')).default
await run(checkCommitMessages, owner, repo, pr, options)
})

View File

@@ -2,11 +2,12 @@
/*
#!nix-shell -i node -p nodejs
*/
// @ts-nocheck
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const typeConfig = {
type BranchType = 'channel' | 'development' | 'primary' | 'secondary'
const typeConfig: Record<string, BranchType[]> = {
master: ['development', 'primary'],
release: ['development', 'primary'],
staging: ['development', 'secondary'],
@@ -19,7 +20,7 @@ const typeConfig = {
// "order" ranks the development branches by how likely they are the intended base branch
// when they are an otherwise equally good fit according to ci/github-script/prepare.js.
const orderConfig = {
const orderConfig: Record<string, number> = {
master: 0,
release: 1,
staging: 2,
@@ -28,15 +29,30 @@ const orderConfig = {
'staging-next': 4,
}
function split(branch) {
return {
...branch.match(
/(?<prefix>.+?)(-(?<version>\d{2}\.\d{2}|unstable)(?:-(?<suffix>.*))?)?$/,
).groups,
}
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
type Version = `${Digit}${Digit}.${Digit}${Digit}` | 'unstable'
interface SplitResult {
prefix: string
version: Version
suffix?: string
}
function classify(branch) {
function split(branch: string) {
const groups = branch.match(
/(?<prefix>.+?)(-(?<version>\d{2}\.\d{2}|unstable)(?:-(?<suffix>.*))?)?$/,
)!.groups!
return groups as unknown as SplitResult
}
interface BranchClassification {
branch: string
order: number
stable: boolean
type: BranchType[]
version: Version
}
function classify(branch: string): BranchClassification {
const { prefix, version } = split(branch)
return {
branch,
@@ -55,7 +71,7 @@ if (
fileURLToPath(import.meta.url) === resolve(process.argv[1])
) {
console.log('split(branch)')
function testSplit(branch) {
function testSplit(branch: string) {
console.log(branch, split(branch))
}
testSplit('master')
@@ -72,7 +88,7 @@ if (
console.log('')
console.log('classify(branch)')
function testClassify(branch) {
function testClassify(branch: string) {
console.log(branch, classify(branch))
}
testClassify('master')

View File

@@ -1,11 +0,0 @@
// @ts-nocheck
export default async ({ github, context, targetSha }) => {
const { content, encoding } = (
await github.rest.repos.getContent({
...context.repo,
path: 'pkgs/top-level/release-supported-systems.json',
ref: targetSha,
})
).data
return JSON.parse(Buffer.from(content, encoding).toString())
}

View File

@@ -0,0 +1,30 @@
interface SupportedSystemsProps {
github: InstanceType<typeof import('@actions/github/lib/utils').GitHub>
context: typeof import('@actions/github').context
targetSha: string
}
export default async ({
github,
context,
targetSha,
}: SupportedSystemsProps) => {
const contentObject = (
await github.rest.repos.getContent({
...context.repo,
path: 'pkgs/top-level/release-supported-systems.json',
ref: targetSha,
})
).data
if ('type' in contentObject && contentObject.type === 'file') {
const { content, encoding } = contentObject
return JSON.parse(
Buffer.from(content, encoding as BufferEncoding).toString(),
)
} else {
throw new Error(
'Fetched pkgs/top-level/release-supported-systems.json is not a file',
)
}
}

View File

@@ -2,6 +2,7 @@
stdenv,
lib,
fetchFromGitHub,
fetchpatch,
glib,
gobject-introspection,
intltool,
@@ -46,6 +47,15 @@ stdenv.mkDerivation (finalAttrs: {
hash = "sha256-wycQmScxuSlo6Ln6piSBF7kmzvi6FnTm/ES/Ds+/h8I=";
};
patches = [
# ev-poppler.cc: Only read a link destination for GOTO_DEST
# Fixes CVE-2026-19772
(fetchpatch {
url = "https://github.com/linuxmint/xreader/commit/28ee72cc2779a3716b7d00da1aa87da992648d24.patch";
hash = "sha256-pd0kyfwsph+H9lii7CfndETsKTYSWyYw2tb9bMOHRX8=";
})
];
nativeBuildInputs = [
shared-mime-info
wrapGAppsHook3