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