relay_knowledge/code/index/deleted_symbols/
mod.rs1use std::{
4 collections::BTreeSet,
5 path::{Path, PathBuf},
6};
7
8use crate::domain::{CodeRepositoryRegistration, CodeRepositorySelector};
9
10use super::{MAX_INCREMENTAL_GITLINK_EXPANDED_PATHS, tracked_entry_scope_for_selector};
11use crate::code::{
12 CodeIndexError,
13 changes::{self, GitChange, diff_changes, tracked_entries_with_scope},
14 git::resolve_ref,
15 parser::parse_indexed_file,
16 scope::{self, discover_source_layout, path_is_selected_with_layout, path_scope_overlaps},
17 snapshot::SnapshotBuild,
18 source::{
19 gitlink as source_gitlink, gitlink::paths as source_gitlink_paths,
20 source_bytes_after_content_verification, source_commit_is_filesystem, source_kind,
21 },
22};
23
24pub fn deleted_symbol_names_for_diff(
26 registration: &CodeRepositoryRegistration,
27 selector: &CodeRepositorySelector,
28 base_ref: &str,
29 head_ref: &str,
30) -> Result<Vec<String>, CodeIndexError> {
31 let root = PathBuf::from(®istration.root_path);
32 if source_commit_is_filesystem(base_ref) || source_commit_is_filesystem(head_ref) {
33 return Ok(Vec::new());
34 }
35 if source_kind(&root)?.is_filesystem() {
36 return Ok(Vec::new());
37 }
38 let base_commit = resolve_ref(&root, base_ref)?;
39 let head_commit = resolve_ref(&root, head_ref)?;
40 let changes = diff_changes(&root, base_ref, head_ref)?;
41 let entry_scope = tracked_entry_scope_for_selector(registration, selector);
42 let base_entries = tracked_entries_with_scope(&root, &base_commit, &entry_scope)?;
43 let source_layout = discover_source_layout(&base_entries);
44 let context = DeletedSymbolContext {
45 registration,
46 selector,
47 root: &root,
48 base_commit: &base_commit,
49 source_layout: &source_layout,
50 };
51 let mut names = Vec::new();
52
53 for change in changes {
54 match change {
55 GitChange::Deleted { path } | GitChange::Renamed { old_path: path, .. } => {
56 append_deleted_symbol_names_for_removed_path(
57 &mut names,
58 &context,
59 &base_entries,
60 &path,
61 )?;
62 }
63 GitChange::AddedOrModified { path } | GitChange::TypeChanged { path } => {
64 append_deleted_symbol_names_for_gitlink_update(
65 &mut names,
66 &context,
67 &head_commit,
68 &path,
69 )?;
70 }
71 GitChange::Copied { .. } => {}
72 }
73 }
74 names.sort();
75 names.dedup();
76
77 Ok(names)
78}
79
80struct DeletedSymbolContext<'a> {
81 registration: &'a CodeRepositoryRegistration,
82 selector: &'a CodeRepositorySelector,
83 root: &'a Path,
84 base_commit: &'a str,
85 source_layout: &'a scope::SourceLayoutDiscovery,
86}
87
88fn append_deleted_symbol_names_for_removed_path(
89 names: &mut Vec<String>,
90 context: &DeletedSymbolContext<'_>,
91 base_entries: &[changes::GitTreeEntry],
92 path: &str,
93) -> Result<(), CodeIndexError> {
94 if source_gitlink::gitlink_commit_at_tree(context.root, context.base_commit, path)?.is_some() {
95 let include_expanded_path = |path: &str| {
96 path_is_selected_with_layout(
97 path,
98 context.registration,
99 context.selector,
100 context.source_layout,
101 )
102 };
103 let paths = source_gitlink_paths::bounded_expanded_paths_under_with_selector(
104 base_entries,
105 path,
106 MAX_INCREMENTAL_GITLINK_EXPANDED_PATHS,
107 &source_gitlink::GitlinkPathSelector::new(
108 &include_expanded_path,
109 &include_expanded_path,
110 ),
111 )?;
112 for path in paths {
113 append_deleted_symbol_names_for_path(names, context, context.base_commit, &path)?;
114 }
115 return Ok(());
116 }
117
118 append_deleted_symbol_names_for_path(names, context, context.base_commit, path)
119}
120
121fn append_deleted_symbol_names_for_gitlink_update(
122 names: &mut Vec<String>,
123 context: &DeletedSymbolContext<'_>,
124 head_commit: &str,
125 path: &str,
126) -> Result<(), CodeIndexError> {
127 if !path_scope_overlaps(path, context.registration, context.selector) {
128 return Ok(());
129 }
130 let include_expanded_path = |path: &str| {
131 path_is_selected_with_layout(
132 path,
133 context.registration,
134 context.selector,
135 context.source_layout,
136 )
137 };
138 let expanded_scope_overlaps =
139 |path: &str| path_scope_overlaps(path, context.registration, context.selector);
140 let child_filters = |path: &str| {
141 scope::submodule_child_scope_filters(path, context.registration, context.selector)
142 };
143 let Some(expansion) = source_gitlink::changed_gitlink_path_expansion(
144 context.root,
145 path,
146 context.base_commit,
147 head_commit,
148 MAX_INCREMENTAL_GITLINK_EXPANDED_PATHS,
149 &source_gitlink::GitlinkPathSelector::new_with_child_filters(
150 &include_expanded_path,
151 &expanded_scope_overlaps,
152 &child_filters,
153 ),
154 )?
155 else {
156 return Ok(());
157 };
158 if !expansion.base_is_gitlink {
159 append_deleted_symbol_names_for_path(names, context, context.base_commit, path)?;
160 return Ok(());
161 }
162 if expansion.base_paths.is_empty() {
163 return Ok(());
164 }
165 for path in expansion.base_paths {
166 if !path_is_selected_with_layout(
167 &path,
168 context.registration,
169 context.selector,
170 context.source_layout,
171 ) {
172 continue;
173 }
174 let mut removed = symbol_names_for_path(context, context.base_commit, &path)?;
175 if expansion.head_paths.contains(&path) {
176 let retained = symbol_names_for_path(context, head_commit, &path)?;
177 removed.retain(|name| !retained.contains(name));
178 }
179 names.extend(removed);
180 }
181
182 Ok(())
183}
184
185fn append_deleted_symbol_names_for_path(
186 names: &mut Vec<String>,
187 context: &DeletedSymbolContext<'_>,
188 commit: &str,
189 path: &str,
190) -> Result<(), CodeIndexError> {
191 if !path_is_selected_with_layout(
192 path,
193 context.registration,
194 context.selector,
195 context.source_layout,
196 ) {
197 return Ok(());
198 }
199 names.extend(symbol_names_for_path(context, commit, path)?);
200
201 Ok(())
202}
203
204fn symbol_names_for_path(
205 context: &DeletedSymbolContext<'_>,
206 commit: &str,
207 path: &str,
208) -> Result<BTreeSet<String>, CodeIndexError> {
209 let bytes = source_bytes_after_content_verification(context.root, commit, path, None)?;
210 let mut build = SnapshotBuild::new_with_selector(
211 context.registration,
212 context.selector,
213 commit.to_owned(),
214 "deleted-symbol-seed".to_owned(),
215 true,
216 1,
217 0,
218 );
219 parse_indexed_file(&mut build, path, &bytes)?;
220
221 Ok(build
222 .symbols
223 .into_iter()
224 .map(|symbol| symbol.name)
225 .collect())
226}