From 4e6a306a723a24ebc7f385daed90d688e2a092ae Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 12 Apr 2024 16:21:45 +0100 Subject: [PATCH] feat: Don't advance next branch if the target commit message is invalid Closes kemitix/git-next#33 --- Cargo.toml | 3 +++ src/server/actors/repo/branch.rs | 39 +++++++++++++++++++++++------- src/server/forge/forgejo/mod.rs | 14 +++++++++-- src/server/forge/mod.rs | 41 ++++++++++++++++++++++++++++---- 4 files changed, 82 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 25aa3ebf..b1601108 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,9 @@ toml = "0.8" # Secrets and Password secrecy = "0.8" +# Conventional Commit check +git-conventional = "0.12" + # error handling terrors = "0.3" diff --git a/src/server/actors/repo/branch.rs b/src/server/actors/repo/branch.rs index 484b871e..cf7507b9 100644 --- a/src/server/actors/repo/branch.rs +++ b/src/server/actors/repo/branch.rs @@ -124,8 +124,10 @@ pub async fn advance_next( warn!("No commits to advance next to"); return; }; - // TODO: (#33) get commit message for 'commit' - // TODO: (#33) verify commit message is valid + if let Some(problem) = validate_commit_message(commit.message()) { + warn!("Can't advance next to commit '{}': {}", commit, problem); + return; + } match reset( &repo_config.branches().next(), commit, @@ -144,6 +146,24 @@ pub async fn advance_next( }; } +#[tracing::instrument] +fn validate_commit_message(message: &forge::Message) -> Option { + let message = &message.to_string(); + if message.to_ascii_lowercase().starts_with("wip") { + return Some("Is Work-In-Progress".to_string()); + } + match git_conventional::Commit::parse(message) { + Ok(commit) => { + info!(?commit, "Pass"); + None + } + Err(err) => { + warn!(?err, "Fail"); + Some(err.kind().to_string()) + } + } +} + fn find_next_commit_on_dev( next: forge::Commit, dev_commit_history: Vec, @@ -184,7 +204,7 @@ pub async fn advance_main( pub struct GitRef(pub String); impl From for GitRef { fn from(value: forge::Commit) -> Self { - Self(value.sha) + Self(value.sha().to_string()) } } impl From for GitRef { @@ -255,14 +275,15 @@ mod tests { #[actix_rt::test] async fn test_find_next_commit_on_dev() { - let next = forge::Commit::new("current-next"); + let next = forge::Commit::new("current-next", "foo"); + let expected = forge::Commit::new("dev-next", "next-should-go-here"); let dev_commit_history = vec![ - forge::Commit::new("dev"), - forge::Commit::new("dev-next"), - forge::Commit::new("current-next"), - forge::Commit::new("current-main"), + forge::Commit::new("dev", "future"), + expected.clone(), + next.clone(), + forge::Commit::new("current-main", "history"), ]; let next_commit = find_next_commit_on_dev(next, dev_commit_history); - assert_eq!(next_commit, Some(forge::Commit::new("dev-next"))); + assert_eq!(next_commit, Some(expected)); } } diff --git a/src/server/forge/forgejo/mod.rs b/src/server/forge/forgejo/mod.rs index b1cf897a..3c197bf7 100644 --- a/src/server/forge/forgejo/mod.rs +++ b/src/server/forge/forgejo/mod.rs @@ -79,8 +79,7 @@ async fn get_commit_history( .response_body() .unwrap_or_default() .into_iter() - .map(|commit| commit.sha) - .map(|sha| forge::Commit { sha }) + .map(forge::Commit::from) .collect::>(); let found = find_commits.is_empty() @@ -103,6 +102,17 @@ async fn get_commit_history( #[derive(Debug, Default, serde::Deserialize)] struct Commit { sha: String, + commit: RepoCommit, +} +#[allow(dead_code)] +#[derive(Debug, Default, serde::Deserialize)] +struct RepoCommit { + message: String, +} +impl From for forge::Commit { + fn from(value: Commit) -> Self { + Self::new(&value.sha, &value.commit.message) + } } pub async fn get_commit_status( diff --git a/src/server/forge/mod.rs b/src/server/forge/mod.rs index 0826b7a5..677d7214 100644 --- a/src/server/forge/mod.rs +++ b/src/server/forge/mod.rs @@ -14,18 +14,51 @@ pub struct CommitHistories { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Commit { - pub sha: String, + sha: Sha, + message: Message, } impl Commit { - #[cfg(test)] - pub fn new(sha: &str) -> Self { + pub fn new(sha: &str, message: &str) -> Self { Self { - sha: sha.to_string(), + sha: Sha::new(sha.to_string()), + message: Message::new(message.to_string()), } } + pub const fn sha(&self) -> &Sha { + &self.sha + } + pub const fn message(&self) -> &Message { + &self.message + } } impl Display for Commit { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { write!(f, "{}", self.sha) } } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Sha(String); +impl Sha { + pub const fn new(value: String) -> Self { + Self(value) + } +} +impl Display for Sha { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Message(String); +impl Message { + pub const fn new(value: String) -> Self { + Self(value) + } +} +impl Display for Message { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +}