use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use git2::{Commit, Repository, Tree};
use ignore::WalkBuilder;
use crate::analyzer::{parallel::CollectorBuilder, source::AnalysisSource};
pub enum Project<'a> {
Disk {
root: &'a Path,
},
Commit {
repo: &'a Repository,
commit: &'a Commit<'a>,
},
}
impl<'a> Project<'a> {
pub fn file_paths(&self) -> Vec<PathBuf> {
match self {
Project::Disk { root } => {
let root_abs = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
let collector: Arc<Mutex<Vec<PathBuf>>> = Arc::new(Mutex::new(Vec::new()));
let builder = WalkBuilder::new(&root_abs); builder.build_parallel().visit(&mut CollectorBuilder {
collector: collector.clone(),
});
let mut files = collector.lock().unwrap();
files.drain(..).collect()
}
Project::Commit { repo, commit } => {
let tree = commit.tree().expect("Failed to get commit hash");
collect_commit_files(repo, &tree, Path::new(""))
}
}
}
pub fn to_analysis_source(&self) -> AnalysisSource {
match self {
Project::Disk { root } => {
let root_abs = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
AnalysisSource::Disk { root: root_abs }
}
Project::Commit { repo, commit } => {
let repo_path = repo
.path()
.parent()
.unwrap_or_else(|| repo.path())
.to_path_buf();
AnalysisSource::Git {
repo_path,
commit_id: commit.id(),
}
}
}
}
}
fn collect_commit_files(repo: &Repository, tree: &Tree, prefix: &Path) -> Vec<PathBuf> {
let mut files = Vec::new();
let mut stack: Vec<(Tree, PathBuf)> = vec![(tree.clone(), prefix.to_path_buf())];
while let Some((current_tree, current_prefix)) = stack.pop() {
for entry in current_tree.iter() {
let name = match entry.name() {
Some(n) => n,
None => continue,
};
let path = current_prefix.join(name);
match entry.kind() {
Some(git2::ObjectType::Blob) => {
files.push(path);
}
Some(git2::ObjectType::Tree) => {
if let Ok(obj) = entry.to_object(repo) {
if let Ok(subtree) = obj.peel_to_tree() {
stack.push((subtree, path));
}
}
}
Some(git2::ObjectType::Commit) => {
let submodule_id = entry.id();
let sm_path = repo.workdir().unwrap_or_else(|| repo.path()).join(&path);
match Repository::open(&sm_path) {
Ok(sm_repo) => {
if let Ok(sm_commit_obj) = sm_repo.find_commit(submodule_id) {
if let Ok(sm_tree) = sm_commit_obj.tree() {
let sub_files = collect_commit_files(&sm_repo, &sm_tree, &path);
files.extend(sub_files);
}
}
}
Err(_) => {
eprintln!(
"Warning: submodule '{}' not found or not initialized, skipped.",
path.display()
);
}
}
}
_ => {}
}
}
}
files
}