Skip to content

Commit 28e03e5

Browse files
committed
Do the thing
Keep it very simple for now, some design choices: - Automation never makes changes to team members, it only opens PRs for humans to act upon. - Rather than a single file to track team members, use a directory with files for each team member, such that there are never merge conflicts.
0 parents  commit 28e03e5

6 files changed

Lines changed: 252 additions & 0 deletions

File tree

.github/workflows/retire.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: retire
2+
3+
on:
4+
schedule:
5+
# Every day at 17:07 (randomly chosen)
6+
- cron: '7 17 * * *'
7+
8+
# Allows manual trigger
9+
workflow_dispatch:
10+
11+
permissions: {}
12+
13+
jobs:
14+
retire:
15+
name: retire
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/create-github-app-token@v2
19+
id: app-token
20+
with:
21+
app-id: ${{ vars.APP_ID }}
22+
private-key: ${{ secrets.PRIVATE_KEY }}
23+
- name: Get GitHub App User Git String
24+
id: user
25+
run: |
26+
userId=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)
27+
name="${{ steps.app-token.outputs.app-slug }}[bot]"
28+
email="$userId+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com"
29+
git config --global user.name "$name"
30+
git config --global user.email "$email"
31+
env:
32+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
33+
- name: Fetch source
34+
uses: actions/checkout@v4
35+
with:
36+
token: ${{ steps.app-token.outputs.token }}
37+
- name: Run script
38+
run: scripts/sync.sh NixOS nixpkgs nixpkgs-committers members
39+
env:
40+
GH_TOKEN: ${{ steps.app-token.outputs.token }}

