1use std::time::SystemTime;
4
5use endringer_core::error::{anyhow_to_backend, Error as CrateError, NotFoundKind, Result};
6use endringer_core::backend::VcsBackend;
7use endringer_core::types::{
8 AheadBehind, BlameEntry, BranchInfo, BranchTrackingInfo, CommitId, CommitInfo,
9 CommitQuery, CommitQueryResult, ConflictSummary, DiffSummary, OperationState,
10 RefInfo, RefKind, RemoteInfo, RepositoryInfo, RichWorktreeStatus, SortOrder,
11 StashDetail, StashEntry, StatusDigest, StatusOptions, SubmoduleInfo,
12 SubmoduleSummary, TagInfo, TreeEntry,
13 WorktreeDetail, WorktreeInfo, WorktreeStatus,
14};
15
16use crate::{blame, branch, commit, conflict, diff, graph, info, object, operation,
17 refs, stash, stash_detail, status, submodule, submodule_summary,
18 tag, tree, worktree, worktree_detail};
19
20pub struct GitBackend {
27 inner: gix::ThreadSafeRepository,
28}
29
30impl GitBackend {
31 pub fn open(path: &std::path::Path) -> anyhow::Result<Self> {
36 let inner = gix::discover(path)?.into_sync();
37 Ok(GitBackend { inner })
38 }
39}
40
41macro_rules! repo {
45 ($self:expr) => {
46 $self.inner.to_thread_local()
47 };
48}
49
50macro_rules! be {
52 ($e:expr) => {
53 $e.map_err(anyhow_to_backend)
54 };
55}
56
57impl VcsBackend for GitBackend {
58 fn status_digest(&self) -> Result<StatusDigest> {
59 be!(commit::status_digest(&repo!(self)))
60 }
61
62 fn local_branches(&self) -> Result<Vec<BranchInfo>> {
63 be!(branch::local_branches(&repo!(self)))
64 }
65
66 fn remote_branches(&self) -> Result<Vec<BranchInfo>> {
67 be!(branch::remote_branches(&repo!(self)))
68 }
69
70 fn list_commits(&self) -> Result<Vec<CommitInfo>> {
71 be!(branch::list_commits(&repo!(self)))
72 }
73
74 fn list_commits_sorted(&self, order: SortOrder) -> Result<Vec<CommitInfo>> {
75 be!(branch::list_commits_sorted(&repo!(self), order))
76 }
77
78 fn log_since(&self, since: SystemTime, until: SystemTime) -> Result<Vec<CommitInfo>> {
79 be!(branch::log_since(&repo!(self), since, until))
80 }
81
82 fn find_commit(&self, id: &CommitId) -> Result<CommitInfo> {
83 branch::find_commit(&repo!(self), id).map_err(|e| {
84 let msg = e.to_string();
85 if msg.contains("not found") || msg.contains("commit '") && msg.contains("not found") {
86 CrateError::NotFound { kind: NotFoundKind::Commit, name: id.to_string() }
87 } else if msg.contains("not a commit") {
88 CrateError::NotACommit { id: id.clone() }
89 } else {
90 anyhow_to_backend(e)
91 }
92 })
93 }
94
95 fn query_commits(&self, query: CommitQuery) -> Result<CommitQueryResult> {
96 be!(branch::query_commits(&repo!(self), query))
97 }
98
99 fn list_tags(&self) -> Result<Vec<TagInfo>> {
100 be!(tag::list_tags(&repo!(self)))
101 }
102
103 fn list_tags_sorted(&self, order: SortOrder) -> Result<Vec<TagInfo>> {
104 be!(tag::list_tags_sorted(&repo!(self), order))
105 }
106
107 fn create_tag(&self, name: &str) -> Result<()> {
108 be!(tag::create_tag(&repo!(self), name))
109 }
110
111 fn create_annotated_tag(&self, name: &str, message: &str) -> Result<()> {
112 be!(tag::create_annotated_tag(&repo!(self), name, message))
113 }
114
115 fn delete_tag(&self, name: &str) -> Result<()> {
116 be!(tag::delete_tag(&repo!(self), name))
117 }
118
119 fn diff(&self, from: &CommitId, to: &CommitId) -> Result<DiffSummary> {
120 be!(diff::diff(&repo!(self), from, to))
121 }
122
123 fn remote_url(&self, name: &str) -> Result<Option<String>> {
124 let repo = repo!(self);
125 let remote = match repo.find_remote(name) {
126 Ok(r) => r,
127 Err(e) => {
128 let msg = e.to_string();
129 if msg.contains("did not exist")
132 || msg.contains("not found")
133 || msg.contains("does not exist")
134 {
135 return Ok(None);
136 }
137 return Err(anyhow_to_backend(anyhow::anyhow!(msg)));
138 }
139 };
140 let url = remote.url(gix::remote::Direction::Fetch);
141 Ok(url.map(|u| u.to_bstring().to_string()))
142 }
143
144 fn is_dirty(&self) -> Result<bool> {
145 be!(status::is_dirty(&repo!(self)))
146 }
147
148 fn merge_base(&self, a: &CommitId, b: &CommitId) -> Result<Option<CommitId>> {
149 be!(graph::merge_base(&repo!(self), a, b))
150 }
151
152 fn is_ancestor(&self, candidate: &CommitId, descendant: &CommitId) -> Result<bool> {
153 be!(graph::is_ancestor(&repo!(self), candidate, descendant))
154 }
155
156 fn ahead_behind(&self, local: &CommitId, upstream: &CommitId) -> Result<AheadBehind> {
157 be!(graph::ahead_behind(&repo!(self), local, upstream))
158 }
159
160 fn branch_ahead_behind(&self, branch: &str) -> Result<Option<AheadBehind>> {
161 be!(graph::branch_ahead_behind(&repo!(self), branch))
162 }
163
164 fn repository_info(&self) -> Result<RepositoryInfo> {
165 be!(info::repository_info(&repo!(self), endringer_core::types::BackendKind::Git))
166 }
167
168 fn branch_tracking(&self, branch: &str) -> Result<BranchTrackingInfo> {
169 be!(branch::branch_tracking(&repo!(self), branch))
170 }
171
172 fn local_branch_tracking(&self) -> Result<Vec<BranchTrackingInfo>> {
173 be!(branch::local_branch_tracking(&repo!(self)))
174 }
175
176 fn is_merged_into(&self, b: &str, target: &str) -> Result<bool> {
177 be!(branch::is_merged_into(&repo!(self), b, target))
178 }
179
180 fn blame(&self, path: &std::path::Path) -> Result<Vec<BlameEntry>> {
181 be!(blame::blame(&repo!(self), path))
182 }
183
184 fn worktree_status(&self) -> Result<WorktreeStatus> {
185 be!(status::worktree_status(&repo!(self)))
186 }
187
188 fn rich_worktree_status(&self, options: StatusOptions) -> Result<RichWorktreeStatus> {
189 be!(status::rich_worktree_status(&repo!(self), options))
190 }
191
192 fn file_at_commit(&self, path: &std::path::Path, commit_id: &CommitId) -> Result<Vec<u8>> {
193 object::file_at_commit(&repo!(self), path, commit_id).map_err(|e| {
194 let msg = e.to_string();
195 if msg.contains("not found") || msg.contains("does not exist") {
196 CrateError::PathNotFound {
197 path: path.to_path_buf(),
198 commit: Some(commit_id.clone()),
199 }
200 } else {
201 anyhow_to_backend(e)
202 }
203 })
204 }
205
206 fn submodules(&self) -> Result<Vec<SubmoduleInfo>> {
207 be!(submodule::submodules(&repo!(self)))
208 }
209
210 fn stash_entries(&self) -> Result<Vec<StashEntry>> {
211 be!(stash::stash_entries(&repo!(self)))
212 }
213
214 fn worktrees(&self) -> Result<Vec<WorktreeInfo>> {
215 be!(worktree::worktrees(&repo!(self)))
216 }
217
218 fn operation_state(&self) -> Result<OperationState> {
219 be!(operation::operation_state(repo!(self).git_dir()))
220 }
221
222 fn unmerged_paths(&self) -> Result<Vec<std::path::PathBuf>> {
223 be!(conflict::unmerged_paths(&repo!(self)))
224 }
225
226 fn conflict_summary(&self) -> Result<ConflictSummary> {
227 be!(conflict::conflict_summary(&repo!(self)))
228 }
229
230 fn blame_at(&self, path: &std::path::Path, commit_id: &CommitId) -> Result<Vec<BlameEntry>> {
231 be!(blame::blame_at(&repo!(self), path, commit_id))
232 }
233
234 fn tree_at_commit(&self, commit_id: &CommitId) -> Result<Vec<TreeEntry>> {
235 be!(tree::tree_at_commit(&repo!(self), commit_id))
236 }
237
238 fn tree_at_path(&self, commit_id: &CommitId, path: &std::path::Path) -> Result<Vec<TreeEntry>> {
239 be!(tree::tree_at_path(&repo!(self), commit_id, path))
240 }
241
242 fn remotes(&self) -> Result<Vec<RemoteInfo>> {
243 be!(refs::remotes(&repo!(self)))
244 }
245
246 fn references(&self) -> Result<Vec<RefInfo>> {
247 be!(refs::references(&repo!(self)))
248 }
249
250 fn references_by_kind(&self, kind: RefKind) -> Result<Vec<RefInfo>> {
251 be!(refs::references_by_kind(&repo!(self), kind))
252 }
253
254 fn submodule_summaries(&self) -> Result<Vec<SubmoduleSummary>> {
255 be!(submodule_summary::submodule_summaries(&repo!(self)))
256 }
257
258 fn stash_detail(&self, index: usize) -> Result<StashDetail> {
259 be!(stash_detail::stash_detail(&repo!(self), index))
260 }
261
262 fn stash_diff(&self, index: usize) -> Result<DiffSummary> {
263 be!(stash_detail::stash_diff(&repo!(self), index))
264 }
265
266 fn worktree_details(&self) -> Result<Vec<WorktreeDetail>> {
267 be!(worktree_detail::worktree_details(&repo!(self)))
268 }
269}