feat: Don't advance next branch if the target commit message is invalid

Closes kemitix/git-next#33
This commit is contained in:
Paul Campbell 2024-04-12 16:21:45 +01:00
parent cedaf16acf
commit 4e6a306a72
4 changed files with 82 additions and 15 deletions

View file

@ -33,6 +33,9 @@ toml = "0.8"
# Secrets and Password # Secrets and Password
secrecy = "0.8" secrecy = "0.8"
# Conventional Commit check
git-conventional = "0.12"
# error handling # error handling
terrors = "0.3" terrors = "0.3"

View file

@ -124,8 +124,10 @@ pub async fn advance_next(
warn!("No commits to advance next to"); warn!("No commits to advance next to");
return; return;
}; };
// TODO: (#33) get commit message for 'commit' if let Some(problem) = validate_commit_message(commit.message()) {
// TODO: (#33) verify commit message is valid warn!("Can't advance next to commit '{}': {}", commit, problem);
return;
}
match reset( match reset(
&repo_config.branches().next(), &repo_config.branches().next(),
commit, commit,
@ -144,6 +146,24 @@ pub async fn advance_next(
}; };
} }
#[tracing::instrument]
fn validate_commit_message(message: &forge::Message) -> Option<String> {
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( fn find_next_commit_on_dev(
next: forge::Commit, next: forge::Commit,
dev_commit_history: Vec<forge::Commit>, dev_commit_history: Vec<forge::Commit>,
@ -184,7 +204,7 @@ pub async fn advance_main(
pub struct GitRef(pub String); pub struct GitRef(pub String);
impl From<forge::Commit> for GitRef { impl From<forge::Commit> for GitRef {
fn from(value: forge::Commit) -> Self { fn from(value: forge::Commit) -> Self {
Self(value.sha) Self(value.sha().to_string())
} }
} }
impl From<BranchName> for GitRef { impl From<BranchName> for GitRef {
@ -255,14 +275,15 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_find_next_commit_on_dev() { 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![ let dev_commit_history = vec![
forge::Commit::new("dev"), forge::Commit::new("dev", "future"),
forge::Commit::new("dev-next"), expected.clone(),
forge::Commit::new("current-next"), next.clone(),
forge::Commit::new("current-main"), forge::Commit::new("current-main", "history"),
]; ];
let next_commit = find_next_commit_on_dev(next, dev_commit_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));
} }
} }

View file

@ -79,8 +79,7 @@ async fn get_commit_history(
.response_body() .response_body()
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(|commit| commit.sha) .map(forge::Commit::from)
.map(|sha| forge::Commit { sha })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let found = find_commits.is_empty() let found = find_commits.is_empty()
@ -103,6 +102,17 @@ async fn get_commit_history(
#[derive(Debug, Default, serde::Deserialize)] #[derive(Debug, Default, serde::Deserialize)]
struct Commit { struct Commit {
sha: String, sha: String,
commit: RepoCommit,
}
#[allow(dead_code)]
#[derive(Debug, Default, serde::Deserialize)]
struct RepoCommit {
message: String,
}
impl From<Commit> for forge::Commit {
fn from(value: Commit) -> Self {
Self::new(&value.sha, &value.commit.message)
}
} }
pub async fn get_commit_status( pub async fn get_commit_status(

View file

@ -14,18 +14,51 @@ pub struct CommitHistories {
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Commit { pub struct Commit {
pub sha: String, sha: Sha,
message: Message,
} }
impl Commit { impl Commit {
#[cfg(test)] pub fn new(sha: &str, message: &str) -> Self {
pub fn new(sha: &str) -> Self {
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 { impl Display for Commit {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "{}", self.sha) 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)
}
}