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