- src/stats/: OwnEmailMatcher (config emails + GH no-reply per account) + git-side --author filtering (cheap even on the pterodactyl fork's 4700+ commits); streak (current/longest, midnight-grace rule), 52-week heatmap, day/hour histograms — one scan feeds all of them, data layer only for now (visual Stats-tab redesign stays in Sprint D's mockup-first cycle) - src/stats/cadence.rs: release cadence (median tag gap, local tags, Tier A) - src/stats/size.rs + cache.rs: git-object vs working-tree size, persisted in new ~/.vulp/stats_cache.toml (max 1 sample/day), recorded only during [R]/vulp refresh — confirms vulp's own filter-repo cleanup (539MB->834KB) - src/digest.rs: WeeklyDigest struct + 3 renderers (Discord embed/markdown archive/plaintext); vulp digest [send discord|webhook <url>] CLI, webhook stored via existing auth token store (~/.vulp/.tokens, 0600), never logged; send respects --dry-run - TUI: [d] on Stats previews the digest in a Modal::Info before anything is ever sent - fix: list_contributors used --all (double-counts after fetching remotes), now --branches - config: stats_author_emails field (TUI-editable) - verified: streak/delta/top-repo/release math against a hand-built commit history, webhook validation+permissions, dry-run, real refresh (51 repos, vulp's own entry confirms the filter-repo win), TUI preview via pyte
49 lines
1.8 KiB
Rust
49 lines
1.8 KiB
Rust
//! Repo size, measured as two separate signals rather than one sum: git
|
|
//! object-store size (a growing number usually means bloating history —
|
|
//! binaries, accidentally committed build output) and working-tree size
|
|
//! (usually just `node_modules`/`target`, unrelated to the git history
|
|
//! problem). Conflating them would have hidden exactly the pathology that
|
|
//! motivated adding this metric: vulp's own `target/` growing to 539 MB in
|
|
//! its committed history.
|
|
|
|
/// One repo's size, in KiB.
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
pub struct RepoSize {
|
|
/// Loose + packed git objects (`git count-objects -v`).
|
|
pub git_kb: u64,
|
|
/// Working tree excluding `.git` (`du -sk --exclude=.git`).
|
|
pub tree_kb: u64,
|
|
}
|
|
|
|
pub fn repo_size(path: &std::path::Path) -> RepoSize {
|
|
RepoSize { git_kb: count_objects_kb(path), tree_kb: du_kb_excluding_git(path) }
|
|
}
|
|
|
|
fn count_objects_kb(path: &std::path::Path) -> u64 {
|
|
let out = std::process::Command::new("git")
|
|
.arg("-C").arg(path).args(["count-objects", "-v"])
|
|
.output();
|
|
let Ok(out) = out else { return 0 };
|
|
let text = String::from_utf8_lossy(&out.stdout);
|
|
let mut size = 0u64;
|
|
let mut size_pack = 0u64;
|
|
for line in text.lines() {
|
|
if let Some(v) = line.strip_prefix("size: ") { size = v.trim().parse().unwrap_or(0); }
|
|
if let Some(v) = line.strip_prefix("size-pack: ") { size_pack = v.trim().parse().unwrap_or(0); }
|
|
}
|
|
size + size_pack
|
|
}
|
|
|
|
fn du_kb_excluding_git(path: &std::path::Path) -> u64 {
|
|
let out = std::process::Command::new("du")
|
|
.args(["-sk", "--exclude=.git"])
|
|
.arg(path)
|
|
.output();
|
|
let Ok(out) = out else { return 0 };
|
|
String::from_utf8_lossy(&out.stdout)
|
|
.split_whitespace()
|
|
.next()
|
|
.and_then(|s| s.parse().ok())
|
|
.unwrap_or(0)
|
|
}
|