Skip to content

Commit f054b94

Browse files
committed
Add an issue transfer command
1 parent 75f40b6 commit f054b94

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
@@ -44,6 +44,7 @@ pub(crate) struct Config {
4444
#[serde(default = "ValidateConfig::default")]
4545
pub(crate) validate_config: Option<ValidateConfig>,
4646
pub(crate) pr_tracking: Option<ReviewPrefsConfig>,
47+
pub(crate) transfer: Option<TransferConfig>,
4748
}
4849

4950
#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
@@ -344,6 +345,11 @@ pub(crate) struct ReviewPrefsConfig {
344345
_empty: (),
345346
}
346347

348+
#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
349+
#[serde(rename_all = "kebab-case")]
350+
#[serde(deny_unknown_fields)]
351+
pub(crate) struct TransferConfig {}
352+
347353
fn get_cached_config(repo: &str) -> Option<Result<Arc<Config>, ConfigurationError>> {
348354
let cache = CONFIG_CACHE.read().unwrap();
349355
cache.get(repo).and_then(|(config, fetch_time)| {
@@ -520,6 +526,7 @@ mod tests {
520526
no_merges: None,
521527
validate_config: Some(ValidateConfig {}),
522528
pr_tracking: None,
529+
transfer: None,
523530
}
524531
);
525532
}

src/handlers.rs

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

@@ -292,6 +293,7 @@ command_handlers! {
292293
shortcut: Shortcut,
293294
close: Close,
294295
note: Note,
296+
transfer: Transfer,
295297
}
296298

297299
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)