···194 expectedHash: artifact.digest
195 })
196197- // Get all currently set labels that we manage
198- const before =
0199 (await github.paginate(github.rest.issues.listLabelsOnIssue, {
200 ...context.repo,
201 issue_number: pull_request.number
202 }))
203- .map(({ name }) => name)
204- .filter(name =>
205- name.startsWith('10.rebuild') ||
206- name == '11.by: package-maintainer' ||
207- name.startsWith('12.approvals:') ||
208- name == '12.approved-by: package-maintainer'
209- )
210211 const approvals = new Set(
212 (await github.paginate(github.rest.pulls.listReviews, {
···221 JSON.parse(await readFile(`${pull_request.number}/maintainers.json`, 'utf-8'))
222 ).map(m => Number.parseInt(m, 10)))
223224- // And the labels that should be there
225- const after = JSON.parse(await readFile(`${pull_request.number}/changed-paths.json`, 'utf-8')).labels
226- if (approvals.size > 0) after.push(`12.approvals: ${approvals.size > 2 ? '3+' : approvals.size}`)
227- if (Array.from(maintainers).some(m => approvals.has(m))) after.push('12.approved-by: package-maintainer')
228229- if (context.eventName == 'pull_request') {
230- core.info('Skipping labeling on a pull_request event (no privileges).')
231- return
232- }
233-234- // Remove the ones not needed anymore
235- await Promise.all(
236- before.filter(name => !after.includes(name))
237- .map(name => github.rest.issues.removeLabel({
238- ...context.repo,
239- issue_number: pull_request.number,
240- name
241- }))
00000242 )
243244- // And add the ones that aren't set already
245- const added = after.filter(name => !before.includes(name))
246- if (added.length > 0) {
247- await github.rest.issues.addLabels({
248- ...context.repo,
249- issue_number: pull_request.number,
250- labels: added
251- })
252- }
000000253 } catch (cause) {
254 throw new Error(`Labeling PR #${pull_request.number} failed.`, { cause })
255 }
···194 expectedHash: artifact.digest
195 })
196197+ // Create a map (Label -> Boolean) of all currently set labels.
198+ // Each label is set to True and can be disabled later.
199+ const before = Object.fromEntries(
200 (await github.paginate(github.rest.issues.listLabelsOnIssue, {
201 ...context.repo,
202 issue_number: pull_request.number
203 }))
204+ .map(({ name }) => [name, true])
205+ )
00000206207 const approvals = new Set(
208 (await github.paginate(github.rest.pulls.listReviews, {
···217 JSON.parse(await readFile(`${pull_request.number}/maintainers.json`, 'utf-8'))
218 ).map(m => Number.parseInt(m, 10)))
219220+ const evalLabels = JSON.parse(await readFile(`${pull_request.number}/changed-paths.json`, 'utf-8')).labels
000221222+ // Manage the labels
223+ const after = Object.assign(
224+ {},
225+ before,
226+ // Ignore `evalLabels` if it's an array.
227+ // This can happen for older eval runs, before we switched to objects.
228+ // The old eval labels would have been set by the eval run,
229+ // so now they'll be present in `before`.
230+ // TODO: Simplify once old eval results have expired (~2025-10)
231+ (Array.isArray(evalLabels) ? undefined : evalLabels),
232+ {
233+ '12.approvals: 1': approvals.size == 1,
234+ '12.approvals: 2': approvals.size == 2,
235+ '12.approvals: 3+': approvals.size >= 3,
236+ '12.approved-by: package-maintainer': Array.from(maintainers).some(m => approvals.has(m)),
237+ '12.first-time contribution':
238+ [ 'NONE', 'FIRST_TIMER', 'FIRST_TIME_CONTRIBUTOR' ].includes(pull_request.author_association),
239+ }
240 )
241242+ // No need for an API request, if all labels are the same.
243+ const hasChanges = Object.keys(after).some(name => (before[name] ?? false) != after[name])
244+ if (log('Has changes', hasChanges, !hasChanges))
245+ return;
246+247+ // Skipping labeling on a pull_request event, because we have no privileges.
248+ const labels = Object.entries(after).filter(([,value]) => value).map(([name]) => name)
249+ if (log('Set labels', labels, context.eventName == 'pull_request'))
250+ return;
251+252+ await github.rest.issues.setLabels({
253+ ...context.repo,
254+ issue_number: pull_request.number,
255+ labels
256+ })
257 } catch (cause) {
258 throw new Error(`Labeling PR #${pull_request.number} failed.`, { cause })
259 }
···521>
522> See [Versioning](#versioning) for details on package versioning.
52300524### Fetching patches
525526In the interest of keeping our maintenance burden and the size of Nixpkgs to a minimum, patches already merged upstream or published elsewhere _should_ be retrieved using `fetchpatch2`:
···521>
522> See [Versioning](#versioning) for details on package versioning.
523524+The following describes two ways to include the patch. Regardless of how the patch is included, you _must_ ensure its purpose is clear and obvious. This enables other maintainers to more easily determine when old patches are no longer required. Typically, you can improve clarity with carefully considered filenames, attribute names, and/or comments; these should explain the patch's _intention_. Additionally, it may sometimes be helpful to clarify _how_ it resolves the issue. For example: _"fix gcc14 build by adding missing include"_.
525+526### Fetching patches
527528In the interest of keeping our maintenance burden and the size of Nixpkgs to a minimum, patches already merged upstream or published elsewhere _should_ be retrieved using `fetchpatch2`:
···5import sys
6from pathlib import Path
7from subprocess import CalledProcessError, run
8-from textwrap import dedent
9from typing import Final, assert_never
1011from . import nix, tmpdir
···338 )
339340341-def validate_nixos_config(path_to_config: Path) -> None:
342- if not (path_to_config / "nixos-version").exists() and not os.environ.get(
343- "NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM"
344- ):
345- msg = dedent(
346- # the lowercase for the first letter below is proposital
347- f"""
348- your NixOS configuration path seems to be missing essential files.
349- To avoid corrupting your current NixOS installation, the activation will abort.
350-351- This could be caused by Nix bug: https://github.com/NixOS/nix/issues/13367.
352- This is the evaluated NixOS configuration path: {path_to_config}.
353- Change the directory to somewhere else (e.g., `cd $HOME`) before trying again.
354-355- If you think this is a mistake, you can set the environment variable
356- NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM to 1
357- and re-run the command to continue.
358- Please open an issue if this is the case.
359- """
360- ).strip()
361- raise NixOSRebuildError(msg)
362-363-364def execute(argv: list[str]) -> None:
365 args, args_groups = parse_args(argv)
366···514 copy_flags=copy_flags,
515 )
516 if action in (Action.SWITCH, Action.BOOT):
517- validate_nixos_config(path_to_config)
518 nix.set_profile(
519 profile,
520 path_to_config,
···9from pathlib import Path
10from string import Template
11from subprocess import PIPE, CalledProcessError
012from typing import Final, Literal
1314from . import tmpdir
···613 sudo: bool,
614) -> None:
615 "Set a path as the current active Nix profile."
000000000000000000000000000616 run_wrapper(
617 ["nix-env", "-p", profile.path, "--set", path_to_config],
618 remote=target_host,
···9from pathlib import Path
10from string import Template
11from subprocess import PIPE, CalledProcessError
12+from textwrap import dedent
13from typing import Final, Literal
1415from . import tmpdir
···614 sudo: bool,
615) -> None:
616 "Set a path as the current active Nix profile."
617+ if not os.environ.get(
618+ "NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM"
619+ ):
620+ r = run_wrapper(
621+ ["test", "-f", path_to_config / "nixos-version"],
622+ remote=target_host,
623+ check=False,
624+ )
625+ if r.returncode:
626+ msg = dedent(
627+ # the lowercase for the first letter below is proposital
628+ f"""
629+ your NixOS configuration path seems to be missing essential files.
630+ To avoid corrupting your current NixOS installation, the activation will abort.
631+632+ This could be caused by Nix bug: https://github.com/NixOS/nix/issues/13367.
633+ This is the evaluated NixOS configuration path: {path_to_config}.
634+ Change the directory to somewhere else (e.g., `cd $HOME`) before trying again.
635+636+ If you think this is a mistake, you can set the environment variable
637+ NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM to 1
638+ and re-run the command to continue.
639+ Please open an issue if this is the case.
640+ """
641+ ).strip()
642+ raise NixOSRebuildError(msg)
643+644 run_wrapper(
645 ["nix-env", "-p", profile.path, "--set", path_to_config],
646 remote=target_host,
···1562 ''; # Added 2025-03-07
1563 poretools = throw "poretools has been removed from nixpkgs, as it was broken and unmaintained"; # Added 2024-06-03
1564 powerdns = pdns; # Added 2022-03-28
01565 projectm = throw "Since version 4, 'projectm' has been split into 'libprojectm' (the library) and 'projectm-sdl-cpp' (the SDL2 frontend). ProjectM 3 has been moved to 'projectm_3'"; # Added 2024-11-10
15661567 cstore_fdw = postgresqlPackages.cstore_fdw;
···1562 ''; # Added 2025-03-07
1563 poretools = throw "poretools has been removed from nixpkgs, as it was broken and unmaintained"; # Added 2024-06-03
1564 powerdns = pdns; # Added 2022-03-28
1565+ presage = throw "presage has been removed, as it has been unmaintained since 2018"; # Added 2024-03-24
1566 projectm = throw "Since version 4, 'projectm' has been split into 'libprojectm' (the library) and 'projectm-sdl-cpp' (the SDL2 frontend). ProjectM 3 has been moved to 'projectm_3'"; # Added 2024-11-10
15671568 cstore_fdw = postgresqlPackages.cstore_fdw;