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;
10mod full_snapshot;
11mod impact_paths;
12mod incremental;
13pub(in crate::code) mod plan;
14pub(in crate::code) mod snapshot;
15mod worktree_overlay;
16
17use crate::domain::{
18 CodeFileFingerprint, CodeIndexMode, CodeIndexResourceBudget, CodeIndexSnapshot,
19 CodeRepositoryRegistration, CodeRepositorySelector, CodeWorkspaceDetectionConfig,
20};
21
22#[cfg(test)]
23use crate::code::source::changes::GitChange;
24use crate::code::{
25 CodeIndexError, identity, ids, parser,
26 parser::parse_indexed_file,
27 source::{
28 self, changes,
29 changes::{TrackedEntryScope, diff_changes},
30 git, gitlink as source_gitlink,
31 layout::{self as scope, scoped_source_snapshot_for_filters},
32 resolution::resolve_repository_ref_with_filters,
33 source_commit_is_filesystem, source_kind,
34 },
35};
36pub use deleted_symbols::deleted_symbol_names_for_diff;
37pub(crate) use filesystem_delta::changed_paths_for_filesystem_diff;
38use full_snapshot::build_full_snapshot;
39pub(crate) use full_snapshot::clean_worktree_overlay_hash;
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(crate) fn compute_worktree_overlay_identity(
120 registration: &CodeRepositoryRegistration,
121 selector: &CodeRepositorySelector,
122 previous_hashes: Vec<CodeFileFingerprint>,
123 base_resolved_commit_sha: Option<String>,
124) -> Result<(String, String), CodeIndexError> {
125 let root = PathBuf::from(®istration.root_path);
126 let previous_hashes = previous_hashes
127 .into_iter()
128 .map(|fingerprint| (fingerprint.path, fingerprint.blob_hash))
129 .collect::<BTreeMap<_, _>>();
130
131 worktree_overlay::worktree_overlay_identity(
132 registration,
133 selector,
134 &root,
135 &previous_hashes,
136 base_resolved_commit_sha.as_deref(),
137 )
138}
139
140pub fn changed_paths_for_diff(
141 root_path: impl AsRef<Path>,
142 base_ref: &str,
143 head_ref: &str,
144) -> Result<Vec<String>, CodeIndexError> {
145 changed_paths_for_diff_with_filters(root_path, base_ref, head_ref, &[], &[])
146}
147
148pub fn changed_paths_for_diff_with_path_filters(
149 root_path: impl AsRef<Path>,
150 base_ref: &str,
151 head_ref: &str,
152 path_filters: &[String],
153) -> Result<Vec<String>, CodeIndexError> {
154 changed_paths_for_diff_with_filters(root_path, base_ref, head_ref, path_filters, &[])
155}
156
157pub fn changed_paths_for_diff_with_filters(
158 root_path: impl AsRef<Path>,
159 base_ref: &str,
160 head_ref: &str,
161 path_filters: &[String],
162 language_filters: &[String],
163) -> Result<Vec<String>, CodeIndexError> {
164 if source_commit_is_filesystem(base_ref) || source_commit_is_filesystem(head_ref) {
165 if base_ref == head_ref {
166 return Ok(Vec::new());
167 }
168 let base_commit = resolve_repository_ref_with_filters(
169 root_path.as_ref(),
170 base_ref,
171 path_filters,
172 language_filters,
173 )?;
174 let head_commit = resolve_repository_ref_with_filters(
175 root_path.as_ref(),
176 head_ref,
177 path_filters,
178 language_filters,
179 )?;
180 if base_commit == head_commit {
181 return Ok(Vec::new());
182 }
183 let snapshot = scoped_source_snapshot_for_filters(
184 root_path.as_ref(),
185 head_ref,
186 path_filters,
187 language_filters,
188 )?;
189 return Ok(snapshot
190 .entries
191 .into_iter()
192 .map(|entry| entry.path)
193 .collect());
194 }
195 if source_kind(root_path.as_ref())?.is_filesystem() {
196 if base_ref == head_ref {
197 return Ok(Vec::new());
198 }
199 let snapshot = scoped_source_snapshot_for_filters(
200 root_path.as_ref(),
201 head_ref,
202 path_filters,
203 language_filters,
204 )?;
205 return Ok(snapshot
206 .entries
207 .into_iter()
208 .map(|entry| entry.path)
209 .collect());
210 }
211 let changes = diff_changes(root_path.as_ref(), base_ref, head_ref)?;
212
213 impact_paths::paths_from_changes_with_gitlinks(
214 root_path.as_ref(),
215 base_ref,
216 head_ref,
217 changes,
218 path_filters,
219 language_filters,
220 )
221}
222
223#[cfg(test)]
224pub(in crate::code) fn impact_paths_from_changes(changes: Vec<GitChange>) -> Vec<String> {
225 let mut paths = Vec::new();
226 for change in changes {
227 match change {
228 GitChange::AddedOrModified { path }
229 | GitChange::Deleted { path }
230 | GitChange::TypeChanged { path } => paths.push(path),
231 GitChange::Renamed { old_path, new_path } => {
232 paths.push(old_path);
233 paths.push(new_path);
234 }
235 GitChange::Copied { new_path, .. } => paths.push(new_path),
236 }
237 }
238 paths.sort();
239 paths.dedup();
240
241 paths
242}
243
244pub(crate) fn repository_uses_filesystem_source(
245 root_path: impl AsRef<Path>,
246) -> Result<bool, CodeIndexError> {
247 Ok(source_kind(root_path.as_ref())?.is_filesystem())
248}
249
250pub(in crate::code::index) fn tracked_entry_scope_for_selector(
251 registration: &CodeRepositoryRegistration,
252 selector: &CodeRepositorySelector,
253) -> TrackedEntryScope {
254 match scope::intersect_path_filters(®istration.path_filters, &selector.path_filters) {
255 Some(filters) => TrackedEntryScope::from_path_filters(filters.iter()),
256 None => TrackedEntryScope::empty(),
257 }
258}