Skip to content

Commit 8e0e906

Browse files
committed
Add @rustbot reroll to pick a new reviewer
Signed-off-by: xizheyin <xizheyin@smail.nju.edu.cn>
1 parent 527bf21 commit 8e0e906

File tree

3 files changed

+73
-4
lines changed

3 files changed

+73
-4
lines changed

parser/src/command/assign.rs

Lines changed: 17 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)]
@@ -55,6 +57,15 @@ impl AssignCommand {
5557
} else {
5658
return Err(toks.error(ParseError::ExpectedEnd));
5759
}
60+
} else if let Some(Token::Word("reroll")) = toks.peek_token()? {
61+
toks.next_token()?;
62+
if let Some(Token::Dot) | Some(Token::EndOfLine) = toks.peek_token()? {
63+
toks.next_token()?;
64+
*input = toks;
65+
return Ok(Some(AssignCommand::Reroll));
66+
} else {
67+
return Err(toks.error(ParseError::ExpectedEnd));
68+
}
5869
} else if let Some(Token::Word("assign")) = toks.peek_token()? {
5970
toks.next_token()?;
6071
if let Some(Token::Word(user)) = toks.next_token()? {
@@ -181,4 +192,10 @@ mod tests {
181192
)
182193
}
183194
}
195+
196+
#[test]
197+
fn test_reroll() {
198+
assert_eq!(parse("reroll."), Ok(Some(AssignCommand::Reroll)),);
199+
assert_eq!(parse("reroll"), Ok(Some(AssignCommand::Reroll)),);
200+
}
184201
}

src/handlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ macro_rules! command_handlers {
306306
// case, just ignore it.
307307
if commands
308308
.iter()
309-
.all(|cmd| matches!(cmd, Command::Assign(Ok(AssignCommand::RequestReview { .. }))))
309+
.all(|cmd| matches!(cmd, Command::Assign(Ok(AssignCommand::RequestReview { .. })) | Command::Assign(Ok(AssignCommand::Reroll))))
310310
{
311311
return;
312312
}

src/handlers/assign.rs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! * `@rustbot assign @gh-user`: Assigns to the given user.
66
//! * `@rustbot claim`: Assigns to the comment author.
77
//! * `@rustbot release-assignment`: Removes the commenter's assignment.
8+
//! * `@rustbot reroll`: Assigns a new reviewer, excluding the current assignee.
89
//! * `r? @user`: Assigns to the given user (PRs only).
910
//!
1011
//! Note: this module does not handle review assignments issued from the
@@ -134,7 +135,7 @@ pub(super) async fn handle_input(
134135

135136
// Don't auto-assign or welcome if the user manually set the assignee when opening.
136137
if event.issue.assignees.is_empty() {
137-
let (assignee, from_comment) = determine_assignee(ctx, event, config, &diff).await?;
138+
let (assignee, from_comment) = determine_assignee(ctx, event, config, &diff, &[]).await?;
138139
if assignee.as_deref() == Some(GHOST_ACCOUNT) {
139140
// "ghost" is GitHub's placeholder account for deleted accounts.
140141
// It is used here as a convenient way to prevent assignment. This
@@ -202,6 +203,7 @@ fn find_assign_command(ctx: &Context, event: &IssuesEvent) -> Option<String> {
202203
let mut input = Input::new(&event.issue.body, vec![&ctx.username]);
203204
input.find_map(|command| match command {
204205
Command::Assign(Ok(AssignCommand::RequestReview { name })) => Some(name),
206+
Command::Assign(Ok(AssignCommand::Reroll)) => None,
205207
_ => None,
206208
})
207209
}
@@ -260,6 +262,7 @@ async fn determine_assignee(
260262
event: &IssuesEvent,
261263
config: &AssignConfig,
262264
diff: &[FileDiff],
265+
exclude_assignees: &[String],
263266
) -> anyhow::Result<(Option<String>, bool)> {
264267
let db_client = ctx.db.get().await;
265268
let teams = crate::team_data::teams(&ctx.github).await?;
@@ -279,8 +282,18 @@ async fn determine_assignee(
279282
// Errors fall-through to try fallback group.
280283
match find_reviewers_from_diff(config, diff) {
281284
Ok(candidates) if !candidates.is_empty() => {
282-
match find_reviewer_from_names(&db_client, &teams, config, &event.issue, &candidates)
283-
.await
285+
let filtered_candidates = candidates
286+
.into_iter()
287+
.filter(|candidate| !exclude_assignees.contains(candidate))
288+
.collect::<Vec<_>>();
289+
match find_reviewer_from_names(
290+
&db_client,
291+
&teams,
292+
config,
293+
&event.issue,
294+
&filtered_candidates,
295+
)
296+
.await
284297
{
285298
Ok(assignee) => return Ok((Some(assignee), false)),
286299
Err(FindReviewerError::TeamNotFound(team)) => log::warn!(
@@ -450,6 +463,44 @@ pub(super) async fn handle_command(
450463
let assignee = match cmd {
451464
AssignCommand::Claim => event.user().login.clone(),
452465
AssignCommand::AssignUser { username } => username,
466+
AssignCommand::Reroll => {
467+
// Get the current assignees and make sure we don't select them again
468+
let current_assignees: Vec<String> =
469+
issue.assignees.iter().map(|a| a.login.clone()).collect();
470+
471+
if current_assignees.is_empty() {
472+
issue
473+
.post_comment(
474+
&ctx.github,
475+
"Cannot reroll because there is no current assignee.",
476+
)
477+
.await?;
478+
return Ok(());
479+
}
480+
481+
// Get PR diff to find candidates
482+
let Some(diff) = issue.diff(&ctx.github).await? else {
483+
log::error!("Failed to get PR diff for reroll.");
484+
return Ok(());
485+
};
486+
487+
let Event::Issue(issue_event) = event else {
488+
log::error!("Failed to get IssuesEvent for reroll.");
489+
return Ok(());
490+
};
491+
492+
let (Some(new_assignee), _) =
493+
determine_assignee(ctx, &issue_event, config, diff, &current_assignees).await?
494+
else {
495+
issue.post_comment(
496+
&ctx.github,
497+
"Failed to determine new assignee for reroll. Please try r? @author to pick a new reviewer.",
498+
).await?;
499+
return Ok(());
500+
};
501+
502+
new_assignee
503+
}
453504
AssignCommand::ReleaseAssignment => {
454505
log::trace!(
455506
"ignoring release on PR {:?}, must always have assignee",
@@ -542,6 +593,7 @@ pub(super) async fn handle_command(
542593
};
543594
}
544595
AssignCommand::RequestReview { .. } => bail!("r? is only allowed on PRs."),
596+
AssignCommand::Reroll => bail!("Reroll is only allowed on PRs."),
545597
};
546598
// Don't re-assign if aleady assigned, e.g. on comment edit
547599
if issue.contain_assignee(&to_assign) {

0 commit comments

Comments
 (0)