use cuddle_please_frontend::PleaseConfig; use ::semver::Version; use anyhow::Context; use cuddle_please_misc::{ changelog_parser, get_most_significant_version, ChangeLogBuilder, DynRemoteGitClient, NextVersion, VcsClient, }; pub struct ReleaseCommandHandler { config: PleaseConfig, git_client: VcsClient, gitea_client: DynRemoteGitClient, } impl ReleaseCommandHandler { pub fn new( config: PleaseConfig, git_client: VcsClient, gitea_client: DynRemoteGitClient, ) -> Self { Self { config, git_client, gitea_client, } } pub fn execute(&self, dry_run: bool) -> anyhow::Result<()> { tracing::debug!("running command: release"); let owner = self.config.get_owner(); let repository = self.config.get_repository(); 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) // 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 = get_most_significant_version(tags.iter().collect()); // 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"); return Ok(()); } let next_version = current_version.next(&commit_strs); // 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)"); } return Ok(()); } } // 9a. Create / Update Pr // Create or update branch 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 { self.gitea_client.update_pull_request( owner, repository, &next_version.to_string(), &changelog_last_changes.unwrap(), existing_pr, )? } else { tracing::debug!("updating pull request (dry_run)"); 1 } } None => { if !dry_run { self.gitea_client.create_pull_request( owner, repository, &next_version.to_string(), &changelog, branch, )? } else { tracing::debug!("creating pull request (dry_run)"); 1 } } }; Ok(()) } }