grex_core/git/mod.rs
1//! Decoupled git backend surface used by the pack walker and exec path.
2//!
3//! Pack children reference git remotes (`url`, `path`, `ref` — see
4//! `.omne/cfg/pack-spec.md` §children). The walker needs to clone, fetch, and
5//! checkout these remotes; the exec path pins commits. Every one of those
6//! callers goes through the [`GitBackend`] trait rather than the `gix` crate
7//! directly, so:
8//!
9//! - tests can substitute an in-memory mock
10//! - a future IPC or CLI-shell backend can plug in without rewriting callers
11//! - backend-specific error types stay out of the public API (see
12//! `error::GitError` which uses `String` detail fields)
13//!
14//! The default implementation, [`GixBackend`], wraps the pure-Rust `gix`
15//! crate. Auth is the gix default: system SSH keys and anonymous HTTPS.
16//! Credential prompting, SSH-agent integration, shallow clones, submodules,
17//! and concurrent-fetch coordination are all **out of scope** for this slice
18//! and will land in later M3 slices.
19
20pub mod error;
21pub mod gix_backend;
22
23use std::path::{Path, PathBuf};
24
25pub use self::error::GitError;
26pub use self::gix_backend::GixBackend;
27
28/// Result of a successful clone.
29///
30/// `head_sha` is always the 40-char lowercase hex SHA of the commit HEAD was
31/// left pointing at after checkout.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct ClonedRepo {
34 /// Filesystem path of the cloned working tree.
35 pub path: PathBuf,
36 /// HEAD commit SHA, 40-char lowercase hex.
37 pub head_sha: String,
38}
39
40/// Stable surface for all git operations grex needs.
41///
42/// Implementors must be `Send + Sync` so a single instance can be handed to
43/// the scheduler as `Arc<dyn GitBackend>`. All methods are synchronous — the
44/// slice 3 design deliberately avoids async to keep the trait object-safe and
45/// the default backend runtime-free.
46///
47/// Errors are carried as [`GitError`]; the enum is `#[non_exhaustive]` so
48/// future variants (credentials, submodules, …) won't break implementors.
49pub trait GitBackend: Send + Sync {
50 /// Short human-readable name of the backend, e.g. `"gix"`. Used in logs
51 /// and diagnostics; not parsed programmatically.
52 fn name(&self) -> &'static str;
53
54 /// Clone `url` into `dest`.
55 ///
56 /// # Contract
57 ///
58 /// - If `dest` exists and is non-empty → [`GitError::DestinationNotEmpty`].
59 /// - If `r#ref` is `Some`, check out that ref after the clone finishes.
60 /// - If `r#ref` is `None`, leave the working tree on the remote's default
61 /// HEAD.
62 ///
63 /// # Errors
64 ///
65 /// Any clone-, network-, or checkout-layer failure maps to a
66 /// [`GitError`] variant — see that enum for the taxonomy.
67 fn clone(&self, url: &str, dest: &Path, r#ref: Option<&str>) -> Result<ClonedRepo, GitError>;
68
69 /// Fetch from the default remote (`origin`) into an existing repo at
70 /// `dest`. Leaves the working tree untouched.
71 ///
72 /// # Errors
73 ///
74 /// Returns [`GitError::NotARepository`] when `dest` is not a git repo,
75 /// or [`GitError::FetchFailed`] on any network- or ref-update failure.
76 fn fetch(&self, dest: &Path) -> Result<(), GitError>;
77
78 /// Resolve `r#ref` (branch, tag, or SHA) and update the working tree at
79 /// `dest` to match. Refuses to run if the working tree has uncommitted
80 /// changes.
81 ///
82 /// # Errors
83 ///
84 /// - [`GitError::NotARepository`] when `dest` is not a git repo.
85 /// - [`GitError::DirtyWorkingTree`] when there are uncommitted changes.
86 /// - [`GitError::RefNotFound`] when the ref cannot be resolved.
87 /// - [`GitError::CheckoutFailed`] for any other checkout-layer failure.
88 fn checkout(&self, dest: &Path, r#ref: &str) -> Result<(), GitError>;
89
90 /// Return HEAD at `dest` as a 40-char lowercase hex SHA.
91 ///
92 /// # Errors
93 ///
94 /// [`GitError::NotARepository`] when `dest` is not a git repo;
95 /// [`GitError::Internal`] wraps any unexpected head-resolution failure.
96 fn head_sha(&self, dest: &Path) -> Result<String, GitError>;
97}