Merge branch 'main' into renovate/all

This commit is contained in:
Kasper Juul Hermansen 2023-08-01 15:56:42 +00:00
commit 9e5f5f3b87
6 changed files with 222 additions and 70 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
target/ target/
.cuddle/ .cuddle/
.env

60
CHANGELOG.md Normal file
View File

@ -0,0 +1,60 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.2.0] - 2023-08-01
### Added
- add docker setup
- refactor frontend configuration
- with all the way through
- with create pull request and release
- with gitea
- with prepend as well
- add cliff
- remove tokio
- with doctor
- with git client
- with fixes
- with conventional parse
- with tags command
- add semver
- can get commit chain
- with start of environment engine
- with gitea client
- fmt
- add gitea client stub
- add tests for git setup
- split headings into local and global
- rename to cuddle_please
- add config parsing
- with basic get dir
- add mkdocs
- add base
### Other
- release command
- add cuddle.release to this repository
- add granular docker setup
- fix checks
- chck refactor commands
- move doctor command
- fmt
- rename release command
- move gitea command into its own file
- move config list
- move gitea out of the way
- move config building out of main execution loop
- move commands and misc out of main binary package
- fmt
- check hide commands
- move cuddle-please to cuddle-please release
- remove no-vcs option (moved to a later stage if github is someday adopted
- fix clippy warnings
- clippy fix
- fix
- cleanup

View File

@ -90,11 +90,11 @@ impl Command {
pub fn execute(self, current_dir: Option<&Path>) -> anyhow::Result<()> { pub fn execute(self, current_dir: Option<&Path>) -> anyhow::Result<()> {
let config = self.build_config(current_dir)?; let config = self.build_config(current_dir)?;
let git_client = self.get_git(config.get_source())?; let git_client = self.get_git(config.get_source())?;
let gitea_client = self.get_gitea_client(); let gitea_client = self.get_gitea_client(&config);
match &self.commands { match &self.commands {
Some(Commands::Release {}) => { Some(Commands::Release {}) => {
ReleaseCommandHandler::new(config, git_client, gitea_client) ReleaseCommandHandler::new(self.ui, config, git_client, gitea_client)
.execute(self.global.dry_run)?; .execute(self.global.dry_run)?;
} }
Some(Commands::Config { command }) => { Some(Commands::Config { command }) => {
@ -139,11 +139,11 @@ impl Command {
} }
} }
fn get_gitea_client(&self) -> DynRemoteGitClient { fn get_gitea_client(&self, config: &PleaseConfig) -> DynRemoteGitClient {
match self.global.engine { match self.global.engine {
cuddle_please_misc::RemoteEngine::Local => Box::new(LocalGitClient::new()), cuddle_please_misc::RemoteEngine::Local => Box::new(LocalGitClient::new()),
cuddle_please_misc::RemoteEngine::Gitea => Box::new(GiteaClient::new( cuddle_please_misc::RemoteEngine::Gitea => Box::new(GiteaClient::new(
&self.config.api_url.clone().expect("api_url to be set"), config.get_api_url(),
self.global.token.as_deref(), self.global.token.as_deref(),
)), )),
} }

View File

@ -4,11 +4,12 @@ use ::semver::Version;
use anyhow::Context; use anyhow::Context;
use cuddle_please_misc::{ use cuddle_please_misc::{
changelog_parser, get_most_significant_version, ChangeLogBuilder, DynRemoteGitClient, changelog_parser, get_most_significant_version, ChangeLogBuilder, Commit, DynRemoteGitClient,
NextVersion, VcsClient, DynUi, NextVersion, Tag, VcsClient,
}; };
pub struct ReleaseCommandHandler { pub struct ReleaseCommandHandler {
ui: DynUi,
config: PleaseConfig, config: PleaseConfig,
git_client: VcsClient, git_client: VcsClient,
gitea_client: DynRemoteGitClient, gitea_client: DynRemoteGitClient,
@ -16,11 +17,13 @@ pub struct ReleaseCommandHandler {
impl ReleaseCommandHandler { impl ReleaseCommandHandler {
pub fn new( pub fn new(
ui: DynUi,
config: PleaseConfig, config: PleaseConfig,
git_client: VcsClient, git_client: VcsClient,
gitea_client: DynRemoteGitClient, gitea_client: DynRemoteGitClient,
) -> Self { ) -> Self {
Self { Self {
ui,
config, config,
git_client, git_client,
gitea_client, gitea_client,
@ -34,63 +37,63 @@ impl ReleaseCommandHandler {
let branch = self.config.get_branch(); let branch = self.config.get_branch();
let source = self.config.get_source(); let source = self.config.get_source();
// 2. Parse the cuddle.please.yaml let cuddle.please.yaml take precedence self.check_git_remote_connection(owner, repository)?;
// 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 let significant_tag = self.get_most_significant_tag(owner, repository)?;
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()); 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 conventional_commit_results = parse_conventional_commits(current_version, commits)?;
let commits = self.gitea_client.get_commits_since( 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 (changelog_placement, changelog, changelog_last_changes) =
compose_changelog(&commit_strs, &next_version, source)?;
if let Some(first_commit) = commit_strs.first() {
if first_commit.contains("chore(release): ") {
self.create_release(
dry_run,
owner, owner,
repository, repository,
significant_tag.map(|st| st.commit.sha.as_str()), &next_version,
changelog_last_changes,
)?;
return Ok(());
}
}
self.create_pull_request(
changelog_placement,
changelog,
next_version,
dry_run,
owner,
repository,
changelog_last_changes,
branch, branch,
)?; )?;
// 7. Create a versioning client Ok(())
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::<Vec<&str>>();
if commit_strs.is_empty() {
tracing::info!("no commits to base release on");
return Ok(());
} }
let next_version = current_version.next(&commit_strs); fn create_release(
&self,
// Compose changelog dry_run: bool,
let builder = ChangeLogBuilder::new(&commit_strs, next_version.to_string()).build(); owner: &str,
repository: &str,
let changelog_placement = source.join("CHANGELOG.md"); next_version: &Version,
changelog_last_changes: Option<String>,
let changelog = match std::fs::read_to_string(&changelog_placement).ok() { ) -> Result<(), anyhow::Error> {
Some(existing_changelog) => builder.prepend(existing_changelog)?, Ok(if !dry_run {
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( self.gitea_client.create_release(
owner, owner,
repository, repository,
@ -100,21 +103,24 @@ impl ReleaseCommandHandler {
)?; )?;
} else { } else {
tracing::debug!("creating release (dry_run)"); tracing::debug!("creating release (dry_run)");
})
} }
return Ok(()); fn create_pull_request(
} &self,
} changelog_placement: std::path::PathBuf,
changelog: String,
// 9a. Create / Update Pr next_version: Version,
// Create or update branch dry_run: bool,
owner: &str,
repository: &str,
changelog_last_changes: Option<String>,
branch: &str,
) -> Result<(), anyhow::Error> {
self.git_client.checkout_branch()?; self.git_client.checkout_branch()?;
std::fs::write(changelog_placement, changelog.as_bytes())?; std::fs::write(changelog_placement, changelog.as_bytes())?;
self.git_client self.git_client
.commit_and_push(next_version.to_string(), dry_run)?; .commit_and_push(next_version.to_string(), dry_run)?;
let _pr_number = match self.gitea_client.get_pull_request(owner, repository)? { let _pr_number = match self.gitea_client.get_pull_request(owner, repository)? {
Some(existing_pr) => { Some(existing_pr) => {
if !dry_run { if !dry_run {
@ -145,7 +151,84 @@ impl ReleaseCommandHandler {
} }
} }
}; };
Ok(())
}
fn fetch_commits_since_last_tag(
&self,
owner: &str,
repository: &str,
significant_tag: &Option<Tag>,
branch: &str,
) -> Result<Vec<Commit>, 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<Option<Tag>, 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(()) Ok(())
} }
} }
fn compose_changelog(
commit_strs: &Vec<String>,
next_version: &Version,
source: &std::path::PathBuf,
) -> Result<(std::path::PathBuf, String, Option<String>), 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<Commit>,
) -> anyhow::Result<Option<(Vec<String>, Version)>> {
let commit_strs = commits
.iter()
.map(|c| c.commit.message.clone())
.collect::<Vec<_>>();
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<Tag>) -> Version {
let current_version = significant_tag
.map(|st| Version::try_from(st).unwrap())
.unwrap_or(Version::new(0, 0, 0));
current_version
}

View File

@ -9,7 +9,7 @@ mod versioning;
pub use args::{GlobalArgs, RemoteEngine, StdinFn}; pub use args::{GlobalArgs, RemoteEngine, StdinFn};
pub use cliff::{changelog_parser, ChangeLogBuilder}; pub use cliff::{changelog_parser, ChangeLogBuilder};
pub use git_client::VcsClient; 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 local_git_client::LocalGitClient;
pub use ui::{ConsoleUi, DynUi, Ui}; pub use ui::{ConsoleUi, DynUi, Ui};
pub use versioning::{next_version::NextVersion, semver::get_most_significant_version}; pub use versioning::{next_version::NextVersion, semver::get_most_significant_version};

View File

@ -7,6 +7,14 @@ vars:
registry: kasperhermansen registry: kasperhermansen
mkdocs_image: "squidfunk/mkdocs-material:9.1" mkdocs_image: "squidfunk/mkdocs-material:9.1"
please:
project:
owner: kjuulh
repository: cuddle-please
branch: main
settings:
api_url: https://git.front.kjuulh.io
scripts: scripts:
"mkdocs:new": "mkdocs:new":
type: shell type: shell