Loading .github/workflows/reviewers.yml +8 −3 Original line number Diff line number Diff line Loading @@ -146,14 +146,19 @@ jobs: - name: Requesting maintainer reviews if: ${{ steps.app-token.outputs.token }} env: GH_TOKEN: ${{ steps.app-token.outputs.token }} GH_TOKEN: ${{ github.token }} APP_GH_TOKEN: ${{ steps.app-token.outputs.token }} REPOSITORY: ${{ github.repository }} ORG_ID: ${{ github.repository_owner_id }} NUMBER: ${{ github.event.number }} AUTHOR: ${{ github.event.pull_request.user.login }} # Don't request reviewers on draft PRs DRY_MODE: ${{ github.event.pull_request.draft && '1' || '' }} run: result/bin/request-maintainers-reviews.sh "$ORG_ID" "$REPOSITORY" "$NUMBER" "$AUTHOR" comparison run: | # maintainers.json contains GitHub IDs. Look up handles to request reviews from. # There appears to be no API to request reviews based on GitHub IDs jq -r 'keys[]' comparison/maintainers.json \ | while read -r id; do gh api /user/"$id" --jq .login; done \ | GH_TOKEN="$APP_GH_TOKEN" result/bin/request-reviewers.sh "$REPOSITORY" "$NUMBER" "$AUTHOR" - name: Log current API rate limits (app-token) if: ${{ steps.app-token.outputs.token }} Loading ci/eval/compare/default.nix +2 −7 Original line number Diff line number Diff line Loading @@ -179,12 +179,8 @@ runCommand "compare" jq cmp-stats ]; maintainers = builtins.toJSON maintainers.users; teams = builtins.toJSON maintainers.teams; passAsFile = [ "maintainers" "teams" ]; maintainers = builtins.toJSON maintainers; passAsFile = [ "maintainers" ]; } '' mkdir $out Loading Loading @@ -227,5 +223,4 @@ runCommand "compare" fi cp "$maintainersPath" "$out/maintainers.json" cp "$teamsPath" "$out/teams.json" '' ci/eval/compare/maintainers.nix +14 −38 Original line number Diff line number Diff line Loading @@ -51,16 +51,15 @@ let # updates to .json files. # TODO: Support by-name package sets. filenames = lib.optional (lib.length path == 1) "pkgs/by-name/${sharded (lib.head path)}/"; # meta.maintainers also contains all individual team members. # We only want to ping individuals if they're added individually as maintainers, not via teams. maintainers = package.meta.nonTeamMaintainers or [ ]; teams = package.meta.teams or [ ]; # TODO: Refactor this so we can ping entire teams instead of the individual members. # Note that this will require keeping track of GH team IDs in "maintainers/teams.nix". maintainers = package.meta.maintainers or [ ]; } )) # No need to match up packages without maintainers with their files. # This also filters out attributes where `packge = null`, which is the # case for libintl, for example. (lib.filter (pkg: pkg.maintainers != [ ] || pkg.teams != [ ])) (lib.filter (pkg: pkg.maintainers != [ ])) ]; relevantFilenames = Loading Loading @@ -95,43 +94,20 @@ let attrsWithModifiedFiles = lib.filter (pkg: anyMatchingFiles pkg.filenames) attrsWithFilenames; userPings = listToPing = lib.concatMap ( pkg: map (maintainer: { type = "user"; user = toString maintainer.githubId; id = maintainer.githubId; inherit (maintainer) github; packageName = pkg.name; }); teamPings = pkg: team: if team ? github then [ { type = "team"; team = toString team.githubId; packageName = pkg.name; } ] else userPings pkg team.members; maintainersToPing = lib.concatMap ( pkg: userPings pkg pkg.maintainers ++ lib.concatMap (teamPings pkg) pkg.teams dueToFiles = pkg.filenames; }) pkg.maintainers ) attrsWithModifiedFiles; byType = lib.groupBy (ping: ping.type) maintainersToPing; byMaintainer = lib.groupBy (ping: toString ping.id) listToPing; byUser = lib.pipe (byType.user or [ ]) [ (lib.groupBy (ping: ping.user)) (lib.mapAttrs (_user: lib.map (pkg: pkg.packageName))) ]; byTeam = lib.pipe (byType.team or [ ]) [ (lib.groupBy (ping: ping.team)) (lib.mapAttrs (_team: lib.map (pkg: pkg.packageName))) ]; packagesPerMaintainer = lib.mapAttrs ( maintainer: packages: map (pkg: pkg.packageName) packages ) byMaintainer; in { users = byUser; teams = byTeam; } packagesPerMaintainer ci/request-reviews/default.nix +0 −2 Original line number Diff line number Diff line Loading @@ -17,7 +17,6 @@ stdenvNoCC.mkDerivation { ./get-code-owners.sh ./request-reviewers.sh ./request-code-owner-reviews.sh ./request-maintainers-reviews.sh ]; }; nativeBuildInputs = [ makeWrapper ]; Loading @@ -27,7 +26,6 @@ stdenvNoCC.mkDerivation { for bin in *.sh; do mv "$bin" "$out/bin" wrapProgram "$out/bin/$bin" \ --set PAGER cat \ --set PATH ${ lib.makeBinPath [ coreutils Loading ci/request-reviews/get-code-owners.sh +35 −5 Original line number Diff line number Diff line #!/usr/bin/env bash # Get the code owners of the files changed by a PR, returning one GitHub user/team handle per line # Get the code owners of the files changed by a PR, returning one username per line set -euo pipefail Loading Loading @@ -29,9 +29,9 @@ log "This PR touches ${#touchedFiles[@]} files" # remove code owners to avoid pinging them git -C "$gitRepo" show "$baseRef":"$ownersFile" > "$tmp"/codeowners # Associative array with the user/team as the key for easy de-duplication # Associative array with the user as the key for easy de-duplication # Make sure to always lowercase keys to avoid duplicates with different casings declare -A finalOwners=() declare -A users=() for file in "${touchedFiles[@]}"; do result=$(codeowners --file "$tmp"/codeowners "$file") Loading Loading @@ -59,9 +59,39 @@ for file in "${touchedFiles[@]}"; do # The first regex match is everything after the @ entry=${BASH_REMATCH[1]} finalOwners[${entry,,}]= if [[ "$entry" =~ (.*)/(.*) ]]; then # Teams look like $org/$team org=${BASH_REMATCH[1]} team=${BASH_REMATCH[2]} # Instead of requesting a review from the team itself, # we request reviews from the individual users. # This is because once somebody from a team reviewed the PR, # the API doesn't expose that the team was already requested for a review, # so we wouldn't be able to avoid rerequesting reviews # without saving some some extra state somewhere # We could also consider implementing a more advanced heuristic # in the future that e.g. only pings one team member, # but escalates to somebody else if that member doesn't respond in time. gh api \ --cache=1h \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/orgs/$org/teams/$team/members" \ --jq '.[].login' > "$tmp/team-members" readarray -t members < "$tmp/team-members" log "Team $entry has these members: ${members[*]}" for user in "${members[@]}"; do users[${user,,}]= done else # Everything else is a user users[${entry,,}]= fi done done printf "%s\n" "${!finalOwners[@]}" printf "%s\n" "${!users[@]}" Loading
.github/workflows/reviewers.yml +8 −3 Original line number Diff line number Diff line Loading @@ -146,14 +146,19 @@ jobs: - name: Requesting maintainer reviews if: ${{ steps.app-token.outputs.token }} env: GH_TOKEN: ${{ steps.app-token.outputs.token }} GH_TOKEN: ${{ github.token }} APP_GH_TOKEN: ${{ steps.app-token.outputs.token }} REPOSITORY: ${{ github.repository }} ORG_ID: ${{ github.repository_owner_id }} NUMBER: ${{ github.event.number }} AUTHOR: ${{ github.event.pull_request.user.login }} # Don't request reviewers on draft PRs DRY_MODE: ${{ github.event.pull_request.draft && '1' || '' }} run: result/bin/request-maintainers-reviews.sh "$ORG_ID" "$REPOSITORY" "$NUMBER" "$AUTHOR" comparison run: | # maintainers.json contains GitHub IDs. Look up handles to request reviews from. # There appears to be no API to request reviews based on GitHub IDs jq -r 'keys[]' comparison/maintainers.json \ | while read -r id; do gh api /user/"$id" --jq .login; done \ | GH_TOKEN="$APP_GH_TOKEN" result/bin/request-reviewers.sh "$REPOSITORY" "$NUMBER" "$AUTHOR" - name: Log current API rate limits (app-token) if: ${{ steps.app-token.outputs.token }} Loading
ci/eval/compare/default.nix +2 −7 Original line number Diff line number Diff line Loading @@ -179,12 +179,8 @@ runCommand "compare" jq cmp-stats ]; maintainers = builtins.toJSON maintainers.users; teams = builtins.toJSON maintainers.teams; passAsFile = [ "maintainers" "teams" ]; maintainers = builtins.toJSON maintainers; passAsFile = [ "maintainers" ]; } '' mkdir $out Loading Loading @@ -227,5 +223,4 @@ runCommand "compare" fi cp "$maintainersPath" "$out/maintainers.json" cp "$teamsPath" "$out/teams.json" ''
ci/eval/compare/maintainers.nix +14 −38 Original line number Diff line number Diff line Loading @@ -51,16 +51,15 @@ let # updates to .json files. # TODO: Support by-name package sets. filenames = lib.optional (lib.length path == 1) "pkgs/by-name/${sharded (lib.head path)}/"; # meta.maintainers also contains all individual team members. # We only want to ping individuals if they're added individually as maintainers, not via teams. maintainers = package.meta.nonTeamMaintainers or [ ]; teams = package.meta.teams or [ ]; # TODO: Refactor this so we can ping entire teams instead of the individual members. # Note that this will require keeping track of GH team IDs in "maintainers/teams.nix". maintainers = package.meta.maintainers or [ ]; } )) # No need to match up packages without maintainers with their files. # This also filters out attributes where `packge = null`, which is the # case for libintl, for example. (lib.filter (pkg: pkg.maintainers != [ ] || pkg.teams != [ ])) (lib.filter (pkg: pkg.maintainers != [ ])) ]; relevantFilenames = Loading Loading @@ -95,43 +94,20 @@ let attrsWithModifiedFiles = lib.filter (pkg: anyMatchingFiles pkg.filenames) attrsWithFilenames; userPings = listToPing = lib.concatMap ( pkg: map (maintainer: { type = "user"; user = toString maintainer.githubId; id = maintainer.githubId; inherit (maintainer) github; packageName = pkg.name; }); teamPings = pkg: team: if team ? github then [ { type = "team"; team = toString team.githubId; packageName = pkg.name; } ] else userPings pkg team.members; maintainersToPing = lib.concatMap ( pkg: userPings pkg pkg.maintainers ++ lib.concatMap (teamPings pkg) pkg.teams dueToFiles = pkg.filenames; }) pkg.maintainers ) attrsWithModifiedFiles; byType = lib.groupBy (ping: ping.type) maintainersToPing; byMaintainer = lib.groupBy (ping: toString ping.id) listToPing; byUser = lib.pipe (byType.user or [ ]) [ (lib.groupBy (ping: ping.user)) (lib.mapAttrs (_user: lib.map (pkg: pkg.packageName))) ]; byTeam = lib.pipe (byType.team or [ ]) [ (lib.groupBy (ping: ping.team)) (lib.mapAttrs (_team: lib.map (pkg: pkg.packageName))) ]; packagesPerMaintainer = lib.mapAttrs ( maintainer: packages: map (pkg: pkg.packageName) packages ) byMaintainer; in { users = byUser; teams = byTeam; } packagesPerMaintainer
ci/request-reviews/default.nix +0 −2 Original line number Diff line number Diff line Loading @@ -17,7 +17,6 @@ stdenvNoCC.mkDerivation { ./get-code-owners.sh ./request-reviewers.sh ./request-code-owner-reviews.sh ./request-maintainers-reviews.sh ]; }; nativeBuildInputs = [ makeWrapper ]; Loading @@ -27,7 +26,6 @@ stdenvNoCC.mkDerivation { for bin in *.sh; do mv "$bin" "$out/bin" wrapProgram "$out/bin/$bin" \ --set PAGER cat \ --set PATH ${ lib.makeBinPath [ coreutils Loading
ci/request-reviews/get-code-owners.sh +35 −5 Original line number Diff line number Diff line #!/usr/bin/env bash # Get the code owners of the files changed by a PR, returning one GitHub user/team handle per line # Get the code owners of the files changed by a PR, returning one username per line set -euo pipefail Loading Loading @@ -29,9 +29,9 @@ log "This PR touches ${#touchedFiles[@]} files" # remove code owners to avoid pinging them git -C "$gitRepo" show "$baseRef":"$ownersFile" > "$tmp"/codeowners # Associative array with the user/team as the key for easy de-duplication # Associative array with the user as the key for easy de-duplication # Make sure to always lowercase keys to avoid duplicates with different casings declare -A finalOwners=() declare -A users=() for file in "${touchedFiles[@]}"; do result=$(codeowners --file "$tmp"/codeowners "$file") Loading Loading @@ -59,9 +59,39 @@ for file in "${touchedFiles[@]}"; do # The first regex match is everything after the @ entry=${BASH_REMATCH[1]} finalOwners[${entry,,}]= if [[ "$entry" =~ (.*)/(.*) ]]; then # Teams look like $org/$team org=${BASH_REMATCH[1]} team=${BASH_REMATCH[2]} # Instead of requesting a review from the team itself, # we request reviews from the individual users. # This is because once somebody from a team reviewed the PR, # the API doesn't expose that the team was already requested for a review, # so we wouldn't be able to avoid rerequesting reviews # without saving some some extra state somewhere # We could also consider implementing a more advanced heuristic # in the future that e.g. only pings one team member, # but escalates to somebody else if that member doesn't respond in time. gh api \ --cache=1h \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/orgs/$org/teams/$team/members" \ --jq '.[].login' > "$tmp/team-members" readarray -t members < "$tmp/team-members" log "Team $entry has these members: ${members[*]}" for user in "${members[@]}"; do users[${user,,}]= done else # Everything else is a user users[${entry,,}]= fi done done printf "%s\n" "${!finalOwners[@]}" printf "%s\n" "${!users[@]}"