_diffctx/
candidate_files.rs1use std::path::{Path, PathBuf};
2
3use rayon::prelude::*;
4use rustc_hash::FxHashSet;
5use walkdir::WalkDir;
6
7use crate::config::graph_filtering::GRAPH_FILTERING;
8use crate::config::limits::LIMITS;
9use crate::git;
10use crate::languages::get_language_for_file;
11
12fn is_allowed_file(path: &Path) -> bool {
13 get_language_for_file(&path.to_string_lossy()).is_some()
14}
15
16fn is_candidate_file(
17 file_path: &Path,
18 _root_dir: &Path,
19 included_set: &FxHashSet<PathBuf>,
20) -> bool {
21 if !file_path.is_file() {
22 return false;
23 }
24 if !is_allowed_file(file_path) {
25 return false;
26 }
27 if included_set.contains(file_path) {
28 return false;
29 }
30 match file_path.metadata() {
31 Ok(meta) if meta.len() as usize > LIMITS.max_file_size => return false,
32 Err(_) => return false,
33 _ => {}
34 }
35 true
36}
37
38pub fn collect_candidate_files(root_dir: &Path, included_set: &FxHashSet<PathBuf>) -> Vec<PathBuf> {
39 if let Ok(parts) = git::run_git_z(root_dir, &["ls-files", "-z"]) {
40 let all_paths: Vec<PathBuf> = parts.into_iter().map(|f| root_dir.join(f)).collect();
41 let files: Vec<PathBuf> = all_paths
42 .into_par_iter()
43 .filter(|f| is_candidate_file(f, root_dir, included_set))
44 .collect();
45 return filter_ignored_and_secret(root_dir, files);
46 }
47
48 let mut fallback: Vec<PathBuf> = Vec::new();
49 for entry in WalkDir::new(root_dir)
50 .sort_by_file_name()
51 .into_iter()
52 .filter_entry(|e| {
53 if e.depth() == 0 || !e.file_type().is_dir() {
54 return true;
55 }
56 match e.file_name().to_str() {
57 Some(name) => {
58 !name.starts_with('.') && name != "node_modules" && name != "__pycache__"
59 }
60 None => true,
61 }
62 })
63 .filter_map(|e| e.ok())
64 {
65 if !entry.file_type().is_file() {
66 continue;
67 }
68 if fallback.len() >= GRAPH_FILTERING.fallback_max_files {
69 break;
70 }
71 let path = entry.into_path();
72 if is_candidate_file(&path, root_dir, included_set) {
73 fallback.push(path);
74 }
75 }
76 filter_ignored_and_secret(root_dir, fallback)
77}
78
79fn filter_ignored_and_secret(root_dir: &Path, files: Vec<PathBuf>) -> Vec<PathBuf> {
89 let rel_paths: Vec<String> = files
90 .iter()
91 .filter_map(|f| crate::pipeline::rel_path_string(root_dir, f))
92 .collect();
93 let ignored_rel_paths = git::find_ignored_paths(root_dir, &rel_paths);
94 files
95 .into_par_iter()
96 .filter(|f| {
97 !crate::pipeline::is_secret_path(f)
98 && !crate::pipeline::is_ignored_path(root_dir, f, &ignored_rel_paths)
99 })
100 .collect()
101}
102
103pub fn normalize_path(path: &Path, root_dir: &Path) -> PathBuf {
104 if path.is_absolute() {
105 path.canonicalize().unwrap_or_else(|_| path.to_path_buf())
106 } else {
107 let joined = root_dir.join(path);
108 joined.canonicalize().unwrap_or(joined)
109 }
110}