use std::{
path::PathBuf,
sync::{Arc, LazyLock, OnceLock},
thread,
};
use anyhow::Result;
use crate::git::GitRepo;
pub struct HookFileCache {
pub repo: Arc<GitRepo>,
hook_name: String,
staged_files: OnceLock<Vec<PathBuf>>,
all_files: OnceLock<Vec<PathBuf>>,
push_files: OnceLock<Vec<PathBuf>>,
}
impl HookFileCache {
pub fn new(repo: Arc<GitRepo>, hook_name: &str) -> Self {
HookFileCache {
repo,
hook_name: hook_name.to_string(),
staged_files: OnceLock::new(),
all_files: OnceLock::new(),
push_files: OnceLock::new(),
}
}
pub fn precompute(self: &Arc<Self>) {
if self.hook_name == "pre-commit" {
let cache_clone = self.clone();
thread::spawn(move || {
tracing::trace!("🚀 Starting parallel git precomputation for pre-commit");
let _ = cache_clone.get_staged_files();
});
}
}
pub fn get_staged_files(&self) -> &[PathBuf] {
self.staged_files.get_or_init(|| {
if self.hook_name == "pre-commit" {
let start = std::time::Instant::now();
tracing::trace!("📁 Loading staged files for pre-commit hook");
let result = self.repo.get_staged_files().unwrap_or_default();
let elapsed = start.elapsed();
if elapsed.as_millis() == 0 {
tracing::trace!(
"⚡ Staged files loaded in {}μs ({} files)",
elapsed.as_micros(),
result.len()
);
} else {
tracing::trace!(
"⚡ Staged files loaded in {:?} ({} files)",
elapsed,
result.len()
);
}
result
} else {
Vec::new()
}
})
}
pub fn get_all_files(&self) -> &[PathBuf] {
self.all_files.get_or_init(|| {
if self.hook_name == "pre-commit" || self.hook_name == "pre-push" {
let start = std::time::Instant::now();
tracing::trace!("📁 Loading all files for {} hook", self.hook_name);
let result = self.repo.get_all_files().unwrap_or_default();
let elapsed = start.elapsed();
if elapsed.as_millis() == 0 {
tracing::trace!(
"⚡ All files loaded in {}μs ({} files)",
elapsed.as_micros(),
result.len()
);
} else {
tracing::trace!(
"⚡ All files loaded in {:?} ({} files)",
elapsed,
result.len()
);
}
result
} else {
Vec::new()
}
})
}
pub fn get_push_files(&self) -> &[PathBuf] {
self.push_files.get_or_init(|| {
if self.hook_name == "pre-push" {
let start = std::time::Instant::now();
tracing::trace!("📁 Loading push files for pre-push hook");
let result = self
.repo
.get_push_files("origin", "main")
.unwrap_or_default();
tracing::trace!(
"⚡ Lazy push files loaded in {:?} ({})",
start.elapsed(),
result.len()
);
result
} else {
Vec::new()
}
})
}
}
static GIT_REPO: LazyLock<Option<Arc<GitRepo>>> = LazyLock::new(|| {
let start = std::time::Instant::now();
let result = GitRepo::discover();
match &result {
Ok(_) => {
let elapsed = start.elapsed();
if elapsed.as_millis() == 0 {
tracing::trace!(
"⚡ Git repo discovery (gix) completed in {}μs",
elapsed.as_micros()
);
} else {
tracing::trace!("⚡ Git repo discovery (gix) completed in {:?}", elapsed);
}
}
Err(e) => tracing::debug!("Git repo discovery failed: {}", e),
}
result.ok().map(Arc::new)
});
pub fn get_cached_git_repo() -> Result<Arc<GitRepo>> {
GIT_REPO
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Not in a git repository"))
.map(Arc::clone)
}