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