.github/workflows/sync.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: sync
2+
3+
on:
4+
schedule:
5+
# Every day at 14:12 (randomly chosen)
6+
- cron: '12 14 * * *'
7+
8+
# Allows manual trigger
9+
workflow_dispatch:
10+
11+
permissions: {}
12+
13+
jobs:
14+
sync:
15+
name: sync
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/create-github-app-token@v2
19+
id: app-token
20+
with:
21+
app-id: ${{ vars.APP_ID }}
22+
private-key: ${{ secrets.PRIVATE_KEY }}
23+
- name: Get GitHub App User Git String
24+
id: user
25+
run: |
26+
userId=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)
27+
name="${{ steps.app-token.outputs.app-slug }}[bot]"
28+
email="$userId+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com"
29+
git config --global user.name "$name"
30+
git config --global user.email "$email"
31+
echo "git-string=$name <$email>" >> "$GITHUB_OUTPUT"
32+
env:
33+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
34+
- name: Fetch source
35+
uses: actions/checkout@v4
36+
- name: Run script
37+
run: scripts/sync.sh NixOS nixpkgs-committers members
38+
env:
39+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
40+
- name: Create Pull Request
41+
uses: peter-evans/create-pull-request@v7
42+
with:
43+
token: ${{ steps.app-token.outputs.token }}
44+
author: ${{ steps.user.outputs.git-string }}
45+
committer: ${{ steps.user.outputs.git-string }}
46+
commit-message: Automated sync
47+
branch: create-pull-request/sync
48+
title: Automated sync
49+
body: This is an automated PR to sync the member list in this repository to match the GitHub team members. This PR can be merged without taking any further action.
50+
team-reviewers: commit-bit-delegation
51+

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Nix/Nixpkgs/NixOS
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Nixpkgs committers
2+
3+
This repository publicly tracks the [current members](./members) and [changes](../../commits/main/members)
4+
of the [Nixpkgs Committers](https://github.com/orgs/nixos/teams/nixpkgs-committers) team,
5+
whose members have write access to [Nixpkgs](https://github.com/nixos/nixpkgs).
6+
7+
The [Nixpkgs commit delegators](https://github.com/orgs/NixOS/teams/commit-bit-delegation)
8+
maintain the member list in this repository.
9+
While it's in principle possible to request Nixpkgs commit permissions by creating a PR,
10+
please nominate yourself in [this issue](https://github.com/NixOS/nixpkgs/issues/321665) instead.
11+
12+
## Semi-automatic synchronisation
13+
14+
Every day, [a GitHub Action workflow](./.github/workflows/sync.yml) runs
15+
to synchronise the members of the GitHub team with the member list in this repository.
16+
If they don't match, an automated PR is created,
17+
which should be merged by the Nixpkgs commit delegators to reconcile the mismatch.
18+
19+
## Semi-automatic retirement
20+
21+
Every day, [a GitHub Action workflow](./.github/workflows/retire.yml) runs
22+
to check if any Nixpkgs committers have not used their commit access within the last year,
23+
in which case an automated PR is created to remove them from the member list.
24+
This is according to [RFC 55](https://github.com/NixOS/rfcs/blob/master/rfcs/0055-retired-committers.md)
25+
and the SC-approved [amendment](https://github.com/NixOS/org/issues/91).
26+
27+
The PR will ping the user and inform them that it will by default be merged and implemented in one month.
28+
If the PR is still open one month later,
29+
an automated comment will be posted with the next steps for the Nixpkgs commit delegators.
30+
31+
## Automation setup
32+
33+
Automation depends on a GitHub App with the following permissions:
34+
- Organisation: Members read only (to be able to read the team members)
35+
- Repository: Pull requests read write, Contents read write (to be able to create PRs in this repository)
36+
37+
The GitHub App should only be installed on this repository.
38+
To give the workflows access to the GitHub App:
39+
- Configure the App ID as the repository _variable_ `APP_ID`
40+
- Configure the private key as the repository _secret_ `PRIVATE_KEY`

scripts/retire.sh

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
usage() {
5+
echo >&2 "Usage: $0 ORG ACTIVITY_REPO MEMBER_REPO DIR"
6+
exit 1
7+
}
8+
9+
ORG=${1:-$(usage)}
10+
ACTIVITY_REPO=${2:-$(usage)}
11+
MEMBER_REPO=${3:-$(usage)}
12+
DIR=${4:-$(usage)}
13+
14+
tmp=$(mktemp -d)
15+
16+
shopt -s nullglob
17+
18+
# One month plus a bit of leeway
19+
epochOneMonthAgo=$(( $(date --date='1 month ago' +%s) - 60 * 60 * 12 ))
20+
mainBranch=$(git branch --show-current)
21+
22+
mkdir -p "$DIR"
23+
cd "$DIR"
24+
for login in *; do
25+
gh api -X GET /repos/"$ORG"/"$ACTIVITY_REPO"/activity -f time_period=year -f actor="$login" -f per_page=100 \
26+
--jq ".[] | \"- \(.timestamp) [\(.activity_type) on \(.ref | ltrimstr(\"refs/heads/\"))](https://github.com/$ORG/$ACTIVITY_REPO/compare/\(.before)...\(.after))\"" \
27+
> "$tmp/$login"
28+
activityCount=$(wc -l <"$tmp/$login")
29+
30+
branchName=retire-$login
31+
prInfo=$(gh api -X GET /repos/"$ORG"/"$MEMBER_REPO"/pulls -f head="$ORG":"$branchName" --jq '.[0]')
32+
if [[ -n "$prInfo" ]]; then
33+
# If there is a PR already
34+
prNumber=$(jq .number <<< "$prInfo")
35+
epochCreatedAt=$(date --date="$(jq -r .created_at <<< "$prInfo")" +%s)
36+
if (( epochCreatedAt < epochOneMonthAgo )); then
37+
echo "$login has a retirement PR due, comment with a reminder to merge"
38+
{
39+
if (( activityCount > 0 )); then
40+
echo "One month has passed, @$login has been active again:"
41+
cat "$tmp/$login"
42+
echo ""
43+
echo "This PR may be merged and implemented by:"
44+
else
45+
echo "One month has passed, to this PR should now be merged and implemented by:"
46+
fi
47+
echo "- Adding @$login to the [Retired Nixpkgs Contributors team](https://github.com/orgs/NixOS/teams/retired-nixpkgs-contributors)"
48+
echo "- Removing @$login from the [Nixpkgs Committers team](https://github.com/orgs/NixOS/teams/nixpkgs-committers)"
49+
} | gh api --method POST /repos/"$ORG"/"$MEMBER_REPO"/issues/"$prNumber"/comments -F "body=@-" >/dev/null
50+
else
51+
echo "$login has a retirement PR pending"
52+
fi
53+
elif (( activityCount <= 0 )); then
54+
echo "$login has become inactive, opening a PR"
55+
# If there is no PR yet, but they have become inactive
56+
git switch -C "$branchName"
57+
git rm "$login"
58+
git commit -m "Automatic retirement of @$login"
59+
git push -f origin "$branchName"
60+
{
61+
echo "This is an automated PR to retire @$login as a Nixpkgs committers due to not using their commit access for 1 year."
62+
echo ""
63+
echo "Make a comment with your motivation to keep commit access, otherwise this PR will be merged and implemented in 1 month."
64+
} | gh api \
65+
--method POST \
66+
/repos/"$ORG"/"$MEMBER_REPO"/pulls \
67+
-f "title=Automatic retirement of @$login" \
68+
-F "body=@-" \
69+
-f "head=$ORG:$branchName" \
70+
-f "base=$mainBranch" >/dev/null
71+
git checkout "$mainBranch"
72+
else
73+
echo "$login is active with $activityCount activities"
74+
fi
75+
done

scripts/sync.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
usage() {
5+
echo >&2 "Usage: $0 ORG TEAM DIR"
6+
exit 1
7+
}
8+
9+
ORG=${1:-$(usage)}
10+
TEAM=${2:-$(usage)}
11+
DIR=${3:-$(usage)}
12+
13+
shopt -s nullglob
14+
15+
mkdir -p "$DIR"
16+
cd "$DIR"
17+
18+
for login in *; do
19+
rm "$login"
20+
done
21+
22+
gh api /orgs/"$ORG"/teams/"$TEAM"/members --paginate --jq '.[].login' |
23+
while read -r login; do
24+
touch "$login"
25+
done

0 commit comments

Comments
 (0)