use std::collections::HashMap;
use std::path::{Path, PathBuf};
use gix::revision::walk::Sorting;
use gix::traverse::commit::simple::CommitTimeOrder;
use super::history::commit_seconds;
use super::repo::{self, OpenRepo};
use super::{build, current_unix_seconds, walk_err};
use crate::vcs::error::Error;
use crate::vcs::options::Options;
use crate::vcs::stats::Stats;
use crate::vcs::trend::{self, Trend};
pub(crate) fn build_trend(
root: &Path,
base: &Options,
points: usize,
span_secs: i64,
) -> Result<Trend, Error> {
trend::validate_points(points)?;
let end = base.as_of.unwrap_or_else(current_unix_seconds);
let stamps = trend::timestamps(end, span_secs, points);
let OpenRepo { repo, .. } = repo::open(root)?;
let tip = repo::resolve_commit(&repo, &base.reference)?.id;
let timeline = first_parent_timeline(&repo, tip)?;
drop(repo);
let mut per_point: Vec<HashMap<PathBuf, Stats>> = Vec::with_capacity(stamps.len());
let mut truncated = false;
for &at in &stamps {
let Some(oid) = tip_at_or_before(&timeline, at) else {
per_point.push(HashMap::new());
continue;
};
let index = build(root, &snapshot_options(base, oid, at))?;
truncated |= index.truncated_shallow_clone();
per_point.push(index.into_files());
}
Ok(Trend::from_snapshots(
stamps,
per_point,
base.long_window_days(),
base.recent_window_days(),
truncated,
))
}
fn snapshot_options(base: &Options, oid: gix::ObjectId, at: i64) -> Options {
let mut options = base.clone();
options.reference = oid.to_hex().to_string();
options.as_of = Some(at);
options.compute_bus_factor = false;
options
}
pub(super) fn first_parent_timeline(
repo: &gix::Repository,
tip: gix::ObjectId,
) -> Result<Vec<(i64, gix::ObjectId)>, Error> {
let walk = repo
.rev_walk([tip])
.first_parent_only()
.sorting(Sorting::ByCommitTime(CommitTimeOrder::NewestFirst))
.all()
.map_err(walk_err)?;
let mut timeline = Vec::new();
for info in walk {
let info = info.map_err(walk_err)?;
let commit = info.object().map_err(walk_err)?;
timeline.push((commit_seconds(&info, &commit)?, info.id));
}
Ok(timeline)
}
pub(super) fn tip_at_or_before(
timeline: &[(i64, gix::ObjectId)],
at: i64,
) -> Option<gix::ObjectId> {
timeline
.iter()
.enumerate()
.filter(|&(_, &(time, _))| time <= at)
.max_by_key(|&(index, &(time, _))| (time, std::cmp::Reverse(index)))
.map(|(_, &(_, oid))| oid)
}
#[cfg(test)]
#[path = "trend_tests.rs"]
mod tests;