use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use tracing::{info, info_span, warn};
#[tracing::instrument(level = "info", skip(user_paths), fields(user_path_count=user_paths.len(), repo_root=%repo_root.display()), err)]
pub fn resolve_filter_paths(user_paths: &[PathBuf], repo_root: &Path) -> Result<Vec<PathBuf>> {
let span = info_span!("path_filter.resolve");
let _guard = span.enter();
info!("resolving filter paths");
let paths_to_flatten = if !user_paths.is_empty() {
resolve_user_provided_paths(user_paths, repo_root)?
} else {
auto_detect_paths(repo_root)?
};
let normalized_filter_paths = normalize_paths(paths_to_flatten);
info!(
normalized_path_count = normalized_filter_paths.len(),
"path resolution completed"
);
Ok(normalized_filter_paths)
}
#[tracing::instrument(level = "info", skip(user_paths), fields(user_path_count=user_paths.len(), repo_root=%repo_root.display()), err)]
fn resolve_user_provided_paths(user_paths: &[PathBuf], repo_root: &Path) -> Result<Vec<PathBuf>> {
let span = info_span!("path_filter.resolve_user_paths");
let _guard = span.enter();
let current_dir = std::env::current_dir().context("Failed to get current working directory")?;
let mut resolved_paths = Vec::new();
for user_path in user_paths {
let path_span =
info_span!("path_filter.resolve_single_path", user_path=%user_path.display());
let _path_guard = path_span.enter();
let resolved_path = if user_path.is_absolute() {
user_path.clone()
} else {
current_dir.join(user_path)
};
match resolved_path.strip_prefix(repo_root) {
Ok(relative_to_repo) => {
let path = relative_to_repo.to_path_buf();
if !path.as_os_str().is_empty() {
info!(resolved_path=%path.display(), "path resolved successfully");
resolved_paths.push(path);
} else {
info!("path resolved to repository root, including all files");
}
}
Err(_) => {
warn!(user_path=%user_path.display(), repo_root=%repo_root.display(),
"path is outside repository, skipping");
}
}
}
Ok(resolved_paths)
}
#[tracing::instrument(level = "info", fields(repo_root=%repo_root.display()), err)]
fn auto_detect_paths(repo_root: &Path) -> Result<Vec<PathBuf>> {
let span = info_span!("path_filter.auto_detect");
let _guard = span.enter();
let current_dir = std::env::current_dir().context("Failed to get current working directory")?;
match current_dir.strip_prefix(repo_root) {
Ok(relative_path) => {
let path = relative_path.to_path_buf();
if path.as_os_str().is_empty() {
info!("current directory is repository root, including all files");
Ok(vec![])
} else {
info!(detected_path=%path.display(), "auto-detected subdirectory filter");
Ok(vec![path])
}
}
Err(_) => {
info!("current directory is outside repository, including all files");
Ok(vec![])
}
}
}
#[tracing::instrument(level = "info", skip(paths), fields(input_count=paths.len()))]
fn normalize_paths(paths: Vec<PathBuf>) -> Vec<PathBuf> {
let span = info_span!("path_filter.normalize");
let _guard = span.enter();
let normalized: Vec<PathBuf> = paths
.into_iter()
.map(|p| {
let mut components = p.components().collect::<Vec<_>>();
if let Some(std::path::Component::CurDir) = components.first() {
components.remove(0);
}
let mut result = PathBuf::new();
for component in components {
result.push(component);
}
result
})
.filter(|p| !p.as_os_str().is_empty())
.collect();
info!(
output_count = normalized.len(),
"path normalization completed"
);
normalized
}