nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at release-25.11 188 lines 6.9 kB view raw
1async function handleReviewers({ 2 github, 3 context, 4 core, 5 log, 6 dry, 7 pull_request, 8 reviews, 9 user_maintainers, 10 team_maintainers, 11 owners, 12 getUser, 13 getTeam, 14}) { 15 const pull_number = pull_request.number 16 17 // Users that the PR has already reached, e.g. they've left a review or have been requested for one 18 const users_reached = new Set([ 19 ...pull_request.requested_reviewers.map(({ login }) => login.toLowerCase()), 20 ...reviews.map(({ user }) => user.login.toLowerCase()), 21 ]) 22 log('reviewers - users_reached', Array.from(users_reached).join(', ')) 23 24 // Same for teams 25 const teams_reached = new Set([ 26 ...pull_request.requested_teams.map(({ slug }) => slug.toLowerCase()), 27 ...reviews.flatMap(({ onBehalfOf }) => 28 onBehalfOf.nodes.map(({ slug }) => slug.toLowerCase()), 29 ), 30 ]) 31 log('reviewers - teams_reached', Array.from(teams_reached).join(', ')) 32 33 // Early sanity check, before we start making any API requests. The list of maintainers 34 // does not have duplicates so the only user to filter out from this list would be the 35 // PR author. Therefore, we check for a limit of 15+1, where 15 is the limit we check 36 // further down again. 37 // This is to protect against huge treewides consuming all our API requests for no 38 // reason. 39 if (user_maintainers.length + team_maintainers.length > 16) { 40 core.warning('Too many potential reviewers, skipping review requests.') 41 // Return a boolean on whether the "needs: reviewers" label should be set. 42 return users_reached.size === 0 && teams_reached.size === 0 43 } 44 45 // Users that should be reached 46 var users_to_reach = new Set([ 47 ...( 48 await Promise.all( 49 user_maintainers.map(async (id) => { 50 const user = await getUser(id) 51 // User may have deleted their account 52 return user?.login?.toLowerCase() 53 }), 54 ) 55 ).filter(Boolean), 56 ...owners 57 .filter((handle) => handle && !handle.includes('/')) 58 .map((handle) => handle.toLowerCase()), 59 ]) 60 // We can't request a review from the author. 61 .difference(new Set([pull_request.user?.login.toLowerCase()])) 62 63 // Filter users to repository collaborators. If they're not, they can't be requested 64 // for review. In that case, they probably missed their invite to the maintainers team. 65 users_to_reach = new Set( 66 ( 67 await Promise.all( 68 Array.from(users_to_reach, async (username) => { 69 // TODO: Restructure this file to only do the collaborator check for those users 70 // who were not already part of a team. Being a member of a team makes them 71 // collaborators by definition. 72 try { 73 await github.rest.repos.checkCollaborator({ 74 ...context.repo, 75 username, 76 }) 77 return username 78 } catch (e) { 79 if (e.status !== 404) throw e 80 core.warning( 81 `PR #${pull_number}: User ${username} cannot be requested for review because they don't exist or are not a repository collaborator, ignoring. They probably missed the automated invite to the maintainers team (see <https://github.com/NixOS/nixpkgs/issues/234293>).`, 82 ) 83 } 84 }), 85 ) 86 ).filter(Boolean), 87 ) 88 log('reviewers - users_to_reach', Array.from(users_to_reach).join(', ')) 89 90 // Similar for teams 91 var teams_to_reach = new Set([ 92 ...( 93 await Promise.all( 94 team_maintainers.map(async (id) => { 95 const team = await getTeam(id) 96 // Team may have been deleted 97 return team?.slug?.toLowerCase() 98 }), 99 ) 100 ).filter(Boolean), 101 ...owners 102 .map((handle) => handle.split('/')) 103 .filter( 104 ([org, slug]) => 105 org.toLowerCase() === context.repo.owner.toLowerCase() && slug, 106 ) 107 .map(([, slug]) => slug.toLowerCase()), 108 ]) 109 teams_to_reach = new Set( 110 ( 111 await Promise.all( 112 Array.from(teams_to_reach, async (slug) => { 113 try { 114 await github.rest.teams.checkPermissionsForRepoInOrg({ 115 org: context.repo.owner, 116 team_slug: slug, 117 owner: context.repo.owner, 118 repo: context.repo.repo, 119 }) 120 return slug 121 } catch (e) { 122 if (e.status !== 404) throw e 123 core.warning( 124 `PR #${pull_number}: Team ${slug} cannot be requested for review because it doesn't exist or has no repository permissions, ignoring. Probably wasn't added to the nixpkgs-maintainers team (see https://github.com/NixOS/nixpkgs/tree/master/maintainers#maintainer-teams)`, 125 ) 126 } 127 }), 128 ) 129 ).filter(Boolean), 130 ) 131 log('reviewers - teams_to_reach', Array.from(teams_to_reach).join(', ')) 132 133 if (users_to_reach.size + teams_to_reach.size > 15) { 134 core.warning( 135 `Too many reviewers (users: ${Array.from(users_to_reach).join(', ')}, teams: ${Array.from(teams_to_reach).join(', ')}), skipping review requests.`, 136 ) 137 // Return a boolean on whether the "needs: reviewers" label should be set. 138 return users_reached.size === 0 && teams_reached.size === 0 139 } 140 141 // We don't want to rerequest reviews from people who already reviewed or were requested 142 const users_not_yet_reached = Array.from( 143 users_to_reach.difference(users_reached), 144 ) 145 log('reviewers - users_not_yet_reached', users_not_yet_reached.join(', ')) 146 // We don't want to rerequest reviews from teams who already reviewed or were requested 147 const teams_not_yet_reached = Array.from( 148 teams_to_reach.difference(teams_reached), 149 ) 150 log('reviewers - teams_not_yet_reached', teams_not_yet_reached.join(', ')) 151 152 if ( 153 users_not_yet_reached.length === 0 && 154 teams_not_yet_reached.length === 0 155 ) { 156 log('Has reviewer changes', 'false (skipped)') 157 } else if (dry) { 158 core.info( 159 `Requesting user reviewers for #${pull_number}: ${users_not_yet_reached.join(', ')} (dry)`, 160 ) 161 core.info( 162 `Requesting team reviewers for #${pull_number}: ${teams_not_yet_reached.join(', ')} (dry)`, 163 ) 164 } else { 165 // We had tried the "request all reviewers at once" thing in the past, but it didn't work out: 166 // https://github.com/NixOS/nixpkgs/commit/034613f860fcd339bd2c20c8f6bc259a2f9dc034 167 // If we're hitting API errors here again, we'll need to investigate - and possibly reverse 168 // course. 169 await github.rest.pulls.requestReviewers({ 170 ...context.repo, 171 pull_number, 172 reviewers: users_not_yet_reached, 173 team_reviewers: teams_not_yet_reached, 174 }) 175 } 176 177 // Return a boolean on whether the "needs: reviewers" label should be set. 178 return ( 179 users_not_yet_reached.length === 0 && 180 teams_not_yet_reached.length === 0 && 181 users_reached.size === 0 && 182 teams_reached.size === 0 183 ) 184} 185 186module.exports = { 187 handleReviewers, 188}