Skip to content

Commit bbdda31

Browse files
authored
Merge pull request #1963 from Kobzol/no-assign-on-draft
Do not perform PR assignment for draft PRs without r?
2 parents f118be8 + 94a3146 commit bbdda31

File tree

1 file changed

+44
-10
lines changed

1 file changed

+44
-10
lines changed

src/handlers/assign.rs

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,18 @@ Please choose another assignee.";
9090
// Special account that we use to prevent assignment.
9191
const GHOST_ACCOUNT: &str = "ghost";
9292

93+
/// Assignment data stored in the issue/PR body.
9394
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
9495
struct AssignData {
9596
user: Option<String>,
9697
}
9798

98-
/// Input for auto-assignment when a PR is created.
99-
pub(super) struct AssignInput {}
99+
/// Input for auto-assignment when a PR is created or converted from draft.
100+
#[derive(Debug)]
101+
pub(super) enum AssignInput {
102+
Opened { draft: bool },
103+
ReadyForReview,
104+
}
100105

101106
/// Prepares the input when a new PR is opened.
102107
pub(super) async fn parse_input(
@@ -108,13 +113,17 @@ pub(super) async fn parse_input(
108113
Some(config) => config,
109114
None => return Ok(None),
110115
};
111-
if config.owners.is_empty()
112-
|| !matches!(event.action, IssuesAction::Opened)
113-
|| !event.issue.is_pr()
114-
{
116+
if config.owners.is_empty() || !event.issue.is_pr() {
115117
return Ok(None);
116118
}
117-
Ok(Some(AssignInput {}))
119+
120+
match event.action {
121+
IssuesAction::Opened => Ok(Some(AssignInput::Opened {
122+
draft: event.issue.draft,
123+
})),
124+
IssuesAction::ReadyForReview => Ok(Some(AssignInput::ReadyForReview)),
125+
_ => Ok(None),
126+
}
118127
}
119128

120129
/// Handles the work of setting an assignment for a new PR and posting a
@@ -123,8 +132,31 @@ pub(super) async fn handle_input(
123132
ctx: &Context,
124133
config: &AssignConfig,
125134
event: &IssuesEvent,
126-
_input: AssignInput,
135+
input: AssignInput,
127136
) -> anyhow::Result<()> {
137+
let assign_command = find_assign_command(ctx, event);
138+
139+
// Perform assignment when:
140+
// - PR was opened normally
141+
// - PR was opened as a draft with an explicit r? (but not r? ghost)
142+
// - PR was converted from a draft and there are no current assignees
143+
let should_assign = match input {
144+
AssignInput::Opened { draft: false } => true,
145+
AssignInput::Opened { draft: true } => {
146+
// Even if the PR is opened as a draft, we still want to perform assignment if r?
147+
// was used. However, historically, `r? ghost` was supposed to mean "do not
148+
// perform assignment". So in that case, we skip the assignment and only perform it once
149+
// the PR has been marked as being ready for review.
150+
assign_command.as_ref().is_some_and(|a| a != GHOST_ACCOUNT)
151+
}
152+
AssignInput::ReadyForReview => event.issue.assignees.is_empty(),
153+
};
154+
155+
if !should_assign {
156+
log::info!("Skipping PR assignment, input: {input:?}, assign_command: {assign_command:?}");
157+
return Ok(());
158+
}
159+
128160
let Some(diff) = event.issue.diff(&ctx.github).await? else {
129161
bail!(
130162
"expected issue {} to be a PR, but the diff could not be determined",
@@ -134,7 +166,8 @@ pub(super) async fn handle_input(
134166

135167
// Don't auto-assign or welcome if the user manually set the assignee when opening.
136168
if event.issue.assignees.is_empty() {
137-
let (assignee, from_comment) = determine_assignee(ctx, event, config, &diff).await?;
169+
let (assignee, from_comment) =
170+
determine_assignee(ctx, assign_command, event, config, &diff).await?;
138171
if assignee.as_deref() == Some(GHOST_ACCOUNT) {
139172
// "ghost" is GitHub's placeholder account for deleted accounts.
140173
// It is used here as a convenient way to prevent assignment. This
@@ -257,13 +290,14 @@ async fn set_assignee(issue: &Issue, github: &GithubClient, username: &str) {
257290
/// determined from the diff).
258291
async fn determine_assignee(
259292
ctx: &Context,
293+
assign_command: Option<String>,
260294
event: &IssuesEvent,
261295
config: &AssignConfig,
262296
diff: &[FileDiff],
263297
) -> anyhow::Result<(Option<String>, bool)> {
264298
let db_client = ctx.db.get().await;
265299
let teams = crate::team_data::teams(&ctx.github).await?;
266-
if let Some(name) = find_assign_command(ctx, event) {
300+
if let Some(name) = assign_command {
267301
// User included `r?` in the opening PR body.
268302
match find_reviewer_from_names(&db_client, &teams, config, &event.issue, &[name]).await {
269303
Ok(assignee) => return Ok((Some(assignee), true)),

0 commit comments

Comments
 (0)