Skip to main content

rskit_git/
repo.rs

1//! Repository orchestrator that delegates to libgit2 and Git CLI implementations.
2
3use std::path::Path;
4use std::sync::Arc;
5
6use rskit_errors::AppResult;
7
8use crate::auth::AuthProvider;
9use crate::cli;
10use crate::core::{Executor, Repository};
11use crate::embedded;
12use crate::manage::{ConfigReader, Maintainer, RefManager, RemoteManager};
13use crate::options::{
14    BlameOptions, CheckoutOptions, CleanOptions, CommitOptions, DescribeOptions, FetchOptions,
15    GrepOptions, InitOptions, LogOptions, MergeOptions, PushOptions, RebaseOptions,
16};
17use crate::read::{Blamer, Differ, IgnoreReader, IndexReader, Inspector, LogReader, TreeReader};
18use crate::types::{
19    BlameLine, Branch, BranchFilter, Commit, DiffEntry, DiffStats, GrepMatch, IndexEntry,
20    MergeResult, Oid, RebaseResult, Reference, Remote, ResetMode, StashEntry, StatusEntry, Tag,
21    TreeEntry, TreeHash,
22};
23use crate::write::{
24    CheckoutManager, CherryPicker, Committer, IndexManager, Merger, Rebaser, Resetter, Stasher,
25};
26
27/// Repository facade that combines embedded and CLI capabilities.
28pub struct Repo {
29    embedded: embedded::Git2Repository,
30    cli: cli::GitCli,
31}
32
33impl Repo {
34    fn new(embedded: embedded::Git2Repository) -> Self {
35        let cli = cli::GitCli::new(embedded.root().to_path_buf());
36        Self { embedded, cli }
37    }
38}
39
40/// Opens a git repository at the given path (canonicalized).
41pub fn open(path: impl AsRef<Path>) -> AppResult<Repo> {
42    embedded::open(path).map(Repo::new)
43}
44
45/// Discovers a git repository by walking up from the given path.
46pub fn discover(path: impl AsRef<Path>) -> AppResult<Repo> {
47    embedded::discover(path).map(Repo::new)
48}
49
50/// Opens a git repository at the given path with an explicit auth provider.
51///
52/// The provider supplies transport credentials (for example a token read from
53/// caller-named environment variables) to network operations such as push and
54/// fetch on the embedded backend.
55pub fn open_with_auth(path: impl AsRef<Path>, auth: Arc<dyn AuthProvider>) -> AppResult<Repo> {
56    embedded::open_with_auth(path, auth).map(Repo::new)
57}
58
59/// Discovers a git repository by walking up from `path` with an explicit auth provider.
60pub fn discover_with_auth(path: impl AsRef<Path>, auth: Arc<dyn AuthProvider>) -> AppResult<Repo> {
61    embedded::discover_with_auth(path, auth).map(Repo::new)
62}
63
64/// Clones a repository into the given path.
65pub fn clone(url: &str, path: impl AsRef<Path>) -> AppResult<Repo> {
66    embedded::clone(url, path).map(Repo::new)
67}
68
69/// Initializes a new git repository at the given path.
70///
71/// The initial branch is [`DEFAULT_BRANCH`](crate::DEFAULT_BRANCH) regardless
72/// of the host's Git configuration; use [`init_with`] to choose another name.
73pub fn init(path: impl AsRef<Path>) -> AppResult<Repo> {
74    embedded::init(path).map(Repo::new)
75}
76
77/// Initializes a new git repository at the given path with explicit options.
78pub fn init_with(path: impl AsRef<Path>, options: &InitOptions) -> AppResult<Repo> {
79    embedded::init_with(path, options).map(Repo::new)
80}
81
82/// Initializes a new bare git repository at the given path.
83///
84/// The initial branch is [`DEFAULT_BRANCH`](crate::DEFAULT_BRANCH), matching [`init`].
85pub fn init_bare(path: impl AsRef<Path>) -> AppResult<Repo> {
86    embedded::init_bare(path).map(Repo::new)
87}
88
89impl Repository for Repo {
90    fn root(&self) -> &Path {
91        self.embedded.root()
92    }
93
94    fn head(&self) -> AppResult<Reference> {
95        self.embedded.head()
96    }
97
98    fn resolve_ref(&self, refname: &str) -> AppResult<Oid> {
99        self.embedded.resolve_ref(refname)
100    }
101
102    fn is_dirty(&self) -> AppResult<bool> {
103        self.embedded.is_dirty()
104    }
105}
106
107impl Executor for Repo {
108    fn exec(&self, args: &[&str]) -> AppResult<Vec<u8>> {
109        self.cli.exec(args)
110    }
111}
112
113impl Differ for Repo {
114    fn diff(&self, from: &str, to: &str) -> AppResult<Vec<DiffEntry>> {
115        self.embedded.diff(from, to)
116    }
117
118    fn diff_stats(&self, from: &str, to: &str) -> AppResult<DiffStats> {
119        self.embedded.diff_stats(from, to)
120    }
121
122    fn status(&self) -> AppResult<Vec<StatusEntry>> {
123        self.embedded.status()
124    }
125}
126
127impl IgnoreReader for Repo {
128    fn is_ignored(&self, path: &str) -> AppResult<bool> {
129        self.embedded.is_ignored(path)
130    }
131}
132
133impl TreeReader for Repo {
134    fn tree_hash(&self, revision: &str, path: &str) -> AppResult<TreeHash> {
135        self.embedded.tree_hash(revision, path)
136    }
137
138    fn file_at(&self, revision: &str, path: &str) -> AppResult<Vec<u8>> {
139        self.embedded.file_at(revision, path)
140    }
141
142    fn list_entries(&self, revision: &str, path: &str) -> AppResult<Vec<TreeEntry>> {
143        self.embedded.list_entries(revision, path)
144    }
145}
146
147impl IndexReader for Repo {
148    fn index_entry(&self, path: &str) -> AppResult<Option<IndexEntry>> {
149        self.embedded.index_entry(path)
150    }
151}
152
153impl LogReader for Repo {
154    fn log(&self, opts: Option<&LogOptions>) -> AppResult<Vec<Commit>> {
155        self.embedded.log(opts)
156    }
157
158    fn merge_base(&self, a: &str, b: &str) -> AppResult<Oid> {
159        self.embedded.merge_base(a, b)
160    }
161
162    fn is_ancestor(&self, ancestor: &str, descendant: &str) -> AppResult<bool> {
163        self.embedded.is_ancestor(ancestor, descendant)
164    }
165}
166
167impl Blamer for Repo {
168    fn blame(
169        &self,
170        revision: &str,
171        path: &str,
172        opts: Option<&BlameOptions>,
173    ) -> AppResult<Vec<BlameLine>> {
174        self.embedded.blame(revision, path, opts)
175    }
176}
177
178impl Inspector for Repo {
179    fn describe(&self, opts: Option<&DescribeOptions>) -> AppResult<String> {
180        self.cli.describe(opts)
181    }
182
183    fn rev_parse(&self, revision: &str) -> AppResult<Oid> {
184        self.cli.rev_parse(revision)
185    }
186
187    fn grep(
188        &self,
189        pattern: &str,
190        revision: &str,
191        opts: Option<&GrepOptions>,
192    ) -> AppResult<Vec<GrepMatch>> {
193        self.cli.grep(pattern, revision, opts)
194    }
195
196    fn show(&self, object: &str) -> AppResult<Vec<u8>> {
197        self.cli.show(object)
198    }
199}
200
201impl IndexManager for Repo {
202    fn stage(&self, paths: &[&str]) -> AppResult<()> {
203        self.embedded.stage(paths)
204    }
205
206    fn unstage(&self, paths: &[&str]) -> AppResult<()> {
207        self.embedded.unstage(paths)
208    }
209
210    fn staged_entries(&self) -> AppResult<Vec<StatusEntry>> {
211        self.embedded.staged_entries()
212    }
213}
214
215impl Committer for Repo {
216    fn commit(&self, message: &str, opts: Option<&CommitOptions>) -> AppResult<Oid> {
217        self.embedded.commit(message, opts)
218    }
219}
220
221impl Merger for Repo {
222    fn merge(&self, branch: &str, opts: Option<&MergeOptions>) -> AppResult<MergeResult> {
223        self.cli.merge(branch, opts)
224    }
225
226    fn abort_merge(&self) -> AppResult<()> {
227        self.cli.abort_merge()
228    }
229}
230
231impl Rebaser for Repo {
232    fn rebase(&self, onto: &str, opts: Option<&RebaseOptions>) -> AppResult<RebaseResult> {
233        self.cli.rebase(onto, opts)
234    }
235
236    fn abort_rebase(&self) -> AppResult<()> {
237        self.cli.abort_rebase()
238    }
239
240    fn continue_rebase(&self) -> AppResult<RebaseResult> {
241        self.cli.continue_rebase()
242    }
243}
244
245impl CherryPicker for Repo {
246    fn cherry_pick(
247        &self,
248        commit: &str,
249        opts: Option<&crate::options::CherryPickOptions>,
250    ) -> AppResult<Oid> {
251        self.cli.cherry_pick(commit, opts)
252    }
253
254    fn cherry_pick_continue(&self) -> AppResult<Oid> {
255        self.cli.cherry_pick_continue()
256    }
257
258    fn cherry_pick_abort(&self) -> AppResult<()> {
259        self.cli.cherry_pick_abort()
260    }
261}
262
263impl Resetter for Repo {
264    fn reset(&self, target: &str, mode: ResetMode) -> AppResult<()> {
265        self.cli.reset(target, mode)
266    }
267}
268
269impl CheckoutManager for Repo {
270    fn checkout(&self, ref_name: &str, opts: Option<&CheckoutOptions>) -> AppResult<()> {
271        self.cli.checkout(ref_name, opts)
272    }
273
274    fn checkout_files(&self, paths: &[&str]) -> AppResult<()> {
275        self.cli.checkout_files(paths)
276    }
277}
278
279impl Stasher for Repo {
280    fn stash(&self, message: &str) -> AppResult<Oid> {
281        self.cli.stash(message)
282    }
283
284    fn stash_pop(&self) -> AppResult<()> {
285        self.cli.stash_pop()
286    }
287
288    fn stash_pop_index(&self, index: usize) -> AppResult<()> {
289        self.cli.stash_pop_index(index)
290    }
291
292    fn stash_list(&self) -> AppResult<Vec<StashEntry>> {
293        self.cli.stash_list()
294    }
295}
296
297impl RefManager for Repo {
298    fn list_branches(&self, filter: BranchFilter) -> AppResult<Vec<Branch>> {
299        self.embedded.list_branches(filter)
300    }
301
302    fn list_tags(&self) -> AppResult<Vec<Tag>> {
303        self.embedded.list_tags()
304    }
305
306    fn create_branch(&self, name: &str, target: &str) -> AppResult<()> {
307        self.embedded.create_branch(name, target)
308    }
309
310    fn delete_branch(&self, name: &str) -> AppResult<()> {
311        self.embedded.delete_branch(name)
312    }
313
314    fn create_tag(&self, name: &str, target: &str, message: Option<&str>) -> AppResult<()> {
315        self.embedded.create_tag(name, target, message)
316    }
317
318    fn delete_tag(&self, name: &str) -> AppResult<()> {
319        self.embedded.delete_tag(name)
320    }
321}
322
323impl RemoteManager for Repo {
324    fn list_remotes(&self) -> AppResult<Vec<Remote>> {
325        self.embedded.list_remotes()
326    }
327
328    fn fetch(&self, remote: &str, opts: Option<&FetchOptions>) -> AppResult<()> {
329        self.embedded.fetch(remote, opts)
330    }
331
332    fn push(&self, remote: &str, opts: Option<&PushOptions>) -> AppResult<()> {
333        self.embedded.push(remote, opts)
334    }
335
336    fn tracking_branch(&self, branch: &str) -> AppResult<String> {
337        self.embedded.tracking_branch(branch)
338    }
339}
340
341impl ConfigReader for Repo {
342    fn config_get(&self, key: &str) -> AppResult<String> {
343        self.embedded.config_get(key)
344    }
345
346    fn config_get_all(&self, key: &str) -> AppResult<Vec<String>> {
347        self.embedded.config_get_all(key)
348    }
349
350    fn config_set(&self, key: &str, value: &str) -> AppResult<()> {
351        self.embedded.config_set(key, value)
352    }
353}
354
355impl Maintainer for Repo {
356    fn gc(&self) -> AppResult<()> {
357        self.cli.gc()
358    }
359
360    fn prune(&self) -> AppResult<()> {
361        self.cli.prune()
362    }
363
364    fn fsck(&self) -> AppResult<()> {
365        self.cli.fsck()
366    }
367
368    fn clean(&self, opts: Option<&CleanOptions>) -> AppResult<Vec<String>> {
369        self.cli.clean(opts)
370    }
371}