relay_knowledge/code/index/
mod.rs1use std::{
4 collections::BTreeMap,
5 path::{Path, PathBuf},
6};
7
8mod deleted_symbols;
9pub(in crate::code) mod filesystem_delta;
10#[path = "full.rs"]
11mod full_snapshot;
12mod impact_paths;
13mod incremental;
14pub(in crate::code) mod plan;
15pub(in crate::code) mod snapshot;
16mod worktree_overlay;
17
18use crate::domain::{
19 CodeFileFingerprint, CodeIndexMode, CodeIndexResourceBudget, CodeIndexSnapshot,
20 CodeRepositoryRegistration, CodeRepositorySelector, CodeWorkspaceDetectionConfig,
21};
22
23#[cfg(test)]
24use crate::code::source::changes::GitChange;
25use crate::code::{
26 CodeIndexError, identity, ids, parser,
27 parser::parse_indexed_file,
28 source::{
29 self, changes,
30 changes::{TrackedEntryScope, diff_changes},
31 git, gitlink as source_gitlink,
32 layout::{self as scope, scoped_source_snapshot_for_filters},
33 resolution::resolve_repository_ref_with_filters,
34 source_commit_is_filesystem, source_kind,
35 },
36};
37pub use deleted_symbols::deleted_symbol_names_for_diff;
38pub(crate) use filesystem_delta::changed_paths_for_filesystem_diff;
39use full_snapshot::build_full_snapshot;
40#[cfg(test)]
41pub(crate) use full_snapshot::mutate_next_filesystem_full_snapshot_read;
42use incremental::{IncrementalSnapshotRequest, build_incremental_snapshot};
43pub use plan::{
44 CodeIndexPlan, prepare_full_index_plan, prepare_full_index_plan_with_workspace_detection,
45};
46use worktree_overlay::build_worktree_overlay_snapshot;
47
48pub(in crate::code) const MAX_INCREMENTAL_GITLINK_EXPANDED_PATHS: usize =
49 CodeIndexResourceBudget::DEFAULT_MAX_FILES_PER_BATCH;
50
51pub fn build_index_snapshot(
53 registration: &CodeRepositoryRegistration,
54 selector: &CodeRepositorySelector,
55 mode: CodeIndexMode,
56 previous_hashes: Vec<CodeFileFingerprint>,
57) -> Result<CodeIndexSnapshot, CodeIndexError> {
58 build_index_snapshot_with_base_commit(registration, selector, mode, previous_hashes, None)
59}
60
61pub(crate) fn build_index_snapshot_with_base_commit(
62 registration: &CodeRepositoryRegistration,
63 selector: &CodeRepositorySelector,
64 mode: CodeIndexMode,
65 previous_hashes: Vec<CodeFileFingerprint>,
66 base_resolved_commit_sha: Option<String>,
67) -> Result<CodeIndexSnapshot, CodeIndexError> {
68 build_index_snapshot_with_workspace_detection(
69 registration,
70 selector,
71 mode,
72 previous_hashes,
73 base_resolved_commit_sha,
74 &CodeWorkspaceDetectionConfig::default(),
75 )
76}
77
78pub(crate) fn build_index_snapshot_with_workspace_detection(
79 registration: &CodeRepositoryRegistration,
80 selector: &CodeRepositorySelector,
81 mode: CodeIndexMode,
82 previous_hashes: Vec<CodeFileFingerprint>,
83 base_resolved_commit_sha: Option<String>,
84 workspace_detection: &CodeWorkspaceDetectionConfig,
85) -> Result<CodeIndexSnapshot, CodeIndexError> {
86 let root = PathBuf::from(®istration.root_path);
87 let previous_hashes = previous_hashes
88 .into_iter()
89 .map(|fingerprint| (fingerprint.path, fingerprint.blob_hash))
90 .collect::<BTreeMap<_, _>>();
91
92 match mode {
93 CodeIndexMode::Full => {
94 build_full_snapshot(registration, selector, &root, workspace_detection)
95 }
96 CodeIndexMode::Incremental { base_ref, head_ref } => build_incremental_snapshot(
97 registration,
98 selector,
99 &root,
100 IncrementalSnapshotRequest {
101 base_ref: &base_ref,
102 head_ref: &head_ref,
103 previous_hashes: &previous_hashes,
104 base_resolved_commit_sha: base_resolved_commit_sha.as_deref(),
105 workspace_detection,
106 },
107 ),
108 CodeIndexMode::WorktreeOverlay => build_worktree_overlay_snapshot(
109 registration,
110 selector,
111 &root,
112 &previous_hashes,
113 base_resolved_commit_sha.as_deref(),
114 workspace_detection,
115 ),
116 }
117}
118
119pub fn changed_paths_for_diff(
120 root_path: impl AsRef<Path>,
121 base_ref: &str,
122 head_ref: &str,
123) -> Result<Vec<String>, CodeIndexError> {
124 changed_paths_for_diff_with_filters(root_path, base_ref, head_ref, &[], &[])
125}
126
127pub fn changed_paths_for_diff_with_path_filters(
128 root_path: impl AsRef<Path>,
129 base_ref: &str,
130 head_ref: &str,
131 path_filters: &[String],
132) -> Result<Vec<String>, CodeIndexError> {
133 changed_paths_for_diff_with_filters(root_path, base_ref, head_ref, path_filters, &[])
134}
135
136pub fn changed_paths_for_diff_with_filters(
137 root_path: impl AsRef<Path>,
138 base_ref: &str,
139 head_ref: &str,
140 path_filters: &[String],
141 language_filters: &[String],
142) -> Result<Vec<String>, CodeIndexError> {
143 if source_commit_is_filesystem(base_ref) || source_commit_is_filesystem(head_ref) {
144 if base_ref == head_ref {
145 return Ok(Vec::new());
146 }
147 let base_commit = resolve_repository_ref_with_filters(
148 root_path.as_ref(),
149 base_ref,
150 path_filters,
151 language_filters,
152 )?;
153 let head_commit = resolve_repository_ref_with_filters(
154 root_path.as_ref(),
155 head_ref,
156 path_filters,
157 language_filters,
158 )?;
159 if base_commit == head_commit {
160 return Ok(Vec::new());
161 }
162 let snapshot = scoped_source_snapshot_for_filters(
163 root_path.as_ref(),
164 head_ref,
165 path_filters,
166 language_filters,
167 )?;
168 return Ok(snapshot
169 .entries
170 .into_iter()
171 .map(|entry| entry.path)
172 .collect());
173 }
174 if source_kind(root_path.as_ref())?.is_filesystem() {
175 if base_ref == head_ref {
176 return Ok(Vec::new());
177 }
178 let snapshot = scoped_source_snapshot_for_filters(
179 root_path.as_ref(),
180 head_ref,
181 path_filters,
182 language_filters,
183 )?;
184 return Ok(snapshot
185 .entries
186 .into_iter()
187 .map(|entry| entry.path)
188 .collect());
189 }
190 let changes = diff_changes(root_path.as_ref(), base_ref, head_ref)?;
191
192 impact_paths::paths_from_changes_with_gitlinks(
193 root_path.as_ref(),
194 base_ref,
195 head_ref,
196 changes,
197 path_filters,
198 language_filters,
199 )
200}
201
202#[cfg(test)]
203pub(in crate::code) fn impact_paths_from_changes(changes: Vec<GitChange>) -> Vec<String> {
204 let mut paths = Vec::new();
205 for change in changes {
206 match change {
207 GitChange::AddedOrModified { path }
208 | GitChange::Deleted { path }
209 | GitChange::TypeChanged { path } => paths.push(path),
210 GitChange::Renamed { old_path, new_path } => {
211 paths.push(old_path);
212 paths.push(new_path);
213 }
214 GitChange::Copied { new_path, .. } => paths.push(new_path),
215 }
216 }
217 paths.sort();
218 paths.dedup();
219
220 paths
221}
222
223pub(crate) fn repository_uses_filesystem_source(
224 root_path: impl AsRef<Path>,
225) -> Result<bool, CodeIndexError> {
226 Ok(source_kind(root_path.as_ref())?.is_filesystem())
227}
228
229pub(in crate::code::index) fn tracked_entry_scope_for_selector(
230 registration: &CodeRepositoryRegistration,
231 selector: &CodeRepositorySelector,
232) -> TrackedEntryScope {
233 match scope::intersect_path_filters(®istration.path_filters, &selector.path_filters) {
234 Some(filters) => TrackedEntryScope::from_path_filters(filters.iter()),
235 None => TrackedEntryScope::empty(),
236 }
237}