Skip to main content

fallow_engine/
churn.rs

1//! Git churn helpers and types exposed through the engine boundary.
2
3use std::path::{Path, PathBuf};
4use std::process::{Command, Output};
5
6pub use fallow_types::churn::ChurnTrend;
7use rustc_hash::FxHashMap;
8
9use crate::core_backend;
10
11/// Function pointer signature used to intercept git churn subprocesses.
12pub type ChurnSpawnHook = fn(&mut Command) -> std::io::Result<Output>;
13
14/// Parsed duration for the `--since` flag.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct SinceDuration {
17    /// Value to pass to `git log --after`.
18    pub git_after: String,
19    /// Human-readable display string.
20    pub display: String,
21}
22
23/// Per-author commit aggregation for a single file.
24#[derive(Debug, Clone, Copy, PartialEq)]
25pub struct AuthorContribution {
26    /// Total commits by this author touching this file in the analysis window.
27    pub commits: u32,
28    /// Recency-weighted commit sum.
29    pub weighted_commits: f64,
30    /// Earliest commit timestamp by this author.
31    pub first_commit_ts: u64,
32    /// Latest commit timestamp by this author.
33    pub last_commit_ts: u64,
34}
35
36/// Per-file churn data collected from git history.
37#[derive(Debug, Clone)]
38pub struct FileChurn {
39    /// Absolute file path.
40    pub path: PathBuf,
41    /// Total number of commits touching this file in the analysis window.
42    pub commits: u32,
43    /// Recency-weighted commit count.
44    pub weighted_commits: f64,
45    /// Total lines added across all commits.
46    pub lines_added: u32,
47    /// Total lines deleted across all commits.
48    pub lines_deleted: u32,
49    /// Churn trend: accelerating, stable, or cooling.
50    pub trend: ChurnTrend,
51    /// Per-author contributions keyed by interned author index.
52    pub authors: FxHashMap<u32, AuthorContribution>,
53}
54
55/// Result of churn analysis.
56#[derive(Debug, Clone)]
57pub struct ChurnResult {
58    /// Per-file churn data, keyed by absolute path.
59    pub files: FxHashMap<PathBuf, FileChurn>,
60    /// Whether the repository is a shallow clone.
61    pub shallow_clone: bool,
62    /// Author email pool.
63    pub author_pool: Vec<String>,
64}
65
66/// Install a spawn hook for git churn analysis.
67pub fn set_spawn_hook(hook: ChurnSpawnHook) {
68    core_backend::set_churn_spawn_hook(hook);
69}
70
71/// Parse a `--since` value into a git-compatible duration.
72///
73/// # Errors
74///
75/// Returns an error if the input is not a supported duration or ISO date.
76pub fn parse_since(input: &str) -> Result<SinceDuration, String> {
77    core_backend::parse_since(input)
78}
79
80/// Analyze git churn for files under `root`.
81#[must_use]
82pub fn analyze_churn(root: &Path, since: &SinceDuration) -> Option<ChurnResult> {
83    core_backend::analyze_churn(root, since)
84}
85
86/// Analyze churn from a normalized `fallow-churn/v1` file.
87///
88/// # Errors
89///
90/// Returns an error when the import file cannot be read, parsed, or validated.
91pub fn analyze_churn_from_file(path: &Path, root: &Path) -> Result<ChurnResult, String> {
92    core_backend::analyze_churn_from_file(path, root)
93}
94
95/// Check whether `root` is inside a git repository.
96#[must_use]
97pub fn is_git_repo(root: &Path) -> bool {
98    core_backend::is_git_repo(root)
99}
100
101/// Analyze churn with disk caching.
102#[must_use]
103pub fn analyze_churn_cached(
104    root: &Path,
105    since: &SinceDuration,
106    cache_dir: &Path,
107    no_cache: bool,
108) -> Option<(ChurnResult, bool)> {
109    core_backend::analyze_churn_cached(root, since, cache_dir, no_cache)
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn parse_since_returns_engine_owned_duration() {
118        let duration = parse_since("6m").expect("duration should parse");
119        assert_eq!(duration.git_after, "6 months ago");
120        assert_eq!(duration.display, "6 months");
121    }
122}