Skip to content

Commit

Permalink
Auto merge of #21 - troy/auto-try, r=<dry>
Browse files Browse the repository at this point in the history
add auto-try feature
Adds an ability to specify an auto-try feature

Requested-by: ScuffleCloud <[email protected]>
  • Loading branch information
scuffle-brawl[bot] authored Dec 26, 2024
2 parents 3b1e022 + 5d451ff commit 1cfecde
Show file tree
Hide file tree
Showing 17 changed files with 295 additions and 28 deletions.
1 change: 1 addition & 0 deletions migrations/2024-12-26-044555_auto_try/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE github_pr DROP COLUMN auto_try;
1 change: 1 addition & 0 deletions migrations/2024-12-26-044555_auto_try/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE github_pr ADD COLUMN auto_try BOOLEAN NOT NULL DEFAULT FALSE;
18 changes: 9 additions & 9 deletions migrations/schema.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--- migrations/schema.unpatched.rs 2024-12-25 03:01:31.500330121 +0000
+++ server/src/database/schema.rs 2024-12-25 03:01:20.600359108 +0000
--- migrations/schema.unpatched.rs 2024-12-26 04:47:27.914819445 +0000
+++ server/src/database/schema.rs 2024-12-26 04:47:19.166873340 +0000
@@ -1,38 +1,44 @@
+#![cfg_attr(coverage_nightly, coverage(off))]
// @generated automatically by Diesel CLI.
Expand Down Expand Up @@ -99,13 +99,13 @@
- added_labels -> Array<Nullable<Text>>,
+ /// (Manually changed from `Array<Nullable<Text>>` to `Array<Text>`)
+ added_labels -> Array<Text>,
}
}

diesel::table! {
/// Representation of the `health_check` table.
///
@@ -326,12 +332,13 @@
/// The `auto_try` column of the `github_pr` table.
///
/// Its SQL type is `Bool`.
///
/// (Automatically generated by Diesel.)
auto_try -> Bool,
@@ -332,12 +338,13 @@
updated_at -> Timestamptz,
}
}
Expand Down
6 changes: 6 additions & 0 deletions migrations/schema.unpatched.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,12 @@ diesel::table! {
///
/// (Automatically generated by Diesel.)
added_labels -> Array<Nullable<Text>>,
/// The `auto_try` column of the `github_pr` table.
///
/// Its SQL type is `Bool`.
///
/// (Automatically generated by Diesel.)
auto_try -> Bool,
}
}

Expand Down
67 changes: 67 additions & 0 deletions server/src/command/auto_try.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use diesel_async::{AsyncPgConnection, RunQueryDsl};

use super::BrawlCommandContext;
use crate::database::pr::Pr;
use crate::github::messages;
use crate::github::models::PullRequest;
use crate::github::repo::GitHubRepoClient;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AutoTryCommand {
pub force: bool,
pub disable: bool,
}

pub async fn handle<R: GitHubRepoClient>(
conn: &mut AsyncPgConnection,
context: BrawlCommandContext<'_, R>,
command: AutoTryCommand,
) -> anyhow::Result<()> {
let pr = context.repo.get_pull_request(context.pr_number).await?;
handle_with_pr(conn, pr, context, command).await
}

async fn handle_with_pr<R: GitHubRepoClient>(
conn: &mut AsyncPgConnection,
pr: PullRequest,
context: BrawlCommandContext<'_, R>,
command: AutoTryCommand,
) -> anyhow::Result<()> {
if !context.repo.config().enabled {
return Ok(());
}

if !context.repo.can_try(context.user.id).await? {
tracing::debug!("user does not have permission to do this");
return Ok(());
}

let db_pr = Pr::find(context.repo.id(), context.pr_number).get_result(conn).await?;

match (command.disable, db_pr.auto_try) {
(true, true) => {
db_pr.update().auto_try(false).build().query().execute(conn).await?;

context
.repo
.send_message(context.pr_number, &messages::auto_try_disabled())
.await?;
}
(false, false) => {
if !command.force && pr.head.repo.is_none_or(|r| r.id != context.repo.id()) {
context.repo.send_message(context.pr_number, &messages::error_no_body("This PR is not from this repository, so auto-try cannot be enabled. To bypass this check, use force `?brawl auto-try force`")).await?;
return Ok(());
}

db_pr.update().auto_try(true).build().query().execute(conn).await?;

context
.repo
.send_message(context.pr_number, &messages::auto_try_enabled())
.await?;
}
(_, _) => {}
}

Ok(())
}
4 changes: 4 additions & 0 deletions server/src/command/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,15 @@ mod tests {
sha: "head_sha".to_string(),
label: Some("head".to_string()),
ref_field: "head".to_string(),
repo: None,
user: None,
},
base: PrBranch {
sha: "base_sha".to_string(),
label: Some("base".to_string()),
ref_field: "base".to_string(),
repo: None,
user: None,
},
requested_reviewers: vec![
User {
Expand Down
65 changes: 65 additions & 0 deletions server/src/command/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use std::str::FromStr;

use auto_try::AutoTryCommand;
use diesel_async::AsyncPgConnection;
use dry_run::DryRunCommand;
use merge::MergeCommand;

use crate::github::models::User;
use crate::github::repo::GitHubRepoClient;

mod auto_try;
mod cancel;
mod dry_run;
mod merge;
Expand All @@ -20,6 +22,7 @@ pub enum BrawlCommand {
Retry,
Cancel,
Ping,
AutoTry(AutoTryCommand),
}

pub struct BrawlCommandContext<'a, R> {
Expand All @@ -40,6 +43,7 @@ impl BrawlCommand {
BrawlCommand::Retry => retry::handle(conn, context).await,
BrawlCommand::Cancel => cancel::handle(conn, context).await,
BrawlCommand::Ping => ping::handle(conn, context).await,
BrawlCommand::AutoTry(command) => auto_try::handle(conn, context, command).await,
}
}
}
Expand Down Expand Up @@ -110,6 +114,18 @@ impl FromStr for BrawlCommand {
}
"retry" => Ok(BrawlCommand::Retry),
"ping" => Ok(BrawlCommand::Ping),
"auto-try" => {
let mut force = false;
let mut disable = false;

match splits.next() {
Some("force") => force = true,
Some("disable") => disable = true,
_ => (),
}

Ok(BrawlCommand::AutoTry(AutoTryCommand { force, disable }))
}
command => {
tracing::debug!("invalid command: {}", command);
Err(BrawlCommandError::InvalidCommand(command.into()))
Expand Down Expand Up @@ -205,6 +221,55 @@ mod tests {
),
("<no command>", Err(BrawlCommandError::NoCommand)),
("@brawl @brawl merge", Err(BrawlCommandError::InvalidCommand("@brawl".into()))),
(
"@brawl auto-try",
Ok(BrawlCommand::AutoTry(AutoTryCommand {
force: false,
disable: false,
})),
),
(
"@brawl auto-try disable",
Ok(BrawlCommand::AutoTry(AutoTryCommand {
force: false,
disable: true,
})),
),
(
"@brawl auto-try force",
Ok(BrawlCommand::AutoTry(AutoTryCommand {
force: true,
disable: false,
})),
),
(
"@brawl auto-try something else",
Ok(BrawlCommand::AutoTry(AutoTryCommand {
force: false,
disable: false,
})),
),
(
"@brawl auto-try force something else",
Ok(BrawlCommand::AutoTry(AutoTryCommand {
force: true,
disable: false,
})),
),
(
"@brawl auto-try disable something else",
Ok(BrawlCommand::AutoTry(AutoTryCommand {
force: false,
disable: true,
})),
),
(
"@brawl auto-try disable force",
Ok(BrawlCommand::AutoTry(AutoTryCommand {
force: false,
disable: true,
})),
),
];

for (input, expected) in cases {
Expand Down
39 changes: 38 additions & 1 deletion server/src/database/pr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub struct Pr<'a> {
pub added_labels: Vec<Cow<'a, str>>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
pub auto_try: bool,
}

#[derive(AsChangeset, Identifiable, Clone, bon::Builder)]
Expand All @@ -53,6 +54,7 @@ pub struct UpdatePr<'a> {
pub merge_commit_sha: Option<Option<Cow<'a, str>>>,
pub target_branch: Option<Cow<'a, str>>,
pub latest_commit_sha: Option<Cow<'a, str>>,
pub auto_try: Option<bool>,
#[builder(default = chrono::Utc::now())]
pub updated_at: chrono::DateTime<chrono::Utc>,
}
Expand All @@ -67,6 +69,34 @@ impl UpdatePr<'_> {
}
}

