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;
40pub(crate) use full_snapshot::clean_worktree_overlay_hash;
41#[cfg(test)]
42pub(crate) use full_snapshot::mutate_next_filesystem_full_snapshot_read;
43use incremental::{IncrementalSnapshotRequest, build_incremental_snapshot};
44pub use plan::{
45 CodeIndexPlan, prepare_full_index_plan, prepare_full_index_plan_with_workspace_detection,
46};
47use worktree_overlay::build_worktree_overlay_snapshot;
48
49pub(in crate::code) const MAX_INCREMENTAL_GITLINK_EXPANDED_PATHS: usize =
50 CodeIndexResourceBudget::DEFAULT_MAX_FILES_PER_BATCH;
51
52pub fn build_index_snapshot(
54 registration: &CodeRepositoryRegistration,
55 selector: &CodeRepositorySelector,
56 mode: CodeIndexMode,
57 previous_hashes: Vec<CodeFileFingerprint>,
58) -> Result<CodeIndexSnapshot, CodeIndexError> {
59 build_index_snapshot_with_base_commit(registration, selector, mode, previous_hashes, None)
60}
61
62pub(crate) fn build_index_snapshot_with_base_commit(
63 registration: &CodeRepositoryRegistration,
64 selector: &CodeRepositorySelector,
65 mode: CodeIndexMode,
66 previous_hashes: Vec<CodeFileFingerprint>,
67 base_resolved_commit_sha: Option<String>,
68) -> Result<CodeIndexSnapshot, CodeIndexError> {
69 build_index_snapshot_with_workspace_detection(
70 registration,
71 selector,
72 mode,
73 previous_hashes,
74 base_resolved_commit_sha,
75 &CodeWorkspaceDetectionConfig::default(),
76 )
77}
78
79pub(crate) fn build_index_snapshot_with_workspace_detection(
80 registration: &CodeRepositoryRegistration,
81 selector: &CodeRepositorySelector,
82 mode: CodeIndexMode,
83 previous_hashes: Vec<CodeFileFingerprint>,
84 base_resolved_commit_sha: Option<String>,
85 workspace_detection: &CodeWorkspaceDetectionConfig,
86) -> Result<CodeIndexSnapshot, CodeIndexError> {
87 let root = PathBuf::from(®istration.root_path);
88 let previous_hashes = previous_hashes
89 .into_iter()
90 .map(|fingerprint| (fingerprint.path, fingerprint.blob_hash))
91 .collect::<BTreeMap<_, _>>();
92
93 match mode {
94 CodeIndexMode::Full => {
95 build_full_snapshot(registration, selector, &root, workspace_detection)
96 }
97 CodeIndexMode::Incremental { base_ref, head_ref } => build_incremental_snapshot(
98 registration,
99 selector,
100 &root,
101 IncrementalSnapshotRequest {
102 base_ref: &base_ref,
103 head_ref: &head_ref,
104 previous_hashes: &previous_hashes,
105 base_resolved_commit_sha: base_resolved_commit_sha.as_deref(),
106 workspace_detection,
107 },
108 ),
109 CodeIndexMode::WorktreeOverlay => build_worktree_overlay_snapshot(
110 registration,
111 selector,
112 &root,
113 &previous_hashes,
114 base_resolved_commit_sha.as_deref(),
115 workspace_detection,
116 ),
117 }
118}
119
120pub(crate) fn compute_worktree_overlay_identity(
121 registration: &CodeRepositoryRegistration,
122 selector: &CodeRepositorySelector,
123 previous_hashes: Vec<CodeFileFingerprint>,
124 base_resolved_commit_sha: Option<String>,
125) -> Result<(String, String), CodeIndexError> {
126 let root = PathBuf::from(®istration.root_path);
127 let previous_hashes = previous_hashes
128 .into_iter()
129 .map(|fingerprint| (fingerprint.path, fingerprint.blob_hash))
130 .collect::<BTreeMap<_, _>>();
131
132 worktree_overlay::worktree_overlay_identity(
133 registration,
134 selector,
135 &root,
136 &previous_hashes,
137 base_resolved_commit_sha.as_deref(),
138 )
139}
140
141pub fn changed_paths_for_diff(
142 root_path: impl AsRef<Path>,
143 base_ref: &str,
144 head_ref: &str,
145) -> Result<Vec<String>, CodeIndexError> {
146 changed_paths_for_diff_with_filters(root_path, base_ref, head_ref, &[], &[])
147}
148
149pub fn changed_paths_for_diff_with_path_filters(
150 root_path: impl AsRef<Path>,
151 base_ref: &str,
152 head_ref: &str,
153 path_filters: &[String],
154) -> Result<Vec<String>, CodeIndexError> {
155 changed_paths_for_diff_with_filters(root_path, base_ref, head_ref, path_filters, &[])
156}
157
158pub fn changed_paths_for_diff_with_filters(
159 root_path: impl AsRef<Path>,
160 base_ref: &str,
161 head_ref: &str,
162 path_filters: &[String],
163 language_filters: &[String],
164) -> Result<Vec<String>, CodeIndexError> {
165 if source_commit_is_filesystem(base_ref) || source_commit_is_filesystem(head_ref) {
166 if base_ref == head_ref {
167 return Ok(Vec::new());
168 }
169 let base_commit = resolve_repository_ref_with_filters(
170 root_path.as_ref(),
171 base_ref,
172 path_filters,
173 language_filters,
174 )?;
175 let head_commit = resolve_repository_ref_with_filters(
176 root_path.as_ref(),
177 head_ref,
178 path_filters,
179 language_filters,
180 )?;
181 if base_commit == head_commit {
182 return Ok(Vec::new());
183 }
184 let snapshot = scoped_source_snapshot_for_filters(
185 root_path.as_ref(),
186 head_ref,
187 path_filters,
188 language_filters,
189 )?;
190 return Ok(snapshot
191 .entries
192 .into_iter()
193 .map(|entry| entry.path)
194 .collect());
195 }
196 if source_kind(root_path.as_ref())?.is_filesystem() {
197 if base_ref == head_ref {
198 return Ok(Vec::new());
199 }
200 let snapshot = scoped_source_snapshot_for_filters(
201 root_path.as_ref(),
202 head_ref,
203 path_filters,
204 language_filters,
205 )?;
206 return Ok(snapshot
207 .entries
208 .into_iter()
209 .map(|entry| entry.path)
210 .collect());
211 }
212 let changes = diff_changes(root_path.as_ref(), base_ref, head_ref)?;
213
214 impact_paths::paths_from_changes_with_gitlinks(
215 root_path.as_ref(),
216 base_ref,
217 head_ref,
218 changes,
219 path_filters,
220 language_filters,
221 )
222}
223
224#[cfg(test)]
225pub(in crate::code) fn impact_paths_from_changes(changes: Vec<GitChange>) -> Vec<String> {
226 let mut paths = Vec::new();
227 for change in changes {
228 match change {
229 GitChange::AddedOrModified { path }
230 | GitChange::Deleted { path }
231 | GitChange::TypeChanged { path } => paths.push(path),
232 GitChange::Renamed { old_path, new_path } => {
233 paths.push(old_path);
234 paths.push(new_path);
235 }
236 GitChange::Copied { new_path, .. } => paths.push(new_path),
237 }
238 }
239 paths.sort();
240 paths.dedup();
241
242 paths
243}
244
245pub(crate) fn repository_uses_filesystem_source(
246 root_path: impl AsRef<Path>,
247) -> Result<bool, CodeIndexError> {
248 Ok(source_kind(root_path.as_ref())?.is_filesystem())
249}
250
251pub(in crate::code::index) fn tracked_entry_scope_for_selector(
252 registration: &CodeRepositoryRegistration,
253 selector: &CodeRepositorySelector,
254) -> TrackedEntryScope {
255 match scope::intersect_path_filters(®istration.path_filters, &selector.path_filters) {
256 Some(filters) => TrackedEntryScope::from_path_filters(filters.iter()),
257 None => TrackedEntryScope::empty(),
258 }
259}