gmap 0.3.3

Git repository analysis tool for churn and heatmap visualization
Documentation
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;

pub const SCHEMA_VERSION: u32 = 1;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitInfo {
    pub id: String,
    pub author_name: String,
    pub author_email: String,
    pub message: String,
    pub timestamp: DateTime<Utc>,
    pub parent_ids: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileStats {
    pub path: String,
    pub added_lines: u32,
    pub deleted_lines: u32,
    pub is_binary: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitStats {
    pub commit_id: String,
    pub files: Vec<FileStats>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChurnEntry {
    pub path: String,
    pub added_lines: u64,
    pub deleted_lines: u64,
    pub total_lines: u64,
    pub commit_count: u32,
    pub authors: HashSet<String>,
}

impl ChurnEntry {
    pub fn new(path: String) -> Self {
        Self {
            path,
            added_lines: 0,
            deleted_lines: 0,
            total_lines: 0,
            commit_count: 0,
            authors: HashSet::new(),
        }
    }

    pub fn add_stats(&mut self, stats: &FileStats, author: &str) {
        self.added_lines += stats.added_lines as u64;
        self.deleted_lines += stats.deleted_lines as u64;
        self.total_lines += (stats.added_lines + stats.deleted_lines) as u64;
        self.commit_count += 1;
        if self.authors.len() < 100 {
            self.authors.insert(author.to_string());
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChurnOutput {
    pub version: u32,
    pub generated_at: DateTime<Utc>,
    pub repository_path: String,
    pub since: Option<String>,
    pub until: Option<String>,
    pub depth: Option<u32>,
    pub entries: Vec<ChurnEntry>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeatBucket {
    pub week: String,
    pub commit_count: u32,
    pub lines_changed: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeatOutput {
    pub version: u32,
    pub generated_at: DateTime<Utc>,
    pub repository_path: String,
    pub path_prefix: String,
    pub since: Option<String>,
    pub until: Option<String>,
    pub buckets: Vec<HeatBucket>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportEntry {
    pub commit_id: String,
    pub author_name: String,
    pub author_email: String,
    pub timestamp: DateTime<Utc>,
    pub message: String,
    pub files: Vec<FileStats>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportOutput {
    pub version: u32,
    pub generated_at: DateTime<Utc>,
    pub repository_path: String,
    pub since: Option<String>,
    pub until: Option<String>,
    pub entries: Vec<ExportEntry>,
}

#[derive(Debug, Clone)]
pub struct DateRange {
    pub since: Option<DateTime<Utc>>,
    pub until: Option<DateTime<Utc>>,
}

impl DateRange {
    pub fn new() -> Self {
        Self { since: None, until: None }
    }

    pub fn with_since(mut self, since: DateTime<Utc>) -> Self {
        self.since = Some(since);
        self
    }

    pub fn with_until(mut self, until: DateTime<Utc>) -> Self {
        self.until = Some(until);
        self
    }

    pub fn contains(&self, timestamp: &DateTime<Utc>) -> bool {
        if let Some(since) = self.since {
            if timestamp < &since {
                return false;
            }
        }
        if let Some(until) = self.until {
            if timestamp > &until {
                return false;
            }
        }
        true
    }
}

impl Default for DateRange {
    fn default() -> Self {
        Self::new()
    }
}