Skip to content

Commit d37bb39

Browse files
authored
Merge pull request #2219 from Kobzol/reroll
Add `@rustbot reroll` command
2 parents ca35b2f + 9393844 commit d37bb39

File tree

2 files changed

+70
-17
lines changed

2 files changed

+70
-17
lines changed

parser/src/command/assign.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub enum AssignCommand {
2222
AssignUser { username: String },
2323
/// Corresponds to `r? [@]user`.
2424
RequestReview { name: String },
25+
/// Corresponds to `@bot reroll`.
26+
Reroll,
2527
}
2628

2729
#[derive(PartialEq, Eq, Debug)]
@@ -77,6 +79,15 @@ impl AssignCommand {
7779
} else {
7880
Err(toks.error(ParseError::ExpectedEnd))
7981
}
82+
} else if let Some(Token::Word("reroll")) = toks.peek_token()? {
83+
toks.next_token()?;
84+
if let Some(Token::Dot | Token::EndOfLine) = toks.peek_token()? {
85+
toks.next_token()?;
86+
*input = toks;
87+
Ok(Some(AssignCommand::Reroll))
88+
} else {
89+
Err(toks.error(ParseError::ExpectedEnd))
90+
}
8091
} else {
8192
Ok(None)
8293
}

src/handlers/assign.rs

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
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."
324333
async 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

Comments
 (0)