Skip to content

Commit

Permalink
Add an issue transfer command
Browse files Browse the repository at this point in the history
  • Loading branch information
ehuss committed May 3, 2024
1 parent 75f40b6 commit f054b94
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 0 deletions.
8 changes: 8 additions & 0 deletions parser/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod prioritize;
pub mod relabel;
pub mod second;
pub mod shortcut;
pub mod transfer;

#[derive(Debug, PartialEq)]
pub enum Command<'a> {
Expand All @@ -26,6 +27,7 @@ pub enum Command<'a> {
Shortcut(Result<shortcut::ShortcutCommand, Error<'a>>),
Close(Result<close::CloseCommand, Error<'a>>),
Note(Result<note::NoteCommand, Error<'a>>),
Transfer(Result<transfer::TransferCommand, Error<'a>>),
}

#[derive(Debug)]
Expand Down Expand Up @@ -132,6 +134,11 @@ impl<'a> Input<'a> {
Command::Close,
&original_tokenizer,
));
success.extend(parse_single_command(
transfer::TransferCommand::parse,
Command::Transfer,
&original_tokenizer,
));

if success.len() > 1 {
panic!(
Expand Down Expand Up @@ -207,6 +214,7 @@ impl<'a> Command<'a> {
Command::Shortcut(r) => r.is_ok(),
Command::Close(r) => r.is_ok(),
Command::Note(r) => r.is_ok(),
Command::Transfer(r) => r.is_ok(),
}
}

Expand Down
38 changes: 38 additions & 0 deletions parser/src/command/transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! Parses the `@bot transfer reponame` command.

use crate::error::Error;
use crate::token::{Token, Tokenizer};
use std::fmt;

#[derive(Debug, PartialEq, Eq)]
pub struct TransferCommand(pub String);

#[derive(Debug)]
pub enum ParseError {
MissingRepo,
}

impl std::error::Error for ParseError {}

impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ParseError::MissingRepo => write!(f, "missing repository name"),
}
}
}

impl TransferCommand {
pub fn parse<'a>(input: &mut Tokenizer<'a>) -> Result<Option<Self>, Error<'a>> {
if !matches!(input.peek_token()?, Some(Token::Word("transfer"))) {
return Ok(None);
}
input.next_token()?;
let repo = if let Some(Token::Word(repo)) = input.next_token()? {
repo.to_owned()
} else {
return Err(input.error(ParseError::MissingRepo));
};
Ok(Some(TransferCommand(repo)))
}
}
7 changes: 7 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub(crate) struct Config {
#[serde(default = "ValidateConfig::default")]
pub(crate) validate_config: Option<ValidateConfig>,
pub(crate) pr_tracking: Option<ReviewPrefsConfig>,
pub(crate) transfer: Option<TransferConfig>,
}

#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
Expand Down Expand Up @@ -344,6 +345,11 @@ pub(crate) struct ReviewPrefsConfig {
_empty: (),
}

#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
#[serde(deny_unknown_fields)]
pub(crate) struct TransferConfig {}

fn get_cached_config(repo: &str) -> Option<Result<Arc<Config>, ConfigurationError>> {
let cache = CONFIG_CACHE.read().unwrap();
cache.get(repo).and_then(|(config, fetch_time)| {
Expand Down Expand Up @@ -520,6 +526,7 @@ mod tests {
no_merges: None,
validate_config: Some(ValidateConfig {}),
pr_tracking: None,
transfer: None,
}
);
}
Expand Down
2 changes: 2 additions & 0 deletions src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mod review_submitted;
mod rfc_helper;
pub mod rustc_commits;
mod shortcut;
mod transfer;
pub mod types_planning_updates;
mod validate_config;

Expand Down Expand Up @@ -292,6 +293,7 @@ command_handlers! {
shortcut: Shortcut,
close: Close,
note: Note,
transfer: Transfer,
}

pub struct Context {
Expand Down
53 changes: 53 additions & 0 deletions src/handlers/transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! Handles the `@rustbot transfer reponame` command to transfer an issue to
//! another repository.

use crate::{config::TransferConfig, github::Event, handlers::Context};
use parser::command::transfer::TransferCommand;

pub(super) async fn handle_command(
ctx: &Context,
_config: &TransferConfig,
event: &Event,
input: TransferCommand,
) -> anyhow::Result<()> {
let issue = event.issue().unwrap();
if issue.is_pr() {
issue
.post_comment(&ctx.github, "Only issues can be transferred.")
.await?;
return Ok(());
}
if !event
.user()
.is_team_member(&ctx.github)
.await
.ok()
.unwrap_or(false)
{
issue
.post_comment(
&ctx.github,
"Only team members may use the `transfer` command.",
)
.await?;
return Ok(());
}

let repo = input.0;
let repo = repo.strip_prefix("rust-lang/").unwrap_or(&repo);
if repo.contains('/') {
issue
.post_comment(&ctx.github, "Cross-organization transfers are not allowed.")
.await?;
return Ok(());
}

if let Err(e) = issue.transfer(&ctx.github, "rust-lang", &repo).await {
issue
.post_comment(&ctx.github, &format!("Failed to transfer issue:\n{e:?}"))
.await?;
return Ok(());
}

Ok(())
}

0 comments on commit f054b94

Please sign in to comment.