impl<'a> UpdatePr<'a> {
pub fn update_pr(self, pr: &mut Pr<'a>) {
if self.needs_update() {
pr.updated_at = self.updated_at;
}

macro_rules! update_if_some {
($field:expr, $value:expr) => {
if let Some(value) = $value {
$field = value;
}
};
}

update_if_some!(pr.added_labels, self.added_labels);
update_if_some!(pr.title, self.title);
update_if_some!(pr.body, self.body);
update_if_some!(pr.merge_status, self.merge_status);
update_if_some!(pr.assigned_ids, self.assigned_ids);
update_if_some!(pr.status, self.status);
update_if_some!(pr.default_priority, self.default_priority);
update_if_some!(pr.merge_commit_sha, self.merge_commit_sha);
update_if_some!(pr.target_branch, self.target_branch);
update_if_some!(pr.latest_commit_sha, self.latest_commit_sha);
update_if_some!(pr.auto_try, self.auto_try);
}
}

fn pr_status(pr: &PullRequest) -> GithubPrStatus {
match pr.state {
Some(IssueState::Open) if matches!(pr.draft, Some(true)) => GithubPrStatus::Draft,
Expand Down Expand Up @@ -115,6 +145,7 @@ impl<'a> Pr<'a> {
added_labels: Vec::new(),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
auto_try: false,
}
}

Expand Down Expand Up @@ -148,6 +179,7 @@ impl<'a> Pr<'a> {
merge_commit_sha: Some(self.merge_commit_sha.as_deref().map(Cow::Borrowed)),
target_branch: Some(Cow::Borrowed(self.target_branch.as_ref())),
latest_commit_sha: Some(Cow::Borrowed(self.latest_commit_sha.as_ref())),
auto_try: Some(self.auto_try),
updated_at: self.updated_at,
})
.returning(Pr::as_select())
Expand All @@ -162,7 +194,7 @@ impl<'a> Pr<'a> {
UpdatePr::builder(self.github_repo_id, self.github_pr_number)
}

pub fn update_from(&'a self, new: &'a PullRequest) -> UpdatePr<'a> {
pub fn update_from(&self, new: &'a PullRequest) -> UpdatePr<'a> {
let title = Cow::Borrowed(new.title.as_str());
let body = Cow::Borrowed(new.body.as_str());
let merge_status = pr_merge_status(new);
Expand Down Expand Up @@ -272,6 +304,7 @@ mod tests {
merge_status: GithubPrMergeStatus::NotReady,
status: GithubPrStatus::Open,
merge_commit_sha: None,
auto_try: false,
}),
expected: @r#"
INSERT INTO
Expand Down Expand Up @@ -376,6 +409,7 @@ mod tests {
merge_status: GithubPrMergeStatus::NotReady,
status: GithubPrStatus::Open,
merge_commit_sha: None,
auto_try: false,
}),
expected: @r#"
INSERT INTO
Expand Down Expand Up @@ -536,6 +570,7 @@ mod tests {
merge_commit_sha: None,
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
auto_try: false,
}
.insert()
.execute(&mut conn)
Expand Down Expand Up @@ -567,6 +602,7 @@ mod tests {
merge_commit_sha: None,
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
auto_try: false,
}
.insert()
.execute(&mut conn)
Expand Down Expand Up @@ -606,6 +642,7 @@ mod tests {
merge_commit_sha: None,
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
auto_try: false,
}
.insert()
.execute(&mut conn)
Expand Down
6 changes: 6 additions & 0 deletions server/src/database/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@ diesel::table! {
///
/// (Manually changed from `Array<Nullable<Text>>` to `Array<Text>`)
added_labels -> Array<Text>,
/// The `auto_try` column of the `github_pr` table.
///
/// Its SQL type is `Bool`.
///
/// (Automatically generated by Diesel.)
auto_try -> Bool,
}
}

Expand Down
3 changes: 3 additions & 0 deletions server/src/github/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ pub struct GitHubBrawlLabelsConfig {
/// The label to attach to PRs when they fail to try
#[serde(skip_serializing_if = "Vec::is_empty", deserialize_with = "string_or_vec")]
pub on_try_failure: Vec<String>,
/// The label attached to PRs when auto-try is enabled
#[serde(skip_serializing_if = "Vec::is_empty", deserialize_with = "string_or_vec")]
pub auto_try_enabled: Vec<String>,
}

fn string_or_vec<'de, D: Deserializer<'de>>(s: D) -> Result<Vec<String>, D::Error> {
Expand Down
Loading

0 comments on commit 1cfecde

Please sign in to comment.