diff --git a/src/config.rs b/src/config.rs index 248ac546c..acb2f14f9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -29,6 +29,7 @@ pub(crate) struct Config { pub(crate) notify_zulip: Option, pub(crate) github_releases: Option, pub(crate) review_submitted: Option, + pub(crate) beta_backport: Option, } #[derive(PartialEq, Eq, Debug, serde::Deserialize)] @@ -112,6 +113,13 @@ pub(crate) struct AutolabelLabelConfig { pub(crate) exclude_labels: Vec, } +#[derive(PartialEq, Eq, Debug, serde::Deserialize)] +pub(crate) struct BetaBackportConfig { + pub(crate) trigger_labels: Vec, + #[serde(default)] + pub(crate) labels_to_add: Vec, +} + #[derive(PartialEq, Eq, Debug, serde::Deserialize)] pub(crate) struct NotifyZulipConfig { #[serde(flatten)] @@ -298,6 +306,7 @@ mod tests { notify_zulip: None, github_releases: None, review_submitted: None, + beta_backport: None, } ); } diff --git a/src/github.rs b/src/github.rs index 22e8fecfb..a75e07da7 100644 --- a/src/github.rs +++ b/src/github.rs @@ -762,6 +762,20 @@ pub struct Repository { impl Repository { const GITHUB_API_URL: &'static str = "https://api.github.com"; + pub async fn get_issue(&self, client: &GithubClient, id: u64) -> anyhow::Result { + let url = format!( + "{}/repos/{}/issues/{}", + Repository::GITHUB_API_URL, + self.full_name, + id + ); + let result = client.get(&url); + client + .json(result) + .await + .with_context(|| format!("failed to get issue from {}", url)) + } + pub async fn get_issues<'a>( &self, client: &GithubClient, diff --git a/src/handlers.rs b/src/handlers.rs index e1100fcd2..637d7787c 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -25,6 +25,7 @@ impl fmt::Display for HandlerError { mod assign; mod autolabel; +mod beta_backport; mod close; mod github_releases; mod glacier; @@ -141,6 +142,7 @@ macro_rules! issue_handlers { // Each module in the list must contain the functions `parse_input` and `handle_input`. issue_handlers! { autolabel, + beta_backport, major_change, notify_zulip, } diff --git a/src/handlers/beta_backport.rs b/src/handlers/beta_backport.rs new file mode 100644 index 000000000..1576c08c8 --- /dev/null +++ b/src/handlers/beta_backport.rs @@ -0,0 +1,85 @@ +use crate::config::BetaBackportConfig; +use crate::github::{IssuesAction, IssuesEvent, Label}; +use crate::handlers::Context; +use regex::Regex; + +lazy_static! { + // See https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-issues/linking-a-pull-request-to-an-issue + static ref CLOSES_ISSUE: Regex = Regex::new("(close[sd]|fix(e[sd])?|resolve[sd]) #(\\d+)").unwrap(); +} + +pub(crate) struct BetaBackportInput { + ids: Vec, +} + +pub(crate) fn parse_input( + _ctx: &Context, + event: &IssuesEvent, + config: Option<&BetaBackportConfig>, +) -> Result, String> { + if config.is_none() { + return Ok(None); + } + + if event.action != IssuesAction::Opened { + return Ok(None); + } + + if event.issue.pull_request.is_none() { + return Ok(None); + } + + let mut ids = vec![]; + for caps in CLOSES_ISSUE.captures_iter(&event.issue.body) { + let id = caps.get(1).unwrap().as_str(); + let id = match id.parse::() { + Ok(id) => id, + Err(err) => { + return Err(format!("Failed to parse issue id `{}`, error: {}", id, err)); + } + }; + ids.push(id); + } + + return Ok(Some(BetaBackportInput { ids })); +} + +pub(super) async fn handle_input( + ctx: &Context, + config: &BetaBackportConfig, + event: &IssuesEvent, + input: BetaBackportInput, +) -> anyhow::Result<()> { + let mut issues = input + .ids + .iter() + .copied() + .map(|id| async move { event.repository.get_issue(&ctx.github, id).await }); + + let trigger_labels: Vec<_> = config + .trigger_labels + .iter() + .cloned() + .map(|name| Label { name }) + .collect(); + while let Some(issue) = issues.next() { + let issue = issue.await.unwrap(); + if issue + .labels + .iter() + .any(|issue_label| trigger_labels.contains(issue_label)) + { + let mut new_labels = event.issue.labels().to_owned(); + new_labels.extend( + config + .labels_to_add + .iter() + .cloned() + .map(|name| Label { name }), + ); + return event.issue.set_labels(&ctx.github, new_labels).await; + } + } + + Ok(()) +}