Skip to main content

endringer_async/
async_api.rs

1//! [`AsyncRepository`] — async wrapper around [`endringer::repository::Repository`].
2
3use std::path::Path;
4use std::sync::Arc;
5use std::time::SystemTime;
6
7use endringer::Error as EndringerError;
8use endringer::Result;
9use endringer::repository::{Repository, jj_repository, repository};
10use endringer::{
11    AheadBehind, BlameEntry, BranchInfo, BranchTrackingInfo, CommitId, CommitInfo,
12    CommitQuery, CommitQueryResult, ConflictSummary, DiffSummary, OperationState,
13    RefInfo, RefKind, RemoteInfo, RepositoryInfo, RichWorktreeStatus, SortOrder,
14    StashDetail, StashEntry, StatusDigest, StatusOptions, SubmoduleInfo,
15    SubmoduleSummary, TagInfo, TreeEntry, WorktreeDetail, WorktreeInfo, WorktreeStatus,
16};
17
18/// Maps a tokio `JoinError` to `endringer::Error::TaskJoin`.
19fn join_err(e: tokio::task::JoinError) -> EndringerError {
20    EndringerError::TaskJoin { message: e.to_string() }
21}
22
23/// Async wrapper around [`Repository`].
24///
25/// Cloneable. Each method clones the inner `Arc<Repository>` and dispatches
26/// to `tokio::task::spawn_blocking` so that blocking VCS I/O does not stall
27/// the async executor.
28#[derive(Clone)]
29pub struct AsyncRepository {
30    inner: Arc<Repository>,
31}
32
33impl AsyncRepository {
34    /// Opens a Git repository at `path`.
35    pub async fn open(path: &Path) -> Result<Self> {
36        let path = path.to_path_buf();
37        let inner = tokio::task::spawn_blocking(move || repository(&path))
38            .await
39            .map_err(join_err)??;
40        Ok(AsyncRepository { inner: Arc::new(inner) })
41    }
42
43    /// Opens a Jujutsu repository at `path`.
44    pub async fn open_jj(path: &Path) -> Result<Self> {
45        let path = path.to_path_buf();
46        let inner = tokio::task::spawn_blocking(move || jj_repository(&path))
47            .await
48            .map_err(join_err)??;
49        Ok(AsyncRepository { inner: Arc::new(inner) })
50    }
51
52    // ── Status ─────────────────────────────────────────────────────────── //
53
54    pub async fn status_digest(&self) -> Result<StatusDigest> {
55        let r = Arc::clone(&self.inner);
56        tokio::task::spawn_blocking(move || r.status_digest()).await.map_err(join_err)?
57    }
58
59    // ── Branches ───────────────────────────────────────────────────────── //
60
61    pub async fn local_branches(&self) -> Result<Vec<BranchInfo>> {
62        let r = Arc::clone(&self.inner);
63        tokio::task::spawn_blocking(move || r.local_branches()).await.map_err(join_err)?
64    }
65
66    pub async fn remote_branches(&self) -> Result<Vec<BranchInfo>> {
67        let r = Arc::clone(&self.inner);
68        tokio::task::spawn_blocking(move || r.remote_branches()).await.map_err(join_err)?
69    }
70
71    // ── Commits ────────────────────────────────────────────────────────── //
72
73    pub async fn list_commits(&self) -> Result<Vec<CommitInfo>> {
74        let r = Arc::clone(&self.inner);
75        tokio::task::spawn_blocking(move || r.list_commits()).await.map_err(join_err)?
76    }
77
78    pub async fn list_commits_sorted(&self, order: SortOrder) -> Result<Vec<CommitInfo>> {
79        let r = Arc::clone(&self.inner);
80        tokio::task::spawn_blocking(move || r.list_commits_sorted(order)).await.map_err(join_err)?
81    }
82
83    pub async fn log_since(&self, since: SystemTime, until: SystemTime) -> Result<Vec<CommitInfo>> {
84        let r = Arc::clone(&self.inner);
85        tokio::task::spawn_blocking(move || r.log_since(since, until)).await.map_err(join_err)?
86    }
87
88    pub async fn find_commit(&self, id: CommitId) -> Result<CommitInfo> {
89        let r = Arc::clone(&self.inner);
90        tokio::task::spawn_blocking(move || r.find_commit(&id)).await.map_err(join_err)?
91    }
92
93    pub async fn query_commits(&self, query: CommitQuery) -> Result<CommitQueryResult> {
94        let r = Arc::clone(&self.inner);
95        tokio::task::spawn_blocking(move || r.query_commits(query)).await.map_err(join_err)?
96    }
97
98    // ── Tags ───────────────────────────────────────────────────────────── //
99
100    pub async fn list_tags(&self) -> Result<Vec<TagInfo>> {
101        let r = Arc::clone(&self.inner);
102        tokio::task::spawn_blocking(move || r.list_tags()).await.map_err(join_err)?
103    }
104
105    pub async fn list_tags_sorted(&self, order: SortOrder) -> Result<Vec<TagInfo>> {
106        let r = Arc::clone(&self.inner);
107        tokio::task::spawn_blocking(move || r.list_tags_sorted(order)).await.map_err(join_err)?
108    }
109
110    pub async fn create_tag(&self, name: String) -> Result<()> {
111        let r = Arc::clone(&self.inner);
112        tokio::task::spawn_blocking(move || r.create_tag(&name)).await.map_err(join_err)?
113    }
114
115    pub async fn create_annotated_tag(&self, name: String, message: String) -> Result<()> {
116        let r = Arc::clone(&self.inner);
117        tokio::task::spawn_blocking(move || r.create_annotated_tag(&name, &message)).await.map_err(join_err)?
118    }
119
120    pub async fn delete_tag(&self, name: String) -> Result<()> {
121        let r = Arc::clone(&self.inner);
122        tokio::task::spawn_blocking(move || r.delete_tag(&name)).await.map_err(join_err)?
123    }
124
125    // ── Diff ───────────────────────────────────────────────────────────── //
126
127    pub async fn diff(&self, from: CommitId, to: CommitId) -> Result<DiffSummary> {
128        let r = Arc::clone(&self.inner);
129        tokio::task::spawn_blocking(move || r.diff(&from, &to)).await.map_err(join_err)?
130    }
131
132    // ── Remotes ────────────────────────────────────────────────────────── //
133
134    pub async fn remote_url(&self, name: String) -> Result<Option<String>> {
135        let r = Arc::clone(&self.inner);
136        tokio::task::spawn_blocking(move || r.remote_url(&name))
137            .await
138            .map_err(join_err)?
139    }
140
141    // ── Working tree ───────────────────────────────────────────────────── //
142
143    pub async fn is_dirty(&self) -> Result<bool> {
144        let r = Arc::clone(&self.inner);
145        tokio::task::spawn_blocking(move || r.is_dirty()).await.map_err(join_err)?
146    }
147
148    // ── Commit graph ───────────────────────────────────────────────────── //
149
150    pub async fn merge_base(&self, a: CommitId, b: CommitId) -> Result<Option<CommitId>> {
151        let r = Arc::clone(&self.inner);
152        tokio::task::spawn_blocking(move || r.merge_base(&a, &b)).await.map_err(join_err)?
153    }
154
155    pub async fn is_ancestor(&self, candidate: CommitId, descendant: CommitId) -> Result<bool> {
156        let r = Arc::clone(&self.inner);
157        tokio::task::spawn_blocking(move || r.is_ancestor(&candidate, &descendant)).await.map_err(join_err)?
158    }
159
160    // ── Blame ──────────────────────────────────────────────────────────── //
161
162    pub async fn blame(&self, path: std::path::PathBuf) -> Result<Vec<BlameEntry>> {
163        let r = Arc::clone(&self.inner);
164        tokio::task::spawn_blocking(move || r.blame(&path)).await.map_err(join_err)?
165    }
166
167    pub async fn worktree_status(&self) -> Result<WorktreeStatus> {
168        let r = Arc::clone(&self.inner);
169        tokio::task::spawn_blocking(move || r.worktree_status()).await.map_err(join_err)?
170    }
171
172    pub async fn rich_worktree_status(&self, options: StatusOptions) -> Result<RichWorktreeStatus> {
173        let r = Arc::clone(&self.inner);
174        tokio::task::spawn_blocking(move || r.rich_worktree_status(options)).await.map_err(join_err)?
175    }
176
177    pub async fn file_at_commit(
178        &self,
179        path: std::path::PathBuf,
180        commit_id: CommitId,
181    ) -> Result<Vec<u8>> {
182        let r = Arc::clone(&self.inner);
183        tokio::task::spawn_blocking(move || r.file_at_commit(&path, &commit_id)).await.map_err(join_err)?
184    }
185
186    pub async fn submodules(&self) -> Result<Vec<SubmoduleInfo>> {
187        let r = Arc::clone(&self.inner);
188        tokio::task::spawn_blocking(move || r.submodules()).await.map_err(join_err)?
189    }
190
191    pub async fn stash_entries(&self) -> Result<Vec<StashEntry>> {
192        let r = Arc::clone(&self.inner);
193        tokio::task::spawn_blocking(move || r.stash_entries()).await.map_err(join_err)?
194    }
195
196    pub async fn worktrees(&self) -> Result<Vec<WorktreeInfo>> {
197        let r = Arc::clone(&self.inner);
198        tokio::task::spawn_blocking(move || r.worktrees()).await.map_err(join_err)?
199    }
200
201    // ── Ahead / behind ─────────────────────────────────────────────────── //
202
203    pub async fn ahead_behind(
204        &self,
205        local: CommitId,
206        upstream: CommitId,
207    ) -> Result<AheadBehind> {
208        let r = Arc::clone(&self.inner);
209        tokio::task::spawn_blocking(move || r.ahead_behind(&local, &upstream)).await.map_err(join_err)?
210    }
211
212    pub async fn branch_ahead_behind(
213        &self,
214        branch: String,
215    ) -> Result<Option<AheadBehind>> {
216        let r = Arc::clone(&self.inner);
217        tokio::task::spawn_blocking(move || r.branch_ahead_behind(&branch)).await.map_err(join_err)?
218    }
219
220    // ── Repository info ────────────────────────────────────────────────── //
221
222    pub async fn repository_info(&self) -> Result<RepositoryInfo> {
223        let r = Arc::clone(&self.inner);
224        tokio::task::spawn_blocking(move || r.repository_info()).await.map_err(join_err)?
225    }
226
227    // ── Branch tracking ────────────────────────────────────────────────── //
228
229    pub async fn branch_tracking(&self, branch: String) -> Result<BranchTrackingInfo> {
230        let r = Arc::clone(&self.inner);
231        tokio::task::spawn_blocking(move || r.branch_tracking(&branch)).await.map_err(join_err)?
232    }
233
234    pub async fn local_branch_tracking(&self) -> Result<Vec<BranchTrackingInfo>> {
235        let r = Arc::clone(&self.inner);
236        tokio::task::spawn_blocking(move || r.local_branch_tracking()).await.map_err(join_err)?
237    }
238
239    pub async fn is_merged_into(&self, branch: String, target: String) -> Result<bool> {
240        let r = Arc::clone(&self.inner);
241        tokio::task::spawn_blocking(move || r.is_merged_into(&branch, &target)).await.map_err(join_err)?
242    }
243
244    // ── Operation and conflict state ───────────────────────────────────── //
245
246    pub async fn operation_state(&self) -> Result<OperationState> {
247        let r = Arc::clone(&self.inner);
248        tokio::task::spawn_blocking(move || r.operation_state()).await.map_err(join_err)?
249    }
250
251    pub async fn unmerged_paths(&self) -> Result<Vec<std::path::PathBuf>> {
252        let r = Arc::clone(&self.inner);
253        tokio::task::spawn_blocking(move || r.unmerged_paths()).await.map_err(join_err)?
254    }
255
256    pub async fn conflict_summary(&self) -> Result<ConflictSummary> {
257        let r = Arc::clone(&self.inner);
258        tokio::task::spawn_blocking(move || r.conflict_summary()).await.map_err(join_err)?
259    }
260
261    // ── Point-in-time reads ────────────────────────────────────────────── //
262
263    pub async fn blame_at(&self, path: std::path::PathBuf, commit_id: CommitId) -> Result<Vec<BlameEntry>> {
264        let r = Arc::clone(&self.inner);
265        tokio::task::spawn_blocking(move || r.blame_at(&path, &commit_id)).await.map_err(join_err)?
266    }
267
268    pub async fn tree_at_commit(&self, commit_id: CommitId) -> Result<Vec<TreeEntry>> {
269        let r = Arc::clone(&self.inner);
270        tokio::task::spawn_blocking(move || r.tree_at_commit(&commit_id)).await.map_err(join_err)?
271    }
272
273    pub async fn tree_at_path(&self, commit_id: CommitId, path: std::path::PathBuf) -> Result<Vec<TreeEntry>> {
274        let r = Arc::clone(&self.inner);
275        tokio::task::spawn_blocking(move || r.tree_at_path(&commit_id, &path)).await.map_err(join_err)?
276    }
277
278    // ── Remote and reference inventory ────────────────────────────────── //
279
280    pub async fn remotes(&self) -> Result<Vec<RemoteInfo>> {
281        let r = Arc::clone(&self.inner);
282        tokio::task::spawn_blocking(move || r.remotes()).await.map_err(join_err)?
283    }
284
285    pub async fn references(&self) -> Result<Vec<RefInfo>> {
286        let r = Arc::clone(&self.inner);
287        tokio::task::spawn_blocking(move || r.references()).await.map_err(join_err)?
288    }
289
290    pub async fn references_by_kind(&self, kind: RefKind) -> Result<Vec<RefInfo>> {
291        let r = Arc::clone(&self.inner);
292        tokio::task::spawn_blocking(move || r.references_by_kind(kind)).await.map_err(join_err)?
293    }
294
295    // ── Rich detail (RFC 019/020/021) ──────────────────────────────────── //
296
297    pub async fn submodule_summaries(&self) -> Result<Vec<SubmoduleSummary>> {
298        let r = Arc::clone(&self.inner);
299        tokio::task::spawn_blocking(move || r.submodule_summaries()).await.map_err(join_err)?
300    }
301
302    pub async fn stash_detail(&self, index: usize) -> Result<StashDetail> {
303        let r = Arc::clone(&self.inner);
304        tokio::task::spawn_blocking(move || r.stash_detail(index)).await.map_err(join_err)?
305    }
306
307    pub async fn stash_diff(&self, index: usize) -> Result<DiffSummary> {
308        let r = Arc::clone(&self.inner);
309        tokio::task::spawn_blocking(move || r.stash_diff(index)).await.map_err(join_err)?
310    }
311
312    pub async fn worktree_details(&self) -> Result<Vec<WorktreeDetail>> {
313        let r = Arc::clone(&self.inner);
314        tokio::task::spawn_blocking(move || r.worktree_details()).await.map_err(join_err)?
315    }
316}