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 = required_code_repository(&store, &request.repository.repository).await?;
36        let head_commit =
37            resolve_code_ref_for_selector(&status, &request.repository, request.head_ref.clone())
38                .await?;
39        request.repository.ref_selector = head_commit.clone();
40        let scoped_status =
41            resolved_code_scope_status(&store, &status, &request.repository).await?;
42        let root = PathBuf::from(status.root_path.clone());
43        let base_ref = request.base_ref.clone();
44        let head_ref = head_commit.clone();
45        let path_filters = scoped_status.path_filters.clone();
46        let language_filters = scoped_status.language_filters.clone();
47        let base_fingerprints = if base_ref.starts_with("filesystem:") {
48            let mut base_selector = request.repository.clone();
49            base_selector.ref_selector = base_ref.clone();
50            match resolved_code_scope_status(&store, &status, &base_selector).await {
51                Ok(base_status) => match base_status.last_indexed_scope_id {
52                    Some(source_scope) => Some(
53                        store
54                            .code_file_fingerprints_for_scope(source_scope)
55                            .await
56                            .map_err(storage_api_error)?,
57                    ),
58                    None => None,
59                },
60                Err(_) => None,
61            }
62        } else {
63            None
64        };
65        let changed_paths = if let Some(base_fingerprints) = base_fingerprints {
66            run_blocking_code(move || {
67                let previous_hashes = base_fingerprints
68                    .into_iter()
69                    .map(|file| (file.path, file.blob_hash))
70                    .collect::<BTreeMap<_, _>>();
71                changed_paths_for_filesystem_diff(
72                    &root,
73                    &head_ref,
74                    &path_filters,
75                    &language_filters,
76                    &previous_hashes,
77                )
78            })
79            .await?
80        } else {
81            run_blocking_code(move || {
82                changed_paths_for_diff_with_filters(
83                    root,
84                    &base_ref,
85                    &head_ref,
86                    &path_filters,
87                    &language_filters,
88                )
89            })
90            .await?
91        };
92        let registration = registration_from_status(&status);
93        let path_groups = {
94            let registration = registration.clone();
95            let selector = request.repository.clone();
96            let changed_paths = changed_paths.clone();
97            run_blocking_code(move || {
98                partition_changed_paths_for_selector(&registration, &selector, changed_paths)
99            })
100            .await?
101        };
102        let selector = request.repository.clone();
103        let base_ref = request.base_ref.clone();
104        let head_ref = head_commit;
105        let deleted_symbol_names = run_blocking_code(move || {
106            deleted_symbol_names_for_diff(&registration, &selector, &base_ref, &head_ref)
107        })
108        .await?;
109        let source_scope = indexed_source_scope(&scoped_status)
110            .ok_or_else(|| missing_indexed_source_scope_error(&scoped_status))?;
111        let results = store
112            .analyze_code_impact_scope(
113                source_scope,
114                request.clone(),
115                CodeImpactChanges {
116                    paths: changed_paths.clone(),
117                    deleted_symbol_names,
118                },
119            )
120            .await
121            .map_err(storage_api_error)?;
122        let graph_version = store
123            .current_graph_version()
124            .await
125            .map_err(storage_api_error)?;
126        let scope = crate::api::CodeRepositoryScopeMetadata::from_status(
127            &scoped_status,
128            &request.repository,
129            request.head_ref.clone(),
130        );
131
132        Ok(CodeRepositoryImpactResponse {
133            metadata: ApiMetadata::graph_only(&context, graph_version),
134            scope,
135            request,
136            path_groups,
137            results,
138        })
139    }
140}