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