use std::cmp::Ordering;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use serde::Serialize;
use super::gate_score_worsened;
use crate::git::FileChange;
#[derive(Debug, Clone, Serialize)]
pub struct ChangedFile {
pub path: String,
pub before: Option<f64>,
pub after: f64,
pub delta: Option<f64>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ChangedScope {
pub files: Vec<ChangedFile>,
pub duplicated_lines_before: usize,
pub duplicated_lines_after: usize,
}
pub fn changed_files(
changes: &[FileChange],
prefix: &Path,
before: &HashMap<PathBuf, f64>,
after: &HashMap<PathBuf, f64>,
) -> Vec<ChangedFile> {
let mut files: Vec<ChangedFile> = changes
.iter()
.filter_map(|c| {
let after = *after.get(c.path.strip_prefix(prefix).ok()?)?;
let before = c
.old_path
.as_deref()
.and_then(|p| p.strip_prefix(prefix).ok())
.and_then(|p| before.get(p).copied());
Some(ChangedFile {
path: c.path.display().to_string(),
before,
after,
delta: before.map(|b| after - b),
})
})
.collect();
files.sort_by(|a, b| {
let by_delta = match (a.delta, b.delta) {
(Some(x), Some(y)) => x.total_cmp(&y),
(Some(_), None) => Ordering::Less,
(None, Some(_)) => Ordering::Greater,
(None, None) => Ordering::Equal,
};
by_delta.then_with(|| a.path.cmp(&b.path))
});
files
}
pub fn gate_failure(scope: &ChangedScope, tolerance: f64, baseline: f64) -> Option<String> {
let mut reasons: Vec<String> = scope
.files
.iter()
.filter_map(|f| {
let before = f.before?;
(f.after < baseline && gate_score_worsened(before, f.after, tolerance)).then(|| {
format!(
"{} dropped {before:.2} → {:.2} ({:+.4}), below the project score \
{baseline:.2} and more than the {tolerance} tolerance",
f.path,
f.after,
f.after - before
)
})
})
.collect();
let (dup_before, dup_after) = (scope.duplicated_lines_before, scope.duplicated_lines_after);
if dup_after > dup_before {
reasons.push(format!(
"duplicated lines grew {dup_before} → {dup_after} (+{})",
dup_after - dup_before
));
}
(!reasons.is_empty()).then(|| format!("quality gate failed:\n {}", reasons.join("\n ")))
}
#[cfg(test)]
#[path = "changed_test.rs"]
mod tests;