use std::collections::BTreeMap;
use std::path::Path;
use crate::git::Git;
use crate::model::changelog::CommitReference;
use crate::model::changeset::{ChangeType, Changeset};
use crate::package_manager::validate_package_names;
use super::PackageChanges;
pub(crate) fn aggregate_changesets(
changesets: &[(crate::path::AbsolutePath, Changeset)],
package_filter: &[String],
projects: &[crate::package_manager::Project],
commit_refs: &BTreeMap<crate::path::AbsolutePath, Option<CommitReference>>,
) -> anyhow::Result<(
BTreeMap<String, ChangeType>,
BTreeMap<String, PackageChanges>,
)> {
let mut aggregated: BTreeMap<String, ChangeType> = BTreeMap::new();
for (_, cs) in changesets {
for (pkg, ct) in &cs.packages {
aggregated
.entry(pkg.clone())
.and_modify(|e| *e = (*e).max(*ct))
.or_insert(*ct);
}
}
let mut changes_per_package: BTreeMap<String, PackageChanges> = BTreeMap::new();
for (path, cs) in changesets {
let commit_ref = commit_refs.get(path).and_then(|r| r.clone());
for (pkg, ct) in &cs.packages {
changes_per_package.entry(pkg.clone()).or_default().push((
*ct,
cs.message.clone(),
commit_ref.clone(),
));
}
}
if !package_filter.is_empty() {
validate_package_names(projects, package_filter)?;
aggregated.retain(|name, _| package_filter.contains(name));
changes_per_package.retain(|name, _| package_filter.contains(name));
}
Ok((aggregated, changes_per_package))
}
pub(crate) async fn resolve_commit_references(
changesets: &[(crate::path::AbsolutePath, Changeset)],
git: &dyn Git,
git_enabled: bool,
) -> BTreeMap<crate::path::AbsolutePath, Option<CommitReference>> {
if !git_enabled {
log::debug!("Git disabled; skipping commit reference resolution");
return changesets.iter().map(|(p, _)| (p.clone(), None)).collect();
}
let mut result = BTreeMap::new();
for (path, _) in changesets {
let commit_ref = resolve_one_commit_reference(path, git).await;
result.insert(path.clone(), commit_ref);
}
result
}
pub(super) async fn resolve_one_commit_reference(
path: &Path,
git: &dyn Git,
) -> Option<CommitReference> {
let repo_root = git.path();
let rel_path = path.strip_prefix(repo_root).unwrap_or(path);
let sha = match git.log_added_commit(rel_path).await {
Ok(Some(sha)) => sha,
Ok(None) => {
log::debug!("No introducing commit found for {}", path.display());
return None;
}
Err(e) => {
log::warn!("Failed to resolve commit for {}: {e:#}", path.display());
return None;
}
};
let message = match git.log_message(&sha).await {
Ok(s) => s,
Err(e) => {
log::warn!("Failed to get commit message for {sha}: {e:#}");
return None;
}
};
let commit_ref = CommitReference::new(&sha, &message);
if !commit_ref.has_reference() {
log::debug!("No PR/MR reference found in commit message: {message:?}");
}
Some(commit_ref)
}