use std::path::{Path, PathBuf};
use git2::{Index, Repository};
use crate::error::HoldError;
pub fn discover_tracked_files(
repo_path: &Path,
) -> Result<(PathBuf, Vec<PathBuf>, usize), HoldError> {
let repo = Repository::discover(repo_path)
.map_err(|_| HoldError::RepoNotFound(repo_path.to_path_buf()))?;
let repo_root = repo
.workdir()
.ok_or_else(|| HoldError::RepoNotFound(repo_path.to_path_buf()))?
.to_path_buf();
let index = repo.index().map_err(HoldError::IndexError)?;
let (tracked_files, symlink_count) = collect_index_paths(&index, &repo_root)?;
Ok((repo_root, tracked_files, symlink_count))
}
fn collect_index_paths(
index: &Index,
repo_root: &Path,
) -> Result<(Vec<PathBuf>, usize), HoldError> {
let mut paths = Vec::new();
let mut symlink_count = 0;
for entry in index.iter() {
if entry.mode == 0o160000 {
continue;
}
let path = entry.path;
let path_str = std::str::from_utf8(&path).map_err(|e| HoldError::InvalidPath {
message: format!("Invalid UTF-8 in path: {e}"),
})?;
let path_buf = PathBuf::from(path_str);
let full_path = repo_root.join(&path_buf);
match std::fs::symlink_metadata(&full_path) {
Ok(metadata) => {
if metadata.is_symlink() {
symlink_count += 1;
continue; }
}
Err(e) => {
eprintln!(
"Warning: Could not access file '{}': {}. Skipping.",
full_path.display(),
e
);
continue; }
}
paths.push(path_buf);
}
Ok((paths, symlink_count))
}
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::TempDir;
use super::*;
fn setup_test_repo() -> (TempDir, Repository) {
let temp_dir = TempDir::new().unwrap();
let repo = Repository::init(temp_dir.path()).unwrap();
let test_file = temp_dir.path().join("test.txt");
fs::write(&test_file, "test content").unwrap();
let mut index = repo.index().unwrap();
index.add_path(Path::new("test.txt")).unwrap();
index.write().unwrap();
(temp_dir, repo)
}
#[test]
fn test_discover_tracked_files() {
let (temp_dir, _repo) = setup_test_repo();
let (repo_root, files, symlink_count) = discover_tracked_files(temp_dir.path()).unwrap();
assert_eq!(
repo_root.canonicalize().unwrap(),
temp_dir.path().canonicalize().unwrap()
);
assert_eq!(files.len(), 1);
assert!(files[0].ends_with("test.txt"));
assert_eq!(symlink_count, 0);
}
#[test]
fn test_repo_not_found() {
let temp_dir = TempDir::new().unwrap();
let result = discover_tracked_files(temp_dir.path());
assert!(matches!(result, Err(HoldError::RepoNotFound { .. })));
}
}