Skip to content

feat: arbitrary try build #85

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/bors/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub enum BorsCommand {
Try {
/// Parent commit which should be used as the merge base.
parent: Option<Parent>,
/// The CI workflow to run.
jobs: Vec<String>,
},
/// Cancel a try build.
TryCancel,
Expand Down
103 changes: 92 additions & 11 deletions src/bors/command/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,15 @@ fn parser_try<'a>(command: &'a str, parts: &[CommandPart<'a>]) -> ParseResult<'a
}

let mut parent = None;
let mut jobs = Vec::new();

for part in parts {
match part {
CommandPart::Bare(key) => {
return Some(Err(CommandParseError::UnknownArg(key)));
}
CommandPart::KeyValue { key, value } => {
if *key == "parent" {
CommandPart::KeyValue { key, value } => match *key {
"parent" => {
parent = if *value == "last" {
Some(Parent::Last)
} else {
Expand All @@ -160,18 +161,35 @@ fn parser_try<'a>(command: &'a str, parts: &[CommandPart<'a>]) -> ParseResult<'a
}
}
}
} else {
}
"jobs" => {
let raw_jobs: Vec<_> = value.split(',').map(|s| s.to_string()).collect();
if raw_jobs.is_empty() {
return Some(Err(CommandParseError::ValidationError(
"Try jobs must not be empty".to_string(),
)));
}

// rust ci currently allows specifying 10 jobs max
if raw_jobs.len() > 10 {
return Some(Err(CommandParseError::ValidationError(
"Try jobs must not have more than 10 jobs".to_string(),
)));
}
jobs = raw_jobs;
}
_ => {
return Some(Err(CommandParseError::UnknownArg(key)));
}
}
},
}
}
Some(Ok(BorsCommand::Try { parent }))
Some(Ok(BorsCommand::Try { parent, jobs }))
}

/// Parses "@bors try cancel".
fn parser_try_cancel<'a>(command: &'a str, parts: &[CommandPart<'a>]) -> ParseResult<'a> {
if command == "try" && parts.get(0) == Some(&CommandPart::Bare("cancel")) {
if command == "try" && parts.first() == Some(&CommandPart::Bare("cancel")) {
Some(Ok(BorsCommand::TryCancel))
} else {
None
Expand Down Expand Up @@ -262,14 +280,28 @@ line two
"#,
);
assert_eq!(cmds.len(), 1);
assert!(matches!(cmds[0], Ok(BorsCommand::Try { parent: None })));
insta::assert_debug_snapshot!(cmds[0], @r###"
Ok(
Try {
parent: None,
jobs: [],
},
)
"###);
}

#[test]
fn parse_try() {
let cmds = parse_commands("@bors try");
assert_eq!(cmds.len(), 1);
assert!(matches!(cmds[0], Ok(BorsCommand::Try { parent: None })));
insta::assert_debug_snapshot!(cmds[0], @r###"
Ok(
Try {
parent: None,
jobs: [],
},
)
"###);
}

#[test]
Expand All @@ -281,7 +313,8 @@ line two
Ok(BorsCommand::Try {
parent: Some(Parent::CommitSha(CommitSha(
"ea9c1b050cc8b420c2c211d2177811e564a4dc60".to_string()
)))
))),
jobs: Vec::new()
})
);
}
Expand All @@ -293,7 +326,8 @@ line two
assert_eq!(
cmds[0],
Ok(BorsCommand::Try {
parent: Some(Parent::Last)
parent: Some(Parent::Last),
jobs: Vec::new()
})
);
}
Expand All @@ -311,6 +345,46 @@ line two
"###);
}

#[test]
fn parse_try_jobs() {
let cmds = parse_commands("@bors try jobs=ci,lint");
assert_eq!(cmds.len(), 1);
assert_eq!(
cmds[0],
Ok(BorsCommand::Try {
parent: None,
jobs: vec!["ci".to_string(), "lint".to_string()]
})
);
}

#[test]
fn parse_try_jobs_empty() {
let cmds = parse_commands("@bors try jobs=");
assert_eq!(cmds.len(), 1);
insta::assert_debug_snapshot!(cmds[0], @r###"
Err(
MissingArgValue {
arg: "jobs",
},
)
"###);
}

#[test]
fn parse_try_jobs_too_many() {
let cmds =
parse_commands("@bors try jobs=ci,lint,foo,bar,baz,qux,quux,corge,grault,garply,waldo");
assert_eq!(cmds.len(), 1);
insta::assert_debug_snapshot!(cmds[0], @r###"
Err(
ValidationError(
"Try jobs must not have more than 10 jobs",
),
)
"###);
}

#[test]
fn parse_try_unknown_arg() {
let cmds = parse_commands("@bors try a");
Expand All @@ -333,7 +407,14 @@ line two
"#,
);
assert_eq!(cmds.len(), 1);
assert!(matches!(cmds[0], Ok(BorsCommand::Try { parent: None })));
insta::assert_debug_snapshot!(cmds[0], @r###"
Ok(
Try {
parent: None,
jobs: [],
},
)
"###)
}

#[test]
Expand Down
15 changes: 11 additions & 4 deletions src/bors/handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,18 @@ async fn handle_comment<Client: RepositoryClient>(
let span = tracing::info_span!("Ping");
command_ping(repo, &pull_request).instrument(span).await
}
BorsCommand::Try { parent } => {
BorsCommand::Try { parent, jobs } => {
let span = tracing::info_span!("Try");
command_try_build(repo, database, &pull_request, &comment.author, parent)
.instrument(span)
.await
command_try_build(
repo,
database,
&pull_request,
&comment.author,
parent,
jobs,
)
.instrument(span)
.await
}
BorsCommand::TryCancel => {
let span = tracing::info_span!("Cancel try");
Expand Down
15 changes: 11 additions & 4 deletions src/bors/handlers/trybuild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub(super) async fn command_try_build<Client: RepositoryClient>(
pr: &PullRequest,
author: &GithubUser,
parent: Option<Parent>,
jobs: Vec<String>,
) -> anyhow::Result<()> {
let repo = repo.as_ref();
if !check_try_permissions(repo, pr, author).await? {
Expand Down Expand Up @@ -94,7 +95,7 @@ pub(super) async fn command_try_build<Client: RepositoryClient>(
.merge_branches(
TRY_MERGE_BRANCH_NAME,
&pr.head.sha,
&auto_merge_commit_message(pr, "<try>"),
&auto_merge_commit_message(pr, "<try>", jobs),
)
.await
{
Expand Down Expand Up @@ -232,17 +233,23 @@ fn get_pending_build(pr: PullRequestModel) -> Option<BuildModel> {
.and_then(|b| (b.status == BuildStatus::Pending).then_some(b))
}

fn auto_merge_commit_message(pr: &PullRequest, reviewer: &str) -> String {
fn auto_merge_commit_message(pr: &PullRequest, reviewer: &str, jobs: Vec<String>) -> String {
let pr_number = pr.number;
format!(
let mut message = format!(
r#"Auto merge of #{pr_number} - {pr_label}, r={reviewer}
{pr_title}

{pr_message}"#,
pr_label = pr.head_label,
pr_title = pr.title,
pr_message = pr.message
)
);

// if jobs is empty, try-job won't be added to the message
for job in jobs {
message.push_str(&format!("\ntry-job: {}", job));
}
message
}

fn merge_conflict_message(branch: &str) -> String {
Expand Down
Loading