Skip to content
Open
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion src/bors/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,5 +174,20 @@ pub enum BorsCommand {
/// Cancel an auto build currently running on a given PR (without removing it from the queue).
Cancel,
/// Squash all commits of a pull request into a single commit.
Squash { commit_message: SquashCommitMessage },
Squash {
/// Squash message for the commit
commit_message: SquashCommitMessage,
},
SquashApprove {
/// Squash message for the commit
commit_message: SquashCommitMessage,
/// Who is approving the commit.
approver: Approver,
/// Priority of the commit.
priority: Option<Priority>,
/// Rollup status of the commit.
rollup: Option<RollupMode>,
/// Optional note attached at the end of the command.
note: Option<String>,
},
}
186 changes: 185 additions & 1 deletion src/bors/command/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub enum CommandParseError {
}

/// Part of a command, either a bare string like `try` or a key value like `parent=<sha>`.
#[derive(PartialEq, Copy, Clone)]
#[derive(Debug, PartialEq, Copy, Clone)]
enum CommandPart<'a> {
Bare(&'a str),
KeyValue { key: &'a str, value: &'a str },
Expand Down Expand Up @@ -267,6 +267,12 @@ fn parse_parts(input: &str) -> Result<Vec<CommandPart<'_>>, CommandParseError> {
/// - "@bors r+ [p=<priority>] [rollup=<never|iffy|maybe|always>] [note=<note>]"
/// - "@bors r=<user> [p=<priority>] [rollup=<never|iffy|maybe|always>] [note=<note>]"
fn parser_approval(command: &CommandPart<'_>, parts: &[CommandPart<'_>]) -> ParseResult {
if let CommandPart::Bare("r+") = command
&& !parts.is_empty()
&& parts.contains(&CommandPart::Bare("squash"))
{
return parser_squash_approve(command, parts);
}
let approver = match command {
CommandPart::Bare("r+") => Approver::Myself,
CommandPart::KeyValue { key: "r", value } => {
Expand Down Expand Up @@ -670,6 +676,53 @@ fn parser_squash(command: &CommandPart<'_>, parts: &[CommandPart<'_>]) -> ParseR
}
}

/// Parses `@bors r+ squash` command.
/// Supports specifying a commit message via `@bors r+ squash [msg|message]="message"`.
fn parser_squash_approve(_: &CommandPart<'_>, parts: &[CommandPart<'_>]) -> ParseResult {

@Kobzol Kobzol Sep 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should still allow parsing priority, note, rollup mode, etc. I'd suggest modifying the approve parser, rather than creating a new one, and remembering Option<SquashData> while parsing it. Then if the data is Some after parsing is finished, return SquashApprove.

View changes since the review

match parts {
&[CommandPart::Bare("squash")] => Some(Ok(BorsCommand::SquashApprove {
commit_message: SquashCommitMessage::AutoGenerate,
approver: Approver::Myself,
priority: None,
rollup: None,
note: None,
})),
&[
CommandPart::Bare("squash"),
CommandPart::KeyValue {
key: "msg" | "message",
value,
},
..,
] => {
if value == "description" {
Some(Ok(BorsCommand::SquashApprove {
commit_message: SquashCommitMessage::PullRequestDescription,
approver: Approver::Myself,
priority: None,
rollup: None,
note: None,
}))
} else {
Some(Ok(BorsCommand::SquashApprove {
commit_message: SquashCommitMessage::Explicit(value.to_owned()),
approver: Approver::Myself,
priority: None,
rollup: None,
note: None,
}))
}
}
[part, ..] => Some(Err(CommandParseError::UnknownArg {
arg: part.as_key().to_owned(),
did_you_mean: "r+ squash [msg|message=\"<commit-msg>\"|description]".to_string(),
})),
part => Some(Err(CommandParseError::MissingArgValue {
arg: format!("{:#?}", part),
})),
}
}

#[cfg(test)]
mod tests {
use crate::bors::command::BorsCommand;
Expand Down Expand Up @@ -2099,6 +2152,137 @@ for the crater",
"#);
}

#[test]
fn parse_squash_approve() {
let cmds = parse_commands("@bors r+ squash");
insta::assert_debug_snapshot!(cmds, @"
[
Ok(
SquashApprove {
commit_message: AutoGenerate,
approver: Myself,
priority: None,
rollup: None,
note: None,
},
),
]
");
}

#[test]
fn parse_squash_approve_msg() {
let cmds = parse_commands("@bors r+ squash msg=foo");
insta::assert_debug_snapshot!(cmds, @r#"
[
Ok(
SquashApprove {
commit_message: Explicit(
"foo",
),
approver: Myself,
priority: None,
rollup: None,
note: None,
},
),
]
"#);
}

#[test]
fn parse_squash_approve_message() {
let cmds = parse_commands("@bors r+ squash message=foo");
insta::assert_debug_snapshot!(cmds, @r#"
[
Ok(
SquashApprove {
commit_message: Explicit(
"foo",
),
approver: Myself,
priority: None,
rollup: None,
note: None,
},
),
]
"#);
}

#[test]
fn parse_squash_approve_message_quoted() {
let cmds = parse_commands(r#"@bors r+ squash message="foo bar baz""#);
insta::assert_debug_snapshot!(cmds, @r#"
[
Ok(
SquashApprove {
commit_message: Explicit(
"foo bar baz",
),
approver: Myself,
priority: None,
rollup: None,
note: None,
},
),
]
"#);
}

#[test]
fn parse_squash_approve_msg_description() {
let cmds = parse_commands("@bors r+ squash msg=description");
insta::assert_debug_snapshot!(cmds, @"
[
Ok(
SquashApprove {
commit_message: PullRequestDescription,
approver: Myself,
priority: None,
rollup: None,
note: None,
},
),
]
");
}

#[test]
fn parse_squash_approve_unknown_arg() {
let cmds = parse_commands("@bors r+ squash commit=foo");
insta::assert_debug_snapshot!(cmds, @r#"
[
Err(
UnknownArg {
arg: "squash",
did_you_mean: "r+ squash [msg|message=\"<commit-msg>\"|description]",
},
),
]
"#);
}

#[test]
fn parse_squash_approve_extra_args() {
let cmds = parse_commands("@bors r+ squash message=foo baz");
insta::assert_debug_snapshot!(cmds, @r#"
[
Ok(
SquashApprove {
commit_message: Explicit(
"foo",
),
approver: Myself,
priority: None,
rollup: None,
note: None,
},
),
]
"#);
}

#[test]
fn parse_in_html_command() {
let cmds = parse_commands(
Expand Down
3 changes: 3 additions & 0 deletions src/bors/handlers/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ mod tests {
- `squash [msg|message=<commit-message>|description]`: Squash the commits of a PR into a single commit.
- Optionally, you can specify a `<commit-message>` for the created commit. If not specified, the commit messages of all squashed commits will be combined.
- If you specify `msg=description`, then the PR body will be used as the squashed commit message.
- `r+ squash [msg|message=<commit-message>|description]`: Squash the commits of a PR into a single commit, then approve on your behalf.
- Optionally, you can specify a `<commit-message>` for the created commit. If not specified, the commit messages of all squashed commits will be combined.
- If you specify `msg=description`, then the PR body will be used as the squashed commit message.
- `info`: Get information about the current PR

## Repository management
Expand Down
Loading
Loading