Skip to content

Commit ade10bb

Browse files
committed
Add an issue transfer command
1 parent 8a560b3 commit ade10bb

File tree

5 files changed

+108
-0
lines changed

5 files changed

+108
-0
lines changed

parser/src/command.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub mod prioritize;
1313
pub mod relabel;
1414
pub mod second;
1515
pub mod shortcut;
16+
pub mod transfer;
1617

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

3133
#[derive(Debug)]
@@ -132,6 +134,11 @@ impl<'a> Input<'a> {
132134
Command::Close,
133135
&original_tokenizer,
134136
));
137+
success.extend(parse_single_command(
138+
transfer::TransferCommand::parse,
139+
Command::Transfer,
140+
&original_tokenizer,
141+
));
135142

136143
if success.len() > 1 {
137144
panic!(
@@ -207,6 +214,7 @@ impl<'a> Command<'a> {
207214
Command::Shortcut(r) => r.is_ok(),
208215
Command::Close(r) => r.is_ok(),
209216
Command::Note(r) => r.is_ok(),
217+
Command::Transfer(r) => r.is_ok(),
210218
}
211219
}
212220

parser/src/command/transfer.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! Parses the `@bot transfer reponame` command.
2+
3+
use crate::error::Error;
4+
use crate::token::{Token, Tokenizer};
5+
use std::fmt;
6+
7+
#[derive(Debug, PartialEq, Eq)]
8+
pub struct TransferCommand(pub String);
9+
10+
#[derive(Debug)]
11+
pub enum ParseError {
12+
MissingRepo,
13+
}
14+
15+
impl std::error::Error for ParseError {}
16+
17+
impl fmt::Display for ParseError {
18+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
19+
match self {
20+
ParseError::MissingRepo => write!(f, "missing repository name"),
21+
}
22+
}
23+
}
24+
25+
impl TransferCommand {
26+
pub fn parse<'a>(input: &mut Tokenizer<'a>) -> Result<Option<Self>, Error<'a>> {
27+
if !matches!(input.peek_token()?, Some(Token::Word("transfer"))) {
28+
return Ok(None);
29+
}
30+
input.next_token()?;
31+
let repo = if let Some(Token::Word(repo)) = input.next_token()? {
32+
repo.to_owned()
33+
} else {
34+
return Err(input.error(ParseError::MissingRepo));
35+
};
36+
Ok(Some(TransferCommand(repo)))
37+
}
38+
}

src/config.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub(crate) struct Config {
3939
// We want this validation to run even without the entry in the config file
4040
#[serde(default = "ValidateConfig::default")]
4141
pub(crate) validate_config: Option<ValidateConfig>,
42+
pub(crate) transfer: Option<TransferConfig>,
4243
}
4344

4445
#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
@@ -317,6 +318,11 @@ pub(crate) struct GitHubReleasesConfig {
317318
pub(crate) changelog_branch: String,
318319
}
319320

321+
#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
322+
#[serde(rename_all = "kebab-case")]
323+
#[serde(deny_unknown_fields)]
324+
pub(crate) struct TransferConfig {}
325+
320326
fn get_cached_config(repo: &str) -> Option<Result<Arc<Config>, ConfigurationError>> {
321327
let cache = CONFIG_CACHE.read().unwrap();
322328
cache.get(repo).and_then(|(config, fetch_time)| {
@@ -463,6 +469,7 @@ mod tests {
463469
mentions: None,
464470
no_merges: None,
465471
validate_config: Some(ValidateConfig {}),
472+
transfer: None,
466473
}
467474
);
468475
}

src/handlers.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ mod review_submitted;
4646
mod rfc_helper;
4747
pub mod rustc_commits;
4848
mod shortcut;
49+
mod transfer;
4950
pub mod types_planning_updates;
5051
mod validate_config;
5152

@@ -290,6 +291,7 @@ command_handlers! {
290291
shortcut: Shortcut,
291292
close: Close,
292293
note: Note,
294+
transfer: Transfer,
293295
}
294296

295297
pub struct Context {

src/handlers/transfer.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//! Handles the `@rustbot transfer reponame` command to transfer an issue to
2+
//! another repository.
3+
4+
use crate::{config::TransferConfig, github::Event, handlers::Context};
5+
use parser::command::transfer::TransferCommand;
6+
7+
pub(super) async fn handle_command(
8+
ctx: &Context,
9+
_config: &TransferConfig,
10+
event: &Event,
11+
input: TransferCommand,
12+
) -> anyhow::Result<()> {
13+
let issue = event.issue().unwrap();
14+
if issue.is_pr() {
15+
issue
16+
.post_comment(&ctx.github, "Only issues can be transferred.")
17+
.await?;
18+
return Ok(());
19+
}
20+
if !event
21+
.user()
22+
.is_team_member(&ctx.github)
23+
.await
24+
.ok()
25+
.unwrap_or(false)
26+
{
27+
issue
28+
.post_comment(
29+
&ctx.github,
30+
"Only team members may use the `transfer` command.",
31+
)
32+
.await?;
33+
return Ok(());
34+
}
35+
36+
let repo = input.0;
37+
let repo = repo.strip_prefix("rust-lang/").unwrap_or(&repo);
38+
if repo.contains('/') {
39+
issue
40+
.post_comment(&ctx.github, "Cross-organization transfers are not allowed.")
41+
.await?;
42+
return Ok(());
43+
}
44+
45+
if let Err(e) = issue.transfer(&ctx.github, "rust-lang", &repo).await {
46+
issue
47+
.post_comment(&ctx.github, &format!("Failed to transfer issue:\n{e:?}"))
48+
.await?;
49+
return Ok(());
50+
}
51+
52+
Ok(())
53+
}

0 commit comments

Comments
 (0)