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