Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
meysam81 authored Oct 23, 2023
2 parents 7c6abec + d3eb488 commit 6d3e115
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 54 deletions.
6 changes: 6 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ GITHUB_WEBHOOK_SECRET=MUST_BE_CONFIGURED
# for logging, refer to this document: https://rust-lang-nursery.github.io/rust-cookbook/development_tools/debugging/config_log.html
# `RUSTC_LOG` is not required to run the application, but it makes local development easier
# RUST_LOG=MUST_BE_CONFIGURED

# If you are running a bot on non-rustbot account,
# this allows to configure that username which the bot will respond to.
# For example write blahblahblah here, if you want for this bot to
# respond to @blahblahblah claim.
# TRIAGEBOT_USERNAME=CAN_BE_CONFIGURED
12 changes: 10 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub(crate) struct Config {
pub(crate) notify_zulip: Option<NotifyZulipConfig>,
pub(crate) github_releases: Option<GitHubReleasesConfig>,
pub(crate) review_submitted: Option<ReviewSubmittedConfig>,
pub(crate) review_requested: Option<ReviewRequestedConfig>,
pub(crate) shortcut: Option<ShortcutConfig>,
pub(crate) note: Option<NoteConfig>,
pub(crate) mentions: Option<MentionsConfig>,
Expand Down Expand Up @@ -108,9 +109,9 @@ impl AssignConfig {

#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
pub(crate) struct NoMergesConfig {
/// No action will be taken on PRs with these labels.
/// No action will be taken on PRs with these substrings in the title.
#[serde(default)]
pub(crate) exclude_labels: Vec<String>,
pub(crate) exclude_titles: Vec<String>,
/// Set these labels on the PR when merge commits are detected.
#[serde(default)]
pub(crate) labels: Vec<String>,
Expand Down Expand Up @@ -269,6 +270,12 @@ pub(crate) struct ReviewSubmittedConfig {
pub(crate) reviewed_label: String,
}

#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
pub(crate) struct ReviewRequestedConfig {
pub(crate) remove_labels: Vec<String>,
pub(crate) add_labels: Vec<String>,
}

pub(crate) async fn get(
gh: &GithubClient,
repo: &Repository,
Expand Down Expand Up @@ -437,6 +444,7 @@ mod tests {
notify_zulip: None,
github_releases: None,
review_submitted: None,
review_requested: None,
mentions: None,
no_merges: None,
validate_config: Some(ValidateConfig {}),
Expand Down
22 changes: 16 additions & 6 deletions src/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ pub struct IssueCommentEvent {
}

#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(rename_all = "snake_case", tag = "action")]
pub enum IssuesAction {
Opened,
Edited,
Expand All @@ -936,13 +936,22 @@ pub enum IssuesAction {
Reopened,
Assigned,
Unassigned,
Labeled,
Unlabeled,
Labeled {
/// The label added from the issue
label: Label,
},
Unlabeled {
/// The label removed from the issue
label: Label,
},
Locked,
Unlocked,
Milestoned,
Demilestoned,
ReviewRequested,
ReviewRequested {
/// The person requested to review the pull request
requested_reviewer: User,
},
ReviewRequestRemoved,
ReadyForReview,
Synchronize,
Expand All @@ -953,13 +962,14 @@ pub enum IssuesAction {

#[derive(Debug, serde::Deserialize)]
pub struct IssuesEvent {
#[serde(flatten)]
pub action: IssuesAction,
#[serde(alias = "pull_request")]
pub issue: Issue,
pub changes: Option<Changes>,
pub repository: Repository,
/// Some if action is IssuesAction::Labeled, for example
pub label: Option<Label>,
/// The GitHub user that triggered the event.
pub sender: User,
}

#[derive(Debug, serde::Deserialize)]
Expand Down
2 changes: 2 additions & 0 deletions src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ mod notify_zulip;
mod ping;
mod prioritize;
mod relabel;
mod review_requested;
mod review_submitted;
mod rfc_helper;
pub mod rustc_commits;
Expand Down Expand Up @@ -165,6 +166,7 @@ issue_handlers! {
mentions,
no_merges,
notify_zulip,
review_requested,
validate_config,
}

Expand Down
16 changes: 11 additions & 5 deletions src/handlers/assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,16 @@ minimum review times lag, PR authors and assigned reviewers should ensure that t
label (`S-waiting-on-review` and `S-waiting-on-author`) stays updated, invoking these commands \
when appropriate:
- `@rustbot author`: the review is finished, PR author should check the comments and take action accordingly
- `@rustbot review`: the author is ready for a review, this PR will be queued again in the reviewer's queue";
- `@{bot} author`: the review is finished, PR author should check the comments and take action accordingly
- `@{bot} review`: the author is ready for a review, this PR will be queued again in the reviewer's queue";

const WELCOME_WITH_REVIEWER: &str = "@{assignee} (or someone else)";

const WELCOME_WITHOUT_REVIEWER: &str = "@Mark-Simulacrum (NB. this repo may be misconfigured)";

const RETURNING_USER_WELCOME_MESSAGE: &str = "r? @{assignee}
(rustbot has picked a reviewer for you, use r? to override)";
({bot} has picked a reviewer for you, use r? to override)";

const RETURNING_USER_WELCOME_MESSAGE_NO_REVIEWER: &str =
"@{author}: no appropriate reviewer found, use r? to override";
Expand Down Expand Up @@ -141,12 +141,18 @@ pub(super) async fn handle_input(
let mut welcome = NEW_USER_WELCOME_MESSAGE.replace("{who}", &who_text);
if let Some(contrib) = &config.contributing_url {
welcome.push_str("\n\n");
welcome.push_str(&CONTRIBUTION_MESSAGE.replace("{contributing_url}", contrib));
welcome.push_str(
&CONTRIBUTION_MESSAGE
.replace("{contributing_url}", contrib)
.replace("{bot}", &ctx.username),
);
}
Some(welcome)
} else if !from_comment {
let welcome = match &assignee {
Some(assignee) => RETURNING_USER_WELCOME_MESSAGE.replace("{assignee}", assignee),
Some(assignee) => RETURNING_USER_WELCOME_MESSAGE
.replace("{assignee}", assignee)
.replace("{bot}", &ctx.username),
None => RETURNING_USER_WELCOME_MESSAGE_NO_REVIEWER
.replace("{author}", &event.issue.user.login),
};
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/autolabel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ pub(super) async fn parse_input(
}
}

if event.action == IssuesAction::Labeled {
if let IssuesAction::Labeled { label } = &event.action {
let mut autolabels = Vec::new();
let applied_label = &event.label.as_ref().expect("label").name;
let applied_label = &label.name;

'outer: for (label, config) in config.get_by_trigger(applied_label) {
let exclude_patterns: Vec<glob::Pattern> = config
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/glacier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub(super) async fn handle_command(

let octocrab = &ctx.octocrab;

let fork = octocrab.repos("rustbot", "glacier");
let fork = octocrab.repos(&ctx.username, "glacier");
let base = octocrab.repos("rust-lang", "glacier");

let master = base
Expand Down Expand Up @@ -65,7 +65,7 @@ pub(super) async fn handle_command(
.pulls("rust-lang", "glacier")
.create(
format!("ICE - rust-lang/rust#{}", number),
format!("rustbot:triagebot-ice-{}", number),
format!("{}:triagebot-ice-{}", ctx.username, number),
"master",
)
.body(format!(
Expand Down
19 changes: 3 additions & 16 deletions src/handlers/major_change.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,7 @@ pub(super) async fn parse_input(
}

// If we were labeled with accepted, then issue that event
if event.action == IssuesAction::Labeled
&& event
.label
.as_ref()
.map_or(false, |l| l.name == config.accept_label)
if matches!(&event.action, IssuesAction::Labeled { label } if label.name == config.accept_label)
{
return Ok(Some(Invocation::AcceptedProposal));
}
Expand All @@ -70,17 +66,8 @@ pub(super) async fn parse_input(
// We want to treat reopened issues as new proposals but if the
// issue is freshly opened, we only want to trigger once;
// currently we do so on the label event.
if (event.action == IssuesAction::Reopened
&& event
.issue
.labels()
.iter()
.any(|l| l.name == enabling_label))
|| (event.action == IssuesAction::Labeled
&& event
.label
.as_ref()
.map_or(false, |l| l.name == enabling_label))
if matches!(event.action, IssuesAction::Reopened if event.issue.labels().iter().any(|l| l.name == enabling_label))
|| matches!(&event.action, IssuesAction::Labeled { label } if label.name == enabling_label)
{
return Ok(Some(Invocation::NewProposal));
}
Expand Down
42 changes: 30 additions & 12 deletions src/handlers/no_merges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ pub(super) async fn parse_input(
return Ok(None);
}

// Don't trigger if the PR has any of the excluded labels.
for label in event.issue.labels() {
if config.exclude_labels.contains(&label.name) {
return Ok(None);
}
// Don't trigger if the PR has any of the excluded title segments.
if config
.exclude_titles
.iter()
.any(|s| event.issue.title.contains(s))
{
return Ok(None);
}

let mut merge_commits = HashSet::new();
Expand All @@ -70,12 +72,11 @@ pub(super) async fn parse_input(
}
}

let input = NoMergesInput { merge_commits };
Ok(if input.merge_commits.is_empty() {
None
} else {
Some(input)
})
if merge_commits.is_empty() {
return Ok(None);
}

Ok(Some(NoMergesInput { merge_commits }))
}

const DEFAULT_MESSAGE: &str = "
Expand All @@ -102,14 +103,15 @@ pub(super) async fn handle_input(
let mut client = ctx.db.get().await;
let mut state: IssueData<'_, NoMergesState> =
IssueData::load(&mut client, &event.issue, NO_MERGES_KEY).await?;
let first_time = state.data.mentioned_merge_commits.is_empty();

let mut message = config
.message
.as_deref()
.unwrap_or(DEFAULT_MESSAGE)
.to_string();

let since_last_posted = if state.data.mentioned_merge_commits.is_empty() {
let since_last_posted = if first_time {
""
} else {
" (since this message was last posted)"
Expand All @@ -132,6 +134,22 @@ pub(super) async fn handle_input(
}

if should_send {
if !first_time {
// Check if the labels are still set.
// Otherwise, they were probably removed manually.
let any_removed = config.labels.iter().any(|label| {
// No label on the issue matches.
event.issue.labels().iter().all(|l| &l.name != label)
});

if any_removed {
// Assume it was a false positive, so don't
// re-add the labels or send a message this time.
state.save().await?;
return Ok(());
}
}

// Set labels
let labels = config
.labels
Expand Down
18 changes: 10 additions & 8 deletions src/handlers/notify_zulip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ pub(super) async fn parse_input(
None => return Ok(None),
};

match event.action {
IssuesAction::Labeled | IssuesAction::Unlabeled => {
let applied_label = event.label.as_ref().expect("label").clone();
match &event.action {
IssuesAction::Labeled { label } | IssuesAction::Unlabeled { label } => {
let applied_label = label.clone();
Ok(config
.labels
.get(&applied_label.name)
Expand All @@ -60,14 +60,16 @@ fn parse_label_change_input(
}

match event.action {
IssuesAction::Labeled if config.message_on_add.is_some() => Some(NotifyZulipInput {
IssuesAction::Labeled { .. } if config.message_on_add.is_some() => Some(NotifyZulipInput {
notification_type: NotificationType::Labeled,
label,
}),
IssuesAction::Unlabeled if config.message_on_remove.is_some() => Some(NotifyZulipInput {
notification_type: NotificationType::Unlabeled,
label,
}),
IssuesAction::Unlabeled { .. } if config.message_on_remove.is_some() => {
Some(NotifyZulipInput {
notification_type: NotificationType::Unlabeled,
label,
})
}
_ => None,
}
}
Expand Down
57 changes: 57 additions & 0 deletions src/handlers/review_requested.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use crate::config::ReviewRequestedConfig;
use crate::github::{IssuesAction, IssuesEvent, Label};
use crate::handlers::Context;

pub(crate) struct ReviewRequestedInput {}

pub(crate) async fn parse_input(
_ctx: &Context,
event: &IssuesEvent,
config: Option<&ReviewRequestedConfig>,
) -> Result<Option<ReviewRequestedInput>, String> {
// PR author requests a review from one of the assignees

if config.is_none() {
return Ok(None);
}

let IssuesAction::ReviewRequested { requested_reviewer } = &event.action else {
return Ok(None);
};

if event.sender != event.issue.user {
return Ok(None);
}

if !event.issue.assignees.contains(requested_reviewer) {
return Ok(None);
}

Ok(Some(ReviewRequestedInput {}))
}

pub(crate) async fn handle_input(
ctx: &Context,
config: &ReviewRequestedConfig,
event: &IssuesEvent,
ReviewRequestedInput {}: ReviewRequestedInput,
) -> anyhow::Result<()> {
event
.issue
.add_labels(
&ctx.github,
config
.add_labels
.iter()
.cloned()
.map(|name| Label { name })
.collect(),
)
.await?;

for label in &config.remove_labels {
event.issue.remove_label(&ctx.github, label).await?;
}

Ok(())
}
Loading

0 comments on commit 6d3e115

Please sign in to comment.