Skip to main content

endringer_git/
backend.rs

1//! [`GitBackend`] — wraps `gix::ThreadSafeRepository` and implements [`VcsBackend`].
2
3use std::time::SystemTime;
4
5use anyhow::Result;
6use endringer_core::backend::VcsBackend;
7use endringer_core::types::{
8    AheadBehind, BlameEntry, BranchInfo, CommitId, CommitInfo, DiffSummary, SortOrder,
9    StashEntry, StatusDigest, SubmoduleInfo, TagInfo, WorktreeInfo, WorktreeStatus,
10};
11
12use crate::{blame, branch, commit, diff, graph, object, stash, status, submodule, tag, worktree};
13
14/// Git backend.
15///
16/// Uses [`gix::ThreadSafeRepository`] so the struct is natively `Send + Sync`
17/// without any mutex. Each method obtains a cheap thread-local repository view
18/// via [`gix::ThreadSafeRepository::to_thread_local`], eliminating serialization
19/// under concurrent async load.
20pub struct GitBackend {
21    inner: gix::ThreadSafeRepository,
22}
23
24impl GitBackend {
25    /// Opens or discovers a Git repository at or above `path`.
26    ///
27    /// Traverses parent directories until it finds a `.git` directory (or a
28    /// bare repository), so callers may pass any subdirectory of the worktree.
29    pub fn open(path: &std::path::Path) -> Result<Self> {
30        let inner = gix::discover(path)?.into_sync();
31        Ok(GitBackend { inner })
32    }
33}
34
35/// Obtains a thread-local [`gix::Repository`] view from the shared handle.
36///
37/// This is a zero-copy operation: no re-opening of files, no locking.
38macro_rules! repo {
39    ($self:expr) => {
40        $self.inner.to_thread_local()
41    };
42}
43
44impl VcsBackend for GitBackend {
45    fn status_digest(&self) -> Result<StatusDigest> {
46        commit::status_digest(&repo!(self))
47    }
48
49    fn local_branches(&self) -> Result<Vec<BranchInfo>> {
50        branch::local_branches(&repo!(self))
51    }
52
53    fn remote_branches(&self) -> Result<Vec<BranchInfo>> {
54        branch::remote_branches(&repo!(self))
55    }
56
57    fn list_commits(&self) -> Result<Vec<CommitInfo>> {
58        branch::list_commits(&repo!(self))
59    }
60
61    fn list_commits_sorted(&self, order: SortOrder) -> Result<Vec<CommitInfo>> {
62        branch::list_commits_sorted(&repo!(self), order)
63    }
64
65    fn log_since(&self, since: SystemTime, until: SystemTime) -> Result<Vec<CommitInfo>> {
66        branch::log_since(&repo!(self), since, until)
67    }
68
69    fn find_commit(&self, id: &CommitId) -> Result<CommitInfo> {
70        branch::find_commit(&repo!(self), id)
71    }
72
73    fn list_tags(&self) -> Result<Vec<TagInfo>> {
74        tag::list_tags(&repo!(self))
75    }
76
77    fn list_tags_sorted(&self, order: SortOrder) -> Result<Vec<TagInfo>> {
78        tag::list_tags_sorted(&repo!(self), order)
79    }
80
81    fn create_tag(&self, name: &str) -> Result<()> {
82        tag::create_tag(&repo!(self), name)
83    }
84
85    fn create_annotated_tag(&self, name: &str, message: &str) -> Result<()> {
86        tag::create_annotated_tag(&repo!(self), name, message)
87    }
88
89    fn delete_tag(&self, name: &str) -> Result<()> {
90        tag::delete_tag(&repo!(self), name)
91    }
92
93    fn diff(&self, from: &CommitId, to: &CommitId) -> Result<DiffSummary> {
94        diff::diff(&repo!(self), from, to)
95    }
96
97    fn remote_url(&self, name: &str) -> Option<String> {
98        let repo = repo!(self);
99        let remote = repo.find_remote(name).ok()?;
100        let url = remote.url(gix::remote::Direction::Fetch)?;
101        Some(url.to_bstring().to_string())
102    }
103
104    fn is_dirty(&self) -> Result<bool> {
105        status::is_dirty(&repo!(self))
106    }
107
108    fn merge_base(&self, a: &CommitId, b: &CommitId) -> Result<Option<CommitId>> {
109        graph::merge_base(&repo!(self), a, b)
110    }
111
112    fn is_ancestor(&self, candidate: &CommitId, descendant: &CommitId) -> Result<bool> {
113        graph::is_ancestor(&repo!(self), candidate, descendant)
114    }
115
116    fn ahead_behind(&self, local: &CommitId, upstream: &CommitId) -> Result<AheadBehind> {
117        graph::ahead_behind(&repo!(self), local, upstream)
118    }
119
120    fn branch_ahead_behind(&self, branch: &str) -> Result<Option<AheadBehind>> {
121        graph::branch_ahead_behind(&repo!(self), branch)
122    }
123
124    fn blame(&self, path: &std::path::Path) -> Result<Vec<BlameEntry>> {
125        blame::blame(&repo!(self), path)
126    }
127
128    fn worktree_status(&self) -> Result<WorktreeStatus> {
129        status::worktree_status(&repo!(self))
130    }
131
132    fn file_at_commit(&self, path: &std::path::Path, commit_id: &CommitId) -> Result<Vec<u8>> {
133        object::file_at_commit(&repo!(self), path, commit_id)
134    }
135
136    fn submodules(&self) -> Result<Vec<SubmoduleInfo>> {
137        submodule::submodules(&repo!(self))
138    }
139
140    fn stash_entries(&self) -> Result<Vec<StashEntry>> {
141        stash::stash_entries(&repo!(self))
142    }
143
144    fn worktrees(&self) -> Result<Vec<WorktreeInfo>> {
145        worktree::worktrees(&repo!(self))
146    }
147}