Skip to main content

relay_knowledge/application/code_repository/repository/
mod.rs

1#[cfg(test)]
2#[path = "mod_tests.rs"]
3mod mod_tests;
4mod staleness;
5mod status;
6#[cfg(test)]
7#[path = "test_support.rs"]
8mod test_support;
9mod worktree;
10#[cfg(test)]
11#[path = "worktree_review_tests.rs"]
12mod worktree_review_tests;
13
14use crate::{
15    api::{
16        ApiError, ApiMetadata, CodeRepositoryListResponse, CodeRepositoryRegisterRequest,
17        CodeRepositoryRegisterResponse, CodeRepositoryRemoveResponse, CodeRepositoryReportResponse,
18        CodeRepositoryStatusResponse, RequestContext,
19    },
20    code::{REGISTRATION_LANGUAGE_FILTER_ERROR, register_repository},
21    domain::CodeRepositorySelector,
22};
23
24use crate::application::service::RelayKnowledgeService;
25
26use super::{
27    blocking::run_blocking_code, clock::now_millis, errors::storage_api_error,
28    indexing::recover_code_index_task_leases,
29};
30
31pub(super) use staleness::annotate_query_result_staleness;
32pub(super) use status::{
33    code_status_checkpoint, registration_from_status, required_code_repository,
34};
35pub(super) use worktree::ensure_worktree_overlay_matches_current_worktree;
36
37impl RelayKnowledgeService {
38    /// Lists repositories that have at least one completed indexed scope.
39    pub async fn list_indexed_code_repositories(
40        &self,
41        context: RequestContext,
42    ) -> Result<CodeRepositoryListResponse, ApiError> {
43        let store = self.store().await.map_err(storage_api_error)?;
44        let repositories = store
45            .list_code_repositories()
46            .await
47            .map_err(storage_api_error)?
48            .into_iter()
49            .filter(|status| status.last_indexed_scope_id.is_some())
50            .collect();
51        let graph_version = store
52            .current_graph_version()
53            .await
54            .map_err(storage_api_error)?;
55
56        Ok(CodeRepositoryListResponse {
57            metadata: ApiMetadata::graph_only(&context, graph_version),
58            repositories,
59        })
60    }
61
62    /// Registers a Git repository as a code source.
63    pub async fn register_code_repository(
64        &self,
65        request: CodeRepositoryRegisterRequest,
66        context: RequestContext,
67    ) -> Result<CodeRepositoryRegisterResponse, ApiError> {
68        if !request.language_filters.is_empty() {
69            return Err(ApiError::invalid_argument(
70                REGISTRATION_LANGUAGE_FILTER_ERROR,
71            ));
72        }
73        let registration = run_blocking_code(move || {
74            register_repository(
75                request.root_path,
76                request.alias,
77                request.path_filters,
78                request.language_filters,
79            )
80        })
81        .await?;
82        let store = self.store().await.map_err(storage_api_error)?;
83        let status = store
84            .upsert_code_repository(registration.clone())
85            .await
86            .map_err(storage_api_error)?;
87        let _ = self.refresh_watched_code_repository(&status).await;
88        let graph_version = store
89            .current_graph_version()
90            .await
91            .map_err(storage_api_error)?;
92
93        Ok(CodeRepositoryRegisterResponse {
94            metadata: ApiMetadata::graph_only(&context, graph_version),
95            registration,
96            status,
97        })
98    }
99
100    /// Removes a registered code repository and its derived index state.
101    pub async fn remove_code_repository(
102        &self,
103        repository: String,
104        context: RequestContext,
105    ) -> Result<CodeRepositoryRemoveResponse, ApiError> {
106        let store = self.store().await.map_err(storage_api_error)?;
107        let now_ms = now_millis();
108        recover_code_index_task_leases(&store, now_ms).await?;
109        let removed_status = required_code_repository(&store, &repository).await?;
110        let summary = store
111            .remove_code_repository(removed_status.repository_id.clone(), now_ms)
112            .await
113            .map_err(storage_api_error)?
114            .ok_or_else(|| {
115                ApiError::storage_unavailable("removed code repository disappeared before delete")
116            })?;
117        let _ = self
118            .remove_watched_code_repository(&removed_status.alias, &removed_status.repository_id)
119            .await;
120        let graph_version = store
121            .current_graph_version()
122            .await
123            .map_err(storage_api_error)?;
124
125        Ok(CodeRepositoryRemoveResponse {
126            metadata: ApiMetadata::graph_only(&context, graph_version),
127            removed_status,
128            summary,
129        })
130    }
131
132    pub async fn code_repository_status(
133        &self,
134        selector: CodeRepositorySelector,
135        context: RequestContext,
136    ) -> Result<CodeRepositoryStatusResponse, ApiError> {
137        let store = self.store().await.map_err(storage_api_error)?;
138        let status = required_code_repository(&store, &selector.repository).await?;
139        recover_code_index_task_leases(&store, now_millis()).await?;
140        let active_task = store
141            .active_code_index_task(status.repository_id.clone())
142            .await
143            .map_err(storage_api_error)?;
144        let checkpoint = code_status_checkpoint(&store, &status, active_task.as_ref()).await?;
145        let retention = store
146            .code_scope_retention(status.repository_id.clone())
147            .await
148            .map_err(storage_api_error)?;
149        let graph_version = store
150            .current_graph_version()
151            .await
152            .map_err(storage_api_error)?;
153
154        Ok(CodeRepositoryStatusResponse {
155            metadata: ApiMetadata::graph_only(&context, graph_version),
156            status,
157            active_task,
158            checkpoint,
159            retention,
160        })
161    }
162
163    pub(crate) async fn code_repository_is_registered(
164        &self,
165        repository: String,
166    ) -> Result<bool, ApiError> {
167        let selector = CodeRepositorySelector::new(repository, "HEAD", Vec::new(), Vec::new())
168            .map_err(|error| ApiError::invalid_argument(error.to_string()))?;
169        let store = self.store().await.map_err(storage_api_error)?;
170        store
171            .code_repository_status(selector.repository)
172            .await
173            .map(|status| status.is_some())
174            .map_err(storage_api_error)
175    }
176
177    /// Builds a reusable operations report for a registered code repository.
178    pub async fn code_repository_report(
179        &self,
180        selector: CodeRepositorySelector,
181        context: RequestContext,
182    ) -> Result<CodeRepositoryReportResponse, ApiError> {
183        let store = self.store().await.map_err(storage_api_error)?;
184        let status = required_code_repository(&store, &selector.repository).await?;
185        let report = store
186            .code_repository_report(status.repository_id.clone())
187            .await
188            .map_err(storage_api_error)?;
189        let graph_version = store
190            .current_graph_version()
191            .await
192            .map_err(storage_api_error)?;
193
194        Ok(CodeRepositoryReportResponse {
195            metadata: ApiMetadata::graph_only(&context, graph_version),
196            scope: crate::api::CodeRepositoryScopeMetadata::from_status(
197                &status,
198                &selector,
199                selector.ref_selector.clone(),
200            ),
201            report,
202        })
203    }
204}