66//! * `@rustbot claim`: Assigns to the comment author.
77//! * `@rustbot release-assignment`: Removes the commenter's assignment.
88//! * `r? @user`: Assigns to the given user (PRs only).
9+ //! * `@rustbot reroll`: Re-run the automatic assignment logic based on PR diff and owner map that
10+ //! is normally triggered when a PR is opened.
911//!
1012//! Note: this module does not handle review assignments issued from the
1113//! GitHub "Assignees" dropdown menu
@@ -137,8 +139,15 @@ pub(super) async fn handle_input(
137139
138140 // Don't auto-assign or welcome if the user manually set the assignee when opening.
139141 if event. issue . assignees . is_empty ( ) {
140- let ( assignee, from_comment) =
141- determine_assignee ( ctx, assign_command, event, config, diff) . await ?;
142+ let ( assignee, from_comment) = determine_assignee (
143+ ctx,
144+ assign_command,
145+ & event. issue ,
146+ & event. issue . user . login ,
147+ config,
148+ diff,
149+ )
150+ . await ?;
142151 if assignee. as_ref ( ) . map ( |r| r. name . as_str ( ) ) == Some ( GHOST_ACCOUNT ) {
143152 // "ghost" is GitHub's placeholder account for deleted accounts.
144153 // It is used here as a convenient way to prevent assignment. This
@@ -324,7 +333,8 @@ They may take a while to respond."
324333async fn determine_assignee (
325334 ctx : & Context ,
326335 assign_command : Option < String > ,
327- event : & IssuesEvent ,
336+ issue : & Issue ,
337+ requested_by : & str ,
328338 config : & AssignConfig ,
329339 diff : & [ FileDiff ] ,
330340) -> anyhow:: Result < ( Option < ReviewerSelection > , bool ) > {
@@ -337,18 +347,15 @@ async fn determine_assignee(
337347 ctx. workqueue . clone ( ) ,
338348 teams,
339349 config,
340- & event . issue ,
341- & event . issue . user . login ,
350+ issue,
351+ requested_by ,
342352 & [ name] ,
343353 )
344354 . await
345355 {
346356 Ok ( assignee) => return Ok ( ( Some ( assignee) , true ) ) ,
347357 Err ( e) => {
348- event
349- . issue
350- . post_comment ( & ctx. github , & e. to_string ( ) )
351- . await ?;
358+ issue. post_comment ( & ctx. github , & e. to_string ( ) ) . await ?;
352359 // Fall through below for normal diff detection.
353360 }
354361 }
@@ -361,8 +368,8 @@ async fn determine_assignee(
361368 ctx. workqueue . clone ( ) ,
362369 teams,
363370 config,
364- & event . issue ,
365- & event . issue . user . login ,
371+ issue,
372+ requested_by ,
366373 & candidates,
367374 )
368375 . await
@@ -371,7 +378,7 @@ async fn determine_assignee(
371378 Err ( FindReviewerError :: TeamNotFound ( team) ) => log:: warn!(
372379 "team {team} not found via diff from PR {}, \
373380 is there maybe a misconfigured group?",
374- event . issue. global_id( )
381+ issue. global_id( )
375382 ) ,
376383 Err (
377384 e @ ( FindReviewerError :: NoReviewer { .. }
@@ -383,7 +390,7 @@ async fn determine_assignee(
383390 | FindReviewerError :: ReviewerAtMaxCapacity { .. } ) ,
384391 ) => log:: trace!(
385392 "no reviewer could be determined for PR {}: {e}" ,
386- event . issue. global_id( )
393+ issue. global_id( )
387394 ) ,
388395 }
389396 }
@@ -403,8 +410,8 @@ async fn determine_assignee(
403410 ctx. workqueue . clone ( ) ,
404411 teams,
405412 config,
406- & event . issue ,
407- & event . issue . user . login ,
413+ issue,
414+ requested_by ,
408415 fallback,
409416 )
410417 . await
@@ -413,7 +420,7 @@ async fn determine_assignee(
413420 Err ( e) => {
414421 log:: trace!(
415422 "failed to select from fallback group for PR {}: {e}" ,
416- event . issue. global_id( )
423+ issue. global_id( )
417424 ) ;
418425 }
419426 }
@@ -564,6 +571,38 @@ pub(super) async fn handle_command(
564571 }
565572 name
566573 }
574+ AssignCommand :: Reroll => {
575+ // We need to compute the PR diff here, but the IssuesEvent created from a
576+ // comment webhook doesn't contain the required `base` and `head` fields.
577+ // So we have to load the information about the pull request from the GitHub API
578+ // explicitly.
579+ let pr = ctx
580+ . github
581+ . pull_request ( issue. repository ( ) , issue. number )
582+ . await
583+ . context ( "Cannot load pull request from GitHub" ) ?;
584+ let Some ( diff) = pr. diff ( & ctx. github ) . await . context ( "Cannot load PR diff" ) ? else {
585+ bail ! (
586+ "expected issue {} to be a PR, but the diff could not be determined" ,
587+ issue. number
588+ ) ;
589+ } ;
590+
591+ let ( assignee, _) =
592+ determine_assignee ( ctx, None , issue, & event. user ( ) . login , config, diff)
593+ . await
594+ . context ( "Cannot determine assignee when rerolling" ) ?;
595+ if let Some ( assignee) = assignee {
596+ set_assignee ( ctx, issue, & ctx. github , & assignee)
597+ . await
598+ . context ( "Cannot set assignee when rerolling" ) ?;
599+ } else {
600+ return user_error ! (
601+ "Cannot determine a new reviewer. Use `r? <username or team>` to request a specific reviewer or a team."
602+ ) ;
603+ }
604+ return Ok ( ( ) ) ;
605+ }
567606 } ;
568607
569608 // In the PR body, `r? ghost` means "do not assign anybody".
@@ -638,8 +677,11 @@ pub(super) async fn handle_command(
638677 AssignCommand :: RequestReview { .. } => {
639678 return user_error ! ( "r? is only allowed on PRs." ) ;
640679 }
680+ AssignCommand :: Reroll { .. } => {
681+ return user_error ! ( "reroll is only allowed on PRs." ) ;
682+ }
641683 } ;
642- // Don't re-assign if aleady assigned, e.g. on comment edit
684+ // Don't re-assign if already assigned, e.g. on comment edit
643685 if issue. contain_assignee ( & to_assign) {
644686 log:: trace!(
645687 "ignoring assign issue {issue} to {to_assign}, already assigned" ,
0 commit comments