use super::*;
#[cfg(feature = "vcs-git")]
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Vcs {
pub commits_long: u32,
pub commits_recent: u32,
pub churn_long: u64,
pub churn_recent: u64,
pub authors_long: u32,
pub authors_recent: u32,
#[serde(default = "nan_default", with = "non_finite")]
pub ownership_top_share: f64,
#[serde(default = "nan_default", with = "non_finite")]
pub burst: f64,
pub bug_fix_commits: u32,
pub security_fix_commits: u32,
pub revert_commits: u32,
pub age_days: u32,
pub last_modified_days: u32,
#[serde(default = "nan_default", with = "non_finite")]
pub change_entropy_long: f64,
#[serde(default = "nan_default", with = "non_finite")]
pub change_entropy_recent: f64,
#[serde(default = "nan_default", with = "non_finite")]
pub cochange_entropy_long: f64,
#[serde(default = "nan_default", with = "non_finite")]
pub cochange_entropy_recent: f64,
#[serde(default = "nan_default", with = "non_finite")]
pub risk_score: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hotspot_score: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub author_ids: Option<Vec<String>>,
}
#[cfg(feature = "vcs-git")]
impl From<&crate::vcs::Stats> for Vcs {
fn from(s: &crate::vcs::Stats) -> Self {
Self {
commits_long: s.commits_long,
commits_recent: s.commits_recent,
churn_long: s.churn_long,
churn_recent: s.churn_recent,
authors_long: s.authors_long,
authors_recent: s.authors_recent,
ownership_top_share: s.ownership_top_share,
burst: s.burst,
bug_fix_commits: s.bug_fix_commits,
security_fix_commits: s.security_fix_commits,
revert_commits: s.revert_commits,
age_days: s.age_days,
last_modified_days: s.last_modified_days,
change_entropy_long: s.change_entropy_long,
change_entropy_recent: s.change_entropy_recent,
cochange_entropy_long: s.cochange_entropy_long,
cochange_entropy_recent: s.cochange_entropy_recent,
risk_score: s.risk_score,
hotspot_score: s.hotspot_score,
author_ids: s.author_ids.clone(),
}
}
}
#[cfg(feature = "vcs-git")]
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct VcsReportFile {
pub path: String,
pub vcs: Vcs,
}
#[cfg(feature = "vcs-git")]
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct VcsReport {
pub long_window_days: u32,
pub recent_window_days: u32,
pub risk_score_version: u32,
pub vcs_schema_version: u32,
pub truncated_shallow_clone: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub vcs_aggregate: Option<crate::vcs::VcsAggregate>,
pub files: Vec<VcsReportFile>,
}
#[cfg(feature = "vcs-git")]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VcsTrendPoint {
pub as_of: i64,
pub vcs: Vcs,
}
#[cfg(feature = "vcs-git")]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VcsTrendDelta {
pub path: String,
pub first_as_of: i64,
pub last_as_of: i64,
pub first_risk_score: f64,
pub last_risk_score: f64,
pub delta: f64,
}
#[cfg(feature = "vcs-git")]
impl VcsTrendDelta {
fn from_delta(d: &crate::vcs::TrendDelta) -> Option<Self> {
Some(Self {
path: d.path.to_str()?.to_owned(),
first_as_of: d.first_as_of,
last_as_of: d.last_as_of,
first_risk_score: d.first_risk_score,
last_risk_score: d.last_risk_score,
delta: d.delta,
})
}
}
#[cfg(feature = "vcs-git")]
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct VcsTrendDeltas {
pub improved: Vec<VcsTrendDelta>,
pub regressed: Vec<VcsTrendDelta>,
}
#[cfg(feature = "vcs-git")]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VcsTrend {
pub trend_schema_version: u32,
pub vcs_schema_version: u32,
pub risk_score_version: u32,
pub long_window_days: u32,
pub recent_window_days: u32,
pub truncated_shallow_clone: bool,
pub as_of_points: Vec<i64>,
pub files: std::collections::BTreeMap<String, Vec<Option<VcsTrendPoint>>>,
pub deltas: VcsTrendDeltas,
}
#[cfg(feature = "vcs-git")]
impl VcsTrend {
#[must_use]
pub fn from_trend(trend: &crate::vcs::Trend, top_files: usize, top_deltas: usize) -> Self {
let as_of_points = trend.as_of_points().to_vec();
let mut ranked: Vec<(&std::path::PathBuf, &[Option<crate::vcs::Stats>], f64)> = trend
.iter()
.map(|(path, points)| (path, points, latest_present_risk(points)))
.collect();
crate::vcs::rank_by_risk(&mut ranked, top_files, |entry| {
(entry.0.to_str().unwrap_or(""), entry.2)
});
let files = ranked
.into_iter()
.filter_map(|(path, points, _)| {
let key = path.to_str()?.to_owned();
let series = points
.iter()
.zip(&as_of_points)
.map(|(stats, &as_of)| {
stats.as_ref().map(|s| VcsTrendPoint {
as_of,
vcs: Vcs::from(s),
})
})
.collect();
Some((key, series))
})
.collect();
let compute_deltas = trend.deltas(top_deltas);
let deltas = VcsTrendDeltas {
improved: compute_deltas
.improved
.iter()
.filter_map(VcsTrendDelta::from_delta)
.collect(),
regressed: compute_deltas
.regressed
.iter()
.filter_map(VcsTrendDelta::from_delta)
.collect(),
};
Self {
trend_schema_version: crate::vcs::TREND_SCHEMA_VERSION,
vcs_schema_version: crate::vcs::stats::VCS_SCHEMA_VERSION,
risk_score_version: crate::vcs::score::RISK_SCORE_VERSION,
long_window_days: trend.long_window_days(),
recent_window_days: trend.recent_window_days(),
truncated_shallow_clone: trend.truncated_shallow_clone(),
as_of_points,
files,
deltas,
}
}
}