Skip to main content

relay_knowledge/application/code_repository/impact/
mod.rs

1//! Coordinates repository impact analysis at a resolved indexed scope.
2
3use std::{collections::BTreeMap, path::PathBuf};
4
5use crate::{
6    api::{ApiError, ApiMetadata, CodeRepositoryImpactResponse, RequestContext},
7    code::{
8        changed_paths_for_diff_with_filters, changed_paths_for_filesystem_diff,
9        deleted_symbol_names_for_diff, partition_changed_paths_for_selector,
10    },
11    domain::CodeImpactRequest,
12    storage::CodeImpactChanges,
13};
14
15use crate::application::service::RelayKnowledgeService;
16
17use super::{
18    blocking::run_blocking_code,
19    errors::storage_api_error,
20    repository::{registration_from_status, required_code_repository},
21    scope::{
22        indexed_source_scope, missing_indexed_source_scope_error, resolve_code_ref_for_selector,
23        resolved_code_scope_status,
24    },
25};
26
27impl RelayKnowledgeService {
28    /// Returns impact radius for a Git diff using the indexed code graph.
29    pub async fn impact_code_repository(
30        &self,
31        mut request: CodeImpactRequest,
32        context: RequestContext,
33    ) -> Result<CodeRepositoryImpactResponse, ApiError> {
34        let store = self.store().await.map_err(storage_api_error)?;
35        let status =
36            required_code_repository(store.as_ref(), &request.repository.repository).await?;
37        let head_commit =
38            resolve_code_ref_for_selector(&status, &request.repository, request.head_ref.clone())
39                .await?;
40        request.repository.ref_selector = head_commit.clone();
41        let scoped_status =
42            resolved_code_scope_status(&store, &status, &request.repository).await?;
43        let root = PathBuf::from(status.root_path.clone());
44        let base_ref = request.base_ref.clone();
45        let head_ref = head_commit.clone();
46        let path_filters = scoped_status.path_filters.clone();
47        let language_filters = scoped_status.language_filters.clone();
48        let base_fingerprints = if base_ref.starts_with("filesystem:") {
49            let mut base_selector = request.repository.clone();
50            base_selector.ref_selector = base_ref.clone();
51            match resolved_code_scope_status(&store, &status, &base_selector).await {
52                Ok(base_status) => match base_status.last_indexed_scope_id {
53                    Some(source_scope) => Some(
54                        store
55                            .code_file_fingerprints_for_scope(source_scope)
56                            .await
57                            .map_err(storage_api_error)?,
58                    ),
59                    None => None,
60                },
61                Err(_) => None,
62            }
63        } else {
64            None
65        };
66        let changed_paths = if let Some(base_fingerprints) = base_fingerprints {
67            run_blocking_code(move || {
68                let previous_hashes = base_fingerprints
69                    .into_iter()
70                    .map(|file| (file.path, file.blob_hash))
71                    .collect::<BTreeMap<_, _>>();
72                changed_paths_for_filesystem_diff(
73                    &root,
74                    &head_ref,
75                    &path_filters,
76                    &language_filters,
77                    &previous_hashes,
78                )
79            })
80            .await?
81        } else {
82            run_blocking_code(move || {
83                changed_paths_for_diff_with_filters(
84                    root,
85                    &base_ref,
86                    &head_ref,
87                    &path_filters,
88                    &language_filters,
89                )
90            })
91            .await?
92        };
93        let registration = registration_from_status(&status);
94        let path_groups = {
95            let registration = registration.clone();
96            let selector = request.repository.clone();
97            let changed_paths = changed_paths.clone();
98            run_blocking_code(move || {
99                partition_changed_paths_for_selector(&registration, &selector, changed_paths)
100            })
101            .await?
102        };
103        let selector = request.repository.clone();
104        let base_ref = request.base_ref.clone();
105        let head_ref = head_commit;
106        let deleted_symbol_names = run_blocking_code(move || {
107            deleted_symbol_names_for_diff(&registration, &selector, &base_ref, &head_ref)
108        })
109        .await?;
110        let source_scope = indexed_source_scope(&scoped_status)
111            .ok_or_else(|| missing_indexed_source_scope_error(&scoped_status))?;
112        let results = store
113            .analyze_code_impact_scope(
114                source_scope,
115                request.clone(),
116                CodeImpactChanges {
117                    paths: changed_paths.clone(),
118                    deleted_symbol_names,
119                },
120            )
121            .await
122            .map_err(storage_api_error)?;
123        let graph_version = store
124            .current_graph_version()
125            .await
126            .map_err(storage_api_error)?;
127        let scope = crate::api::CodeRepositoryScopeMetadata::from_status(
128            &scoped_status,
129            &request.repository,
130            request.head_ref.clone(),
131        );
132
133        Ok(CodeRepositoryImpactResponse {
134            metadata: ApiMetadata::graph_only(&context, graph_version),
135            scope,
136            request,
137            path_groups,
138            results,
139        })
140    }
141}