use crate::{
analyzer::{
group::GroupParser,
release::{Release, Tag},
version_strategy::{VersionContext, VersionStrategyFactory},
},
error::Result,
forge::request::ForgeCommit,
};
mod commit;
pub mod config;
mod group;
mod helpers;
pub mod release;
mod version_strategy;
pub struct Analyzer<'a> {
config: &'a config::AnalyzerConfig,
group_parser: GroupParser,
}
impl<'a> Analyzer<'a> {
pub fn new(config: &'a config::AnalyzerConfig) -> Result<Self> {
Ok(Self {
config,
group_parser: GroupParser::default(),
})
}
pub fn analyze(
&self,
commits: Vec<ForgeCommit>,
current_tag: Option<Tag>,
) -> Result<Option<Release>> {
let mut release = self.process_commits(commits)?;
if release.commits.is_empty() {
return Ok(None);
}
self.process_release(&mut release, current_tag.as_ref())?;
Ok(Some(release))
}
fn process_release(
&self,
release: &mut Release,
current_tag: Option<&Tag>,
) -> Result<()> {
let strategy =
VersionStrategyFactory::create(self.config.prerelease.as_ref())?;
let commits = release
.commits
.iter()
.map(|c| c.raw_message.to_string())
.collect::<Vec<String>>();
let context = VersionContext {
current_tag,
commits: &commits,
breaking_always_increment_major: self
.config
.breaking_always_increment_major,
features_always_increment_minor: self
.config
.features_always_increment_minor,
custom_major_increment_regex: self
.config
.custom_major_increment_regex
.as_deref(),
custom_minor_increment_regex: self
.config
.custom_minor_increment_regex
.as_deref(),
};
let next = strategy.calculate_next_version(&context)?;
let mut next_tag_name = next.to_string();
if let Some(prefix) = self.config.tag_prefix.as_ref() {
next_tag_name = format!("{prefix}{}", next);
}
let next_tag = release::Tag {
name: next_tag_name,
semver: next,
timestamp: None,
sha: "".into(),
};
if let Some(base_url) = self.config.release_link_base_url.as_ref() {
release.link = base_url.join(&next_tag.name)?.to_string();
}
if let Some(base_url) = self.config.compare_link_base_url.as_ref()
&& let Some(current) = current_tag
{
release.tag_compare_link = base_url
.join(&format!("{}...{}", current.name, next_tag.name))?
.to_string();
release.sha_compare_link = base_url
.join(&format!("{}...{}", current.name, release.sha))?
.to_string();
}
release.tag = next_tag;
let context = tera::Context::from_serialize(&release)?;
let notes = tera::Tera::one_off(&self.config.body, &context, false)?;
release.notes = helpers::strip_extra_lines(notes.trim());
Ok(())
}
fn process_commits(
&self,
commits: Vec<ForgeCommit>,
) -> Result<release::Release> {
let mut release = release::Release::default();
if self.config.include_author {
release.include_author = true;
}
for forge_commit in commits.iter() {
if self
.config
.commit_modifiers
.skip_shas
.iter()
.any(|sha| forge_commit.id.starts_with(sha))
{
log::debug!(
"skip_shas contains commit it: skipping {}",
forge_commit.id
);
continue;
}
let forge_commit = if let Some(reworded) = self
.config
.commit_modifiers
.reword
.iter()
.find(|r| forge_commit.id.starts_with(&r.sha))
{
log::debug!("rewording commit: {}", forge_commit.id);
&ForgeCommit {
message: reworded.message.clone(),
..forge_commit.clone()
}
} else {
forge_commit
};
helpers::update_release_with_commit(
&self.group_parser,
&mut release,
forge_commit,
self.config,
);
}
Ok(release)
}
}
#[cfg(test)]
mod tests;