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