From fe3a2b36f7c4f6b0736404ec65629b025054e462 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Mon, 22 Sep 2025 21:22:11 +0200 Subject: [PATCH 1/3] DRY some bash functions for future commits --- scripts/common.sh | 24 ++++++++++++++++++++++++ scripts/retire.sh | 25 ++----------------------- scripts/sync.sh | 5 +++-- 3 files changed, 29 insertions(+), 25 deletions(-) create mode 100755 scripts/common.sh diff --git a/scripts/common.sh b/scripts/common.sh new file mode 100755 index 0000000..1cd2c42 --- /dev/null +++ b/scripts/common.sh @@ -0,0 +1,24 @@ +set -euo pipefail + +log() { + echo "$@" >&2 +} + +trace() { + log "Running:" "${@@Q}" + "$@" +} + +effect() { + log -en "\e[33m" + if [[ -z "${PROD:-}" ]]; then + log "Skipping effect:" "${@@Q}" + # If there's stdin, show it + if read -t 0 _; then + sed "s/^/[stdin] /" >&2 + fi + else + trace "$@" + fi + log -en "\e[0m" +} diff --git a/scripts/retire.sh b/scripts/retire.sh index 80bc192..e9523df 100755 --- a/scripts/retire.sh +++ b/scripts/retire.sh @@ -1,29 +1,8 @@ #!/usr/bin/env bash -set -euo pipefail -shopt -s nullglob -log() { - echo "$@" >&2 -} +source "$(dirname -- "${BASH_SOURCE[0]}")"/common.sh -trace() { - log "Running:" "${@@Q}" - "$@" -} - -effect() { - log -en "\e[33m" - if [[ -z "${PROD:-}" ]]; then - log "Skipping effect:" "${@@Q}" - # If there's stdin, show it - if read -t 0 _; then - sed "s/^/[stdin] /" >&2 - fi - else - trace "$@" - fi - log -en "\e[0m" -} +shopt -s nullglob usage() { log "Usage: $0 ORG ACTIVITY_REPO MEMBER_REPO DIR NOTICE_CUTOFF CLOSE_CUTOFF" diff --git a/scripts/sync.sh b/scripts/sync.sh index 0fa103b..3bab517 100755 --- a/scripts/sync.sh +++ b/scripts/sync.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash -set -euo pipefail + +source "$(dirname -- "${BASH_SOURCE[0]}")"/common.sh usage() { - echo >&2 "Usage: $0 ORG TEAM DIR" + log "Usage: $0 ORG TEAM DIR" exit 1 } From 52cc9787d618a2682d96ad61b3291c21bfadeb93 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Mon, 22 Sep 2025 21:23:24 +0200 Subject: [PATCH 2/3] Don't manually track markdown numbering It does it automatically when rendered --- scripts/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/README.md b/scripts/README.md index 8202968..2019903 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -76,7 +76,7 @@ The following sequence tests all code paths: ``` Check that no PR would be opened. -2. Run the script with the `empty` repo argument to simulate CI running with inactive users: +1. Run the script with the `empty` repo argument to simulate CI running with inactive users: ```bash scripts/retire.sh infinisil-test-org empty nixpkgs-committers members-test 'yesterday 1 month ago' now @@ -90,27 +90,27 @@ The following sequence tests all code paths: Check that it created the PR appropriately, including assigning the "retirement" label. You can undo this step by closing the PR. -3. Run it again to simulate CI running again later: +1. Run it again to simulate CI running again later: ```bash PROD=1 scripts/retire.sh infinisil-test-org empty nixpkgs-committers members-test 'yesterday 1 month ago' now ``` Check that no other PR is opened. -4. Run it again with `now` as the notice cutoff date to simulate the time interval passing: +1. Run it again with `now` as the notice cutoff date to simulate the time interval passing: ```bash PROD=1 scripts/retire.sh infinisil-test-org empty nixpkgs-committers members-test now now ``` Check that it undrafted the previous PR and posted an appropriate comment. -5. Run it again to simulate CI running again later: +1. Run it again to simulate CI running again later: ```bash PROD=1 scripts/retire.sh infinisil-test-org empty nixpkgs-committers members-test now now ``` Check that no other PR is opened. -6. Reset by marking the PR as a draft again, then run it again with the `active` repo argument to simulate activity during the time interval: +1. Reset by marking the PR as a draft again, then run it again with the `active` repo argument to simulate activity during the time interval: ```bash PROD=1 scripts/retire.sh infinisil-test-org active nixpkgs-committers members-test now now ``` Check that it gets undrafted with a comment listing the new activity. -8. Close the PR, then run the script again with no activity and for an earlier close cutoff, simulating that the retirement was delayed: +1. Close the PR, then run the script again with no activity and for an earlier close cutoff, simulating that the retirement was delayed: ```bash PROD=1 scripts/retire.sh infinisil-test-org empty nixpkgs-committers members-test now '1 day ago' ``` From 50673a7a43017ace7500abf4ce77c765312ef110 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Mon, 22 Sep 2025 21:24:38 +0200 Subject: [PATCH 3/3] Switch to PR workflow for nominations --- .github/workflows/nomination.yml | 36 +++++++++++++++++++++ README.md | 13 ++++++-- scripts/README.md | 36 ++++++++++++++++++++- scripts/nomination.sh | 54 ++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/nomination.yml create mode 100755 scripts/nomination.sh diff --git a/.github/workflows/nomination.yml b/.github/workflows/nomination.yml new file mode 100644 index 0000000..3400598 --- /dev/null +++ b/.github/workflows/nomination.yml @@ -0,0 +1,36 @@ +name: nomination + +on: + pull_request_target: + types: [edited, opened, synchronize, reopened] + +# We don't need to use the GitHub App for this workflow, +# because it's all localised to this repo +permissions: + issues: write + pull-requests: write + +jobs: + process: + name: Check + runs-on: ubuntu-latest + if: ${{ ! contains(github.event.pull_request.labels.*.name, 'nomination') && github.event.pull_request.head.ref != 'create-pull-request/sync' }} + steps: + - name: Fetch source + uses: actions/checkout@v4 + - name: Process nomination + run: | + set -o pipefail + gh api "repos/$REPOSITORY/pulls/$PR_NUMBER/files" \ + --jq '.[] | "\(.status) \(.filename)"' \ + | scripts/nomination.sh members "$REPOSITORY" "$PR_NUMBER" "$ANNOUNCEMENT_ISSUE_NUMBER" + env: + REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + ANNOUNCEMENT_ISSUE_NUMBER: "${{ + github.repository_owner == 'NixOS' && 35 || + github.repository_owner == 'infinisil-test-org' && 30 || + 'NO_ISSUE_NUMBER' + }}" + GH_TOKEN: ${{ github.token }} + PROD: "1" diff --git a/README.md b/README.md index 9b8aaf1..b434301 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,17 @@ whose members have write access to [Nixpkgs](https://github.com/nixos/nixpkgs). The [Nixpkgs commit delegators](https://github.com/orgs/NixOS/teams/commit-bit-delegation) maintain the member list in this repository. -While it's in principle possible to request Nixpkgs commit permissions by creating a PR, -please nominate yourself in [this issue](https://github.com/NixOS/nixpkgs/issues/321665) instead. + +## Nominations + +To nominate yourself or somebody else: +1. Check [open nominations](/../../issues?q=state%3Aopen%20label%3Anomination) to make sure the user hasn't been nominated already. +1. [Click this link](/../../new/main/members?filename=%3CGITHUB_HANDLE%3E) to create a new file in the [`members` directory](./members). +1. Leave the file contents empty and replace `` with the handle (without `@`) of the user you'd like to nominate . +1. Click on "Commit changes..." and follow the steps to create a PR. +1. State your motivation for the nomination in the PR description. + +Such nominations are also automatically announced in [this issue](/../../issues/35), which you can subscribe to for updates. ## Semi-automatic synchronisation diff --git a/scripts/README.md b/scripts/README.md index 2019903..c476b32 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -41,7 +41,41 @@ scripts/sync.sh infinisil-test-org actors members-test Check that it synchronises the files in the `members-test` directory with the team members of the `actors` team. -## `retire.sh` +## Testing `nomination.sh` + +This script does not depend on the current repository, but has some external effects. +For testing, we'll use [PR #33](https://github.com/infinisil-test-org/nixpkgs-committers/pull/33) and [issue #30](https://github.com/infinisil-test-org/nixpkgs-committers/issues/30). + +To test: +1. Delete all labels of the PR and reset the title: + ```bash + gh api --method DELETE /repos/infinisil-test-org/nixpkgs-committers/issues/33/labels + gh api --method PATCH /repos/infinisil-test-org/nixpkgs-committers/pulls/33 -f title="A non-conforming title" + ``` +1. Run the script while simulating that a non-nomination PR was opened: + ```bash + scripts/nomination.sh members infinisil-test-org/nixpkgs-committers 33 30 <<< "removed members/infinisil" + ``` + + Ensure that it exits with 0 and wouldn't run any effects. +1. Run the script while simulating that multiple users were nominated together: + ```bash + scripts/nomination.sh members infinisil-test-org/nixpkgs-committers 33 30 <<< "removed members/foo"$'\n'"added members/bar" + ``` + + Ensure that it exits with non-0 and wouldn't run any effects. +1. Run the script simulating a successful nomination + ```bash + scripts/nomination.sh members infinisil-test-org/nixpkgs-committers 33 30 <<< "added members/infinisil" + ``` + + Ensure that it exits with 0 and would run effects to label the PR, change the title and post a comment in the issue. +1. Rerun with effects + ```bash + PROD=1 scripts/nomination.sh members infinisil-test-org/nixpkgs-committers 33 30 <<< "added members/infinisil" + ``` + +## Testing `retire.sh` This script has external effects and as such needs a bit more care when testing. diff --git a/scripts/nomination.sh b/scripts/nomination.sh new file mode 100755 index 0000000..2c227d7 --- /dev/null +++ b/scripts/nomination.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +source "$(dirname -- "${BASH_SOURCE[0]}")"/common.sh + +shopt -s nocasematch + +usage() { + log "Usage: $0 MEMBERS_DIR REPOSITORY PR_NUMBER ANNOUNCEMENT_ISSUE_NUMBER" + exit 1 +} + +MEMBERS_DIR=${1:-$(usage)} +REPOSITORY=${2:-$(usage)} +PR_NUMBER=${3:-$(usage)} +ANNOUNCEMENT_ISSUE_NUMBER=${4:-$(usage)} + +log "Waiting to get changed files on stdin.." +readarray -t changedFiles +declare -p changedFiles + +regex="^added $MEMBERS_DIR/([^/]+)$" + +nomineeHandle= +for statusFilename in "${changedFiles[@]}"; do + if [[ "$statusFilename" =~ $regex ]]; then + nomineeHandle=${BASH_REMATCH[1]} + break + fi +done + +if [[ -z "$nomineeHandle" ]]; then + log "Not a nomination PR" + exit 0 +elif (( "${#changedFiles[@]}" > 1 )); then + log "Only one person can be nominated per PR" + exit 1 +fi + +effect gh api \ + --method PATCH \ + "/repos/$REPOSITORY/pulls/$PR_NUMBER" \ + -f title="Nominate @$nomineeHandle" \ + +effect gh api \ + --method POST \ + "/repos/$REPOSITORY/issues/$ANNOUNCEMENT_ISSUE_NUMBER/comments" \ + -F "body=@-" << EOF +The user @$nomineeHandle has been nominated. Endorsements and discussions should be held in the corresponding nomination PR: #$PR_NUMBER +EOF + +effect gh api \ + --method POST \ + "/repos/$REPOSITORY/issues/$PR_NUMBER/labels" \ + -f "labels[]=nomination"