vuls: init at 0.27.0
[NixPkgs.git] / ci / request-reviews / get-reviewers.sh
blob1107edd9e6f16d3ca56c26ef9592da7296b67fe4
1 #!/usr/bin/env bash
3 # Get the code owners of the files changed by a PR,
4 # suitable to be consumed by the API endpoint to request reviews:
5 # https://docs.github.com/en/rest/pulls/review-requests?apiVersion=2022-11-28#request-reviewers-for-a-pull-request
7 set -euo pipefail
9 log() {
10 echo "$@" >&2
13 if (( "$#" < 7 )); then
14 log "Usage: $0 GIT_REPO OWNERS_FILE BASE_REPO BASE_REF HEAD_REF PR_NUMBER PR_AUTHOR"
15 exit 1
18 gitRepo=$1
19 ownersFile=$2
20 baseRepo=$3
21 baseRef=$4
22 headRef=$5
23 prNumber=$6
24 prAuthor=$7
26 tmp=$(mktemp -d)
27 trap 'rm -rf "$tmp"' exit
29 git -C "$gitRepo" diff --name-only --merge-base "$baseRef" "$headRef" > "$tmp/touched-files"
30 readarray -t touchedFiles < "$tmp/touched-files"
31 log "This PR touches ${#touchedFiles[@]} files"
33 # Get the owners file from the base, because we don't want to allow PRs to
34 # remove code owners to avoid pinging them
35 git -C "$gitRepo" show "$baseRef":"$ownersFile" > "$tmp"/codeowners
37 # Associative array with the user as the key for easy de-duplication
38 # Make sure to always lowercase keys to avoid duplicates with different casings
39 declare -A users=()
41 for file in "${touchedFiles[@]}"; do
42 result=$(codeowners --file "$tmp"/codeowners "$file")
44 read -r file owners <<< "$result"
45 if [[ "$owners" == "(unowned)" ]]; then
46 log "File $file is unowned"
47 continue
49 log "File $file is owned by $owners"
51 # Split up multiple owners, separated by arbitrary amounts of spaces
52 IFS=" " read -r -a entries <<< "$owners"
54 for entry in "${entries[@]}"; do
55 # GitHub technically also supports Emails as code owners,
56 # but we can't easily support that, so let's not
57 if [[ ! "$entry" =~ @(.*) ]]; then
58 warn -e "\e[33mCodeowner \"$entry\" for file $file is not valid: Must start with \"@\"\e[0m" >&2
59 # Don't fail, because the PR for which this script runs can't fix it,
60 # it has to be fixed in the base branch
61 continue
63 # The first regex match is everything after the @
64 entry=${BASH_REMATCH[1]}
66 if [[ "$entry" =~ (.*)/(.*) ]]; then
67 # Teams look like $org/$team
68 org=${BASH_REMATCH[1]}
69 team=${BASH_REMATCH[2]}
71 # Instead of requesting a review from the team itself,
72 # we request reviews from the individual users.
73 # This is because once somebody from a team reviewed the PR,
74 # the API doesn't expose that the team was already requested for a review,
75 # so we wouldn't be able to avoid rerequesting reviews
76 # without saving some some extra state somewhere
78 # We could also consider implementing a more advanced heuristic
79 # in the future that e.g. only pings one team member,
80 # but escalates to somebody else if that member doesn't respond in time.
81 gh api \
82 --cache=1h \
83 -H "Accept: application/vnd.github+json" \
84 -H "X-GitHub-Api-Version: 2022-11-28" \
85 "/orgs/$org/teams/$team/members" \
86 --jq '.[].login' > "$tmp/team-members"
87 readarray -t members < "$tmp/team-members"
88 log "Team $entry has these members: ${members[*]}"
90 for user in "${members[@]}"; do
91 users[${user,,}]=
92 done
93 else
94 # Everything else is a user
95 users[${entry,,}]=
97 done
99 done
101 # Cannot request a review from the author
102 if [[ -v users[${prAuthor,,}] ]]; then
103 log "One or more files are owned by the PR author, ignoring"
104 unset 'users[${prAuthor,,}]'
107 gh api \
108 -H "Accept: application/vnd.github+json" \
109 -H "X-GitHub-Api-Version: 2022-11-28" \
110 "/repos/$baseRepo/pulls/$prNumber/reviews" \
111 --jq '.[].user.login' > "$tmp/already-reviewed-by"
113 # And we don't want to rerequest reviews from people who already reviewed
114 while read -r user; do
115 if [[ -v users[${user,,}] ]]; then
116 log "User $user is a code owner but has already left a review, ignoring"
117 unset 'users[${user,,}]'
119 done < "$tmp/already-reviewed-by"
121 # Turn it into a JSON for the GitHub API call to request PR reviewers
122 jq -n \
123 --arg users "${!users[*]}" \
125 reviewers: $users | split(" "),