Skip to main content

rskit_git/
repo.rs

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