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