pub mod deps;
mod exclude;
pub mod gitcli;
mod import_resolver;
mod libgit;
mod progress;
mod snapshot_builder;
mod types;
use anyhow::{Context, Result};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::snapshot::{
Author, AuthorId, BlameLine, FileComplexity, FileEntry, RepoSnapshot, TimeWindow,
};
pub use exclude::is_excluded;
pub use progress::{NoProgress, Progress};
pub use types::CommitCollection;
pub struct Collector {
repo: git2::Repository,
pub time_window: TimeWindow,
}
impl Collector {
pub fn open(path: &Path, time_window: TimeWindow) -> Result<Self> {
let repo = git2::Repository::discover(path).with_context(|| {
format!(
"'{}' is not a git repository. Run from a repo root or pass a path.",
path.display()
)
})?;
Ok(Collector { repo, time_window })
}
pub fn repo_name(&self) -> String {
if let Ok(remote) = self.repo.find_remote("origin") {
if let Some(url) = remote.url() {
let segment = url.rsplit('/').next().unwrap_or(url);
let segment = segment.rsplit(':').next().unwrap_or(segment);
let name = segment.trim_end_matches(".git");
if !name.is_empty() {
return name.to_string();
}
}
}
self.repo
.workdir()
.and_then(|p| p.file_name())
.and_then(|n| n.to_str())
.unwrap_or("unknown")
.to_string()
}
pub fn default_branch(&self) -> String {
self.repo
.head()
.ok()
.and_then(|h| h.shorthand().map(String::from))
.unwrap_or_else(|| "main".to_string())
}
pub fn head_commit_hash(&self) -> Result<String> {
let head = self.repo.head().context("Failed to get HEAD")?;
let oid = head.target().context("HEAD has no target")?;
Ok(oid.to_string())
}
pub fn collect_commits(&self) -> Result<CommitCollection> {
libgit::collect_commits(&self.repo, &self.time_window)
}
pub fn collect_files(&self) -> Result<Vec<FileEntry>> {
libgit::collect_files(&self.repo)
}
pub fn collect_blame(
&self,
files: &[FileEntry],
authors: &[Author],
raw_email_to_id: &HashMap<String, AuthorId>,
progress: &dyn Progress,
) -> Result<HashMap<PathBuf, Vec<BlameLine>>> {
gitcli::collect_blame(self.repo_path(), files, authors, raw_email_to_id, progress)
}
pub fn collect_blame_cached(
&self,
files: &[FileEntry],
authors: &[Author],
raw_email_to_id: &HashMap<String, AuthorId>,
cache: &crate::cache::blame::BlameCache,
progress: &dyn Progress,
) -> Result<(
HashMap<PathBuf, Vec<BlameLine>>,
crate::cache::blame::BlameCache,
)> {
gitcli::collect_blame_cached(
self.repo_path(),
files,
authors,
raw_email_to_id,
cache,
progress,
)
}
pub fn is_shallow(&self) -> bool {
gitcli::is_shallow_clone(self.repo_path())
}
pub fn collect_file_metrics(&self, files: &[FileEntry]) -> HashMap<PathBuf, FileComplexity> {
let (metrics, _) = self.collect_file_metrics_with_progress(files, &NoProgress);
metrics
}
pub fn collect_snapshot(&self) -> Result<RepoSnapshot> {
self.collect_snapshot_with_progress(false)
}
pub fn collect_snapshot_with_progress(&self, show_progress: bool) -> Result<RepoSnapshot> {
self.collect_snapshot_inner(show_progress, false, false, false, &[], &[], true)
}
#[allow(clippy::too_many_arguments)]
pub fn collect_snapshot_verbose(
&self,
show_progress: bool,
verbose: bool,
skip_blame: bool,
no_cache: bool,
exclude_patterns: &[String],
exclude_extensions: &[String],
use_default_excludes: bool,
) -> Result<RepoSnapshot> {
self.collect_snapshot_inner(
show_progress,
verbose,
skip_blame,
no_cache,
exclude_patterns,
exclude_extensions,
use_default_excludes,
)
}
pub fn repo_path(&self) -> &Path {
self.repo.workdir().unwrap_or_else(|| self.repo.path())
}
}