diff --git a/crates/cuddle-please-commands/src/command.rs b/crates/cuddle-please-commands/src/command.rs index 5b5c3bb..31c68c7 100644 --- a/crates/cuddle-please-commands/src/command.rs +++ b/crates/cuddle-please-commands/src/command.rs @@ -94,7 +94,7 @@ impl Command { match &self.commands { Some(Commands::Release {}) => { - ReleaseCommandHandler::new(config, git_client, gitea_client) + ReleaseCommandHandler::new(self.ui, config, git_client, gitea_client) .execute(self.global.dry_run)?; } Some(Commands::Config { command }) => { diff --git a/crates/cuddle-please-commands/src/release_command.rs b/crates/cuddle-please-commands/src/release_command.rs index d694c97..82c949e 100644 --- a/crates/cuddle-please-commands/src/release_command.rs +++ b/crates/cuddle-please-commands/src/release_command.rs @@ -4,11 +4,12 @@ use ::semver::Version; use anyhow::Context; use cuddle_please_misc::{ - changelog_parser, get_most_significant_version, ChangeLogBuilder, DynRemoteGitClient, - NextVersion, VcsClient, + changelog_parser, get_most_significant_version, ChangeLogBuilder, Commit, DynRemoteGitClient, + DynUi, NextVersion, Tag, VcsClient, }; pub struct ReleaseCommandHandler { + ui: DynUi, config: PleaseConfig, git_client: VcsClient, gitea_client: DynRemoteGitClient, @@ -16,11 +17,13 @@ pub struct ReleaseCommandHandler { impl ReleaseCommandHandler { pub fn new( + ui: DynUi, config: PleaseConfig, git_client: VcsClient, gitea_client: DynRemoteGitClient, ) -> Self { Self { + ui, config, git_client, gitea_client, @@ -34,87 +37,90 @@ impl ReleaseCommandHandler { let branch = self.config.get_branch(); let source = self.config.get_source(); - // 2. Parse the cuddle.please.yaml let cuddle.please.yaml take precedence - // 2a. if not existing use default. - // 2b. if not in a git repo abort. (unless --no-vcs is turned added) + self.check_git_remote_connection(owner, repository)?; - // 3. Create gitea client and do a health check - self.gitea_client - .connect(owner, repository) - .context("failed to connect to gitea repository")?; - // 4. Fetch git tags for the current repository - let tags = self.gitea_client.get_tags(owner, repository)?; + let significant_tag = self.get_most_significant_tag(owner, repository)?; - let significant_tag = get_most_significant_version(tags.iter().collect()); + let commits = + self.fetch_commits_since_last_tag(owner, repository, &significant_tag, branch)?; + let current_version = get_current_version(significant_tag); - // 5. Fetch git commits since last git tag - let commits = self.gitea_client.get_commits_since( - owner, - repository, - significant_tag.map(|st| st.commit.sha.as_str()), - branch, - )?; - - // 7. Create a versioning client - let current_version = significant_tag - .map(|st| Version::try_from(st).unwrap()) - .unwrap_or(Version::new(0, 1, 0)); - - // 8. Parse conventional commits and determine next version - - let commit_strs = commits - .iter() - .map(|c| c.commit.message.as_str()) - .collect::>(); - - if commit_strs.is_empty() { - tracing::info!("no commits to base release on"); + let conventional_commit_results = parse_conventional_commits(current_version, commits)?; + if conventional_commit_results.is_none() { + tracing::debug!("found no new commits, aborting early"); + self.ui + .write_str_ln("no new commits found, no release required"); return Ok(()); } + let (commit_strs, next_version) = conventional_commit_results.unwrap(); - let next_version = current_version.next(&commit_strs); + let (changelog_placement, changelog, changelog_last_changes) = + compose_changelog(&commit_strs, &next_version, source)?; - // Compose changelog - let builder = ChangeLogBuilder::new(&commit_strs, next_version.to_string()).build(); - - let changelog_placement = source.join("CHANGELOG.md"); - - let changelog = match std::fs::read_to_string(&changelog_placement).ok() { - Some(existing_changelog) => builder.prepend(existing_changelog)?, - None => builder.generate()?, - }; - - let changelog_last_changes = changelog_parser::last_changes(&changelog)?; - - // 9b. check for release commit and release, if release exists continue - // 10b. create release if let Some(first_commit) = commit_strs.first() { if first_commit.contains("chore(release): ") { - if !dry_run { - self.gitea_client.create_release( - owner, - repository, - &next_version.to_string(), - &changelog_last_changes.unwrap(), - !next_version.pre.is_empty(), - )?; - } else { - tracing::debug!("creating release (dry_run)"); - } + self.create_release( + dry_run, + owner, + repository, + &next_version, + changelog_last_changes, + )?; return Ok(()); } } - // 9a. Create / Update Pr - // Create or update branch + self.create_pull_request( + changelog_placement, + changelog, + next_version, + dry_run, + owner, + repository, + changelog_last_changes, + branch, + )?; + + Ok(()) + } + + fn create_release( + &self, + dry_run: bool, + owner: &str, + repository: &str, + next_version: &Version, + changelog_last_changes: Option, + ) -> Result<(), anyhow::Error> { + Ok(if !dry_run { + self.gitea_client.create_release( + owner, + repository, + &next_version.to_string(), + &changelog_last_changes.unwrap(), + !next_version.pre.is_empty(), + )?; + } else { + tracing::debug!("creating release (dry_run)"); + }) + } + + fn create_pull_request( + &self, + changelog_placement: std::path::PathBuf, + changelog: String, + next_version: Version, + dry_run: bool, + owner: &str, + repository: &str, + changelog_last_changes: Option, + branch: &str, + ) -> Result<(), anyhow::Error> { self.git_client.checkout_branch()?; - std::fs::write(changelog_placement, changelog.as_bytes())?; - self.git_client .commit_and_push(next_version.to_string(), dry_run)?; - let _pr_number = match self.gitea_client.get_pull_request(owner, repository)? { Some(existing_pr) => { if !dry_run { @@ -145,7 +151,84 @@ impl ReleaseCommandHandler { } } }; + Ok(()) + } + fn fetch_commits_since_last_tag( + &self, + owner: &str, + repository: &str, + significant_tag: &Option, + branch: &str, + ) -> Result, anyhow::Error> { + let commits = self.gitea_client.get_commits_since( + owner, + repository, + significant_tag.as_ref().map(|st| st.commit.sha.as_str()), + branch, + )?; + Ok(commits) + } + + fn get_most_significant_tag( + &self, + owner: &str, + repository: &str, + ) -> Result, anyhow::Error> { + let tags = self.gitea_client.get_tags(owner, repository)?; + let significant_tag = get_most_significant_version(tags.iter().collect()); + Ok(significant_tag.map(|t| t.clone())) + } + + fn check_git_remote_connection( + &self, + owner: &str, + repository: &str, + ) -> Result<(), anyhow::Error> { + self.gitea_client + .connect(owner, repository) + .context("failed to connect to gitea repository")?; Ok(()) } } + +fn compose_changelog( + commit_strs: &Vec, + next_version: &Version, + source: &std::path::PathBuf, +) -> Result<(std::path::PathBuf, String, Option), anyhow::Error> { + let builder = ChangeLogBuilder::new(commit_strs, next_version.to_string()).build(); + let changelog_placement = source.join("CHANGELOG.md"); + let changelog = match std::fs::read_to_string(&changelog_placement).ok() { + Some(existing_changelog) => builder.prepend(existing_changelog)?, + None => builder.generate()?, + }; + let changelog_last_changes = changelog_parser::last_changes(&changelog)?; + Ok((changelog_placement, changelog, changelog_last_changes)) +} + +fn parse_conventional_commits( + current_version: Version, + commits: Vec, +) -> anyhow::Result, Version)>> { + let commit_strs = commits + .iter() + .map(|c| c.commit.message.clone()) + .collect::>(); + + if commit_strs.is_empty() { + tracing::info!("no commits to base release on"); + return Ok(None); + } + + let next_version = current_version.next(&commit_strs); + + Ok(Some((commit_strs, next_version))) +} + +fn get_current_version(significant_tag: Option) -> Version { + let current_version = significant_tag + .map(|st| Version::try_from(st).unwrap()) + .unwrap_or(Version::new(0, 1, 0)); + current_version +} diff --git a/crates/cuddle-please-misc/src/lib.rs b/crates/cuddle-please-misc/src/lib.rs index a7a4fc6..e7f35be 100644 --- a/crates/cuddle-please-misc/src/lib.rs +++ b/crates/cuddle-please-misc/src/lib.rs @@ -9,7 +9,7 @@ mod versioning; pub use args::{GlobalArgs, RemoteEngine, StdinFn}; pub use cliff::{changelog_parser, ChangeLogBuilder}; pub use git_client::VcsClient; -pub use gitea_client::{DynRemoteGitClient, GiteaClient, RemoteGitEngine}; +pub use gitea_client::{Commit, DynRemoteGitClient, GiteaClient, RemoteGitEngine, Tag}; pub use local_git_client::LocalGitClient; pub use ui::{ConsoleUi, DynUi, Ui}; pub use versioning::{next_version::NextVersion, semver::get_most_significant_version};