mod blame;
mod cached;
mod diff_parse;
mod history;
mod identity;
mod jit;
mod repo;
mod trend;
pub use blame::{LineSpan, PerFunctionBlame};
pub(crate) use cached::build_cached;
pub(crate) use diff_parse::score_diff;
pub(crate) use jit::score_commit;
pub(crate) use repo::workdir_root;
pub(crate) use trend::build_trend;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::vcs::HistoryIndex;
use crate::vcs::error::Error;
use crate::vcs::options::Options;
use crate::vcs::replay;
pub(super) const OBJECT_CACHE_BYTES: usize = 8 * 1024 * 1024;
pub(crate) fn build(root: &Path, options: &Options) -> Result<HistoryIndex, Error> {
let repo::OpenRepo {
mut repo,
workdir,
shallow,
} = repo::open(root)?;
repo.object_cache_size_if_unset(OBJECT_CACHE_BYTES);
let Some(commit) = resolve_anchor(&repo, options)? else {
return Ok(HistoryIndex::new(HashMap::new(), workdir, shallow));
};
let target_tree = commit.tree().map_err(walk_err)?;
let seed = repo::enumerate_target_files(&repo, &target_tree, &options.file_types)?;
let now = options.as_of.unwrap_or_else(current_unix_seconds);
let (events, _) = history::collect_events(&repo, commit.id, options, now, &HashSet::new())?;
let out = replay::replay(seed, &events, options, now);
Ok(HistoryIndex::new(out.files, workdir, shallow).with_bus_factor(out.bus_factor))
}
fn resolve_anchor<'repo>(
repo: &'repo gix::Repository,
options: &Options,
) -> Result<Option<gix::Commit<'repo>>, Error> {
let tip = repo::resolve_commit(repo, &options.reference)?;
let Some(as_of) = options.as_of else {
return Ok(Some(tip));
};
let timeline = trend::first_parent_timeline(repo, tip.id)?;
let Some(anchor) = trend::tip_at_or_before(&timeline, as_of) else {
return Ok(None);
};
Ok(Some(repo::resolve_commit(
repo,
&anchor.to_hex().to_string(),
)?))
}
pub(super) fn walk_err(e: impl std::fmt::Display) -> Error {
Error::Walk(e.to_string())
}
pub(super) fn diff_err(e: impl std::fmt::Display) -> Error {
Error::Diff(e.to_string())
}
pub(crate) fn parse_timestamp(input: &str) -> Result<i64, Error> {
if let Some(epoch) = input.strip_prefix('@') {
return epoch
.parse::<i64>()
.map_err(|_| Error::InvalidTimestamp(format!("{input:?}: not a Unix timestamp")));
}
gix::date::parse(input, Some(SystemTime::now()))
.map(|time| time.seconds)
.map_err(|e| Error::InvalidTimestamp(format!("{input:?}: {e}")))
}
pub(super) fn current_unix_seconds() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| i64::try_from(d.as_secs()).unwrap_or(i64::MAX))
}
#[cfg(test)]
#[path = "mod_tests.rs"]
mod tests;