Skip to main content

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}