use crate::changelog::{Changelog, SectionType};
use crate::config::GitConfig;
use chrono::{DateTime, Utc};
#[derive(Debug, Clone)]
pub struct MergeMessageContext {
pub package_name: Option<String>,
pub version: String,
pub previous_version: Option<String>,
pub bump_type: String,
pub date: DateTime<Utc>,
pub author: Option<String>,
pub changelog: Option<Changelog>,
}
impl MergeMessageContext {
#[must_use]
pub fn new(
package_name: Option<&str>,
version: &str,
previous_version: Option<&str>,
bump_type: &str,
date: DateTime<Utc>,
) -> Self {
Self {
package_name: package_name.map(String::from),
version: version.to_string(),
previous_version: previous_version.map(String::from),
bump_type: bump_type.to_string(),
date,
author: None,
changelog: None,
}
}
#[must_use]
pub fn with_author(mut self, author: Option<String>) -> Self {
self.author = author;
self
}
#[must_use]
pub fn with_changelog(mut self, changelog: Option<Changelog>) -> Self {
self.changelog = changelog;
self
}
#[must_use]
pub fn breaking_changes_count(&self) -> usize {
self.changelog.as_ref().map(|cl| cl.breaking_changes().len()).unwrap_or(0)
}
#[must_use]
pub fn features_count(&self) -> usize {
self.changelog
.as_ref()
.map(|cl| {
cl.sections
.iter()
.filter(|s| s.section_type == SectionType::Features)
.map(|s| s.entries.len())
.sum()
})
.unwrap_or(0)
}
#[must_use]
pub fn fixes_count(&self) -> usize {
self.changelog
.as_ref()
.map(|cl| {
cl.sections
.iter()
.filter(|s| s.section_type == SectionType::Fixes)
.map(|s| s.entries.len())
.sum()
})
.unwrap_or(0)
}
#[must_use]
pub fn changelog_summary(&self) -> String {
let Some(ref changelog) = self.changelog else {
return String::from("No changelog available");
};
if changelog.is_empty() {
return String::from("No changes recorded");
}
let mut parts = Vec::new();
let features = self.features_count();
if features > 0 {
parts.push(format!(
"- {} {}",
features,
if features == 1 { "new feature" } else { "new features" }
));
}
let fixes = self.fixes_count();
if fixes > 0 {
parts.push(format!("- {} bug {}", fixes, if fixes == 1 { "fix" } else { "fixes" }));
}
let breaking = self.breaking_changes_count();
if breaking > 0 {
parts.push(format!(
"- {} breaking {}",
breaking,
if breaking == 1 { "change" } else { "changes" }
));
}
for section in &changelog.sections {
if section.section_type != SectionType::Features
&& section.section_type != SectionType::Fixes
&& section.section_type != SectionType::Breaking
&& !section.is_empty()
{
let count = section.entries.len();
parts.push(format!("- {} {}", count, section.title().to_lowercase()));
}
}
if parts.is_empty() {
String::from("- Minor updates and improvements")
} else {
parts.join("\n")
}
}
#[must_use]
pub fn is_monorepo(&self) -> bool {
self.package_name.is_some()
}
}
#[must_use]
pub fn generate_merge_commit_message(context: &MergeMessageContext, config: &GitConfig) -> String {
let template = if context.is_monorepo() {
&config.monorepo_merge_commit_template
} else {
&config.merge_commit_template
};
let mut message = replace_variables(template, context);
if config.include_breaking_warning && context.breaking_changes_count() > 0 {
let warning = replace_variables(&config.breaking_warning_template, context);
message.push_str(&warning);
}
message
}
fn replace_variables(template: &str, context: &MergeMessageContext) -> String {
let date_str = context.date.format("%Y-%m-%d").to_string();
let previous_version = context.previous_version.as_deref().unwrap_or("N/A");
let package_name = context.package_name.as_deref().unwrap_or("N/A");
let author = context.author.as_deref().unwrap_or("Unknown");
let changelog_summary = context.changelog_summary();
template
.replace("{version}", &context.version)
.replace("{previous_version}", previous_version)
.replace("{package_name}", package_name)
.replace("{bump_type}", &context.bump_type)
.replace("{date}", &date_str)
.replace("{breaking_changes_count}", &context.breaking_changes_count().to_string())
.replace("{features_count}", &context.features_count().to_string())
.replace("{fixes_count}", &context.fixes_count().to_string())
.replace("{changelog_summary}", &changelog_summary)
.replace("{author}", author)
}