Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
radicle/git/repository: define Ancestry trait
Fintan Halpenny committed 24 days ago
commit 38397b53d6ebcb5c761cd9b6655e400e2c4dddba
parent 9ecb0f125da006042c22f3094eed13e4e176ca9e
3 files changed +134 -0
modified crates/radicle/src/git/repository.rs
@@ -3,16 +3,19 @@
//! Provides a library-agnostic interface for git repository operations,
//! separating concerns into:
//!
+
//! - [`ancestry`] – Git ancestry operations.
//! - [`types`] — Git domain types, i.e. Blob, Commit, TreeEntry, etc.
//! - [`object`] — The Git object store; providing read and write capabilities of Git objects.
//! - [`reference`] — The Git reference store; providing read and write capabilities of Git references.
//!
//! [`reference`]: self::reference

+
pub mod ancestry;
pub mod object;
pub mod reference;
pub mod types;

+
pub use ancestry::{AheadBehind, Ancestry};
pub use object::{ObjectReader, ObjectWriter};
pub use reference::{RefReader, RefTarget, RefWriter, SymbolicRefTarget, SymbolicRefWriter};
pub use types::{Blob, Commit, ObjectKind, TreeEntry};
added crates/radicle/src/git/repository/ancestry.rs
@@ -0,0 +1,56 @@
+
//! Git commit graph ancestry trait.
+
//!
+
//! [`Ancestry`] provides merge-base, ancestor checks, and ahead/behind counts.
+

+
pub mod error;
+
pub use error::{AheadBehindError, IsAncestorError, MergeBaseError};
+

+
use radicle_oid::Oid;
+

+
/// The result of [`Ancestry::ahead_behind`].
+
pub struct AheadBehind {
+
    /// The given commit was ahead of the upstream by this many commits.
+
    pub ahead: usize,
+
    /// The given commit was behind the upstream by this many commits.
+
    pub behind: usize,
+
}
+

+
/// Git commit graph operations.
+
///
+
/// Provides merge-base computation and ancestor checks.
+
pub trait Ancestry {
+
    /// Find the merge base (common ancestor) of two commits.
+
    ///
+
    /// Returns `Ok(None)` if there is no common ancestor.
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`CommitNotFound`]: One of the commits was not found.
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`CommitNotFound`]: MergeBaseError::CommitNotFound
+
    /// [`Backend`]: MergeBaseError::Backend
+
    fn merge_base(&self, a: Oid, b: Oid) -> Result<Option<Oid>, MergeBaseError>;
+

+
    /// Check whether `ancestor` is an ancestor of `head`.
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`CommitNotFound`]: One of the commits was not found.
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`CommitNotFound`]: IsAncestorError::CommitNotFound
+
    /// [`Backend`]: IsAncestorError::Backend
+
    fn is_ancestor(&self, ancestor: Oid, head: Oid) -> Result<bool, IsAncestorError>;
+

+
    /// Count how many commits `commit` is ahead of and behind `upstream`.
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`CommitNotFound`]: One of the commits was not found.
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`CommitNotFound`]: AheadBehindError::CommitNotFound
+
    /// [`Backend`]: AheadBehindError::Backend
+
    fn ahead_behind(&self, commit: Oid, upstream: Oid) -> Result<AheadBehind, AheadBehindError>;
+
}
added crates/radicle/src/git/repository/ancestry/error.rs
@@ -0,0 +1,75 @@
+
//! Errors returned by [`Ancestry`] methods.
+
//!
+
//! [`Ancestry`]: super::Ancestry
+

+
use radicle_oid::Oid;
+
use thiserror::Error;
+

+
/// Error returned by [`Ancestry::merge_base`].
+
///
+
/// [`Ancestry::merge_base`]: super::Ancestry::merge_base
+
#[derive(Debug, Error)]
+
#[non_exhaustive]
+
pub enum MergeBaseError {
+
    /// One of the commits could not be found
+
    #[error("failed to find commit '{oid}' during merge base calculation")]
+
    CommitNotFound { oid: Oid },
+
    /// An error from the underlying git library.
+
    #[error(transparent)]
+
    Backend(Box<dyn std::error::Error + Send + Sync + 'static>),
+
}
+

+
impl MergeBaseError {
+
    pub fn backend<E>(err: E) -> Self
+
    where
+
        E: std::error::Error + Send + Sync + 'static,
+
    {
+
        Self::Backend(Box::new(err))
+
    }
+
}
+

+
/// Error returned by [`Ancestry::is_ancestor`].
+
///
+
/// [`Ancestry::is_ancestor`]: super::Ancestry::is_ancestor
+
#[derive(Debug, Error)]
+
#[non_exhaustive]
+
pub enum IsAncestorError {
+
    /// One of the commits could not be found.
+
    #[error("failed to find commit '{oid}'")]
+
    CommitNotFound { oid: Oid },
+
    /// An error from the underlying git library.
+
    #[error(transparent)]
+
    Backend(Box<dyn std::error::Error + Send + Sync + 'static>),
+
}
+

+
impl IsAncestorError {
+
    pub fn backend<E>(err: E) -> Self
+
    where
+
        E: std::error::Error + Send + Sync + 'static,
+
    {
+
        Self::Backend(Box::new(err))
+
    }
+
}
+

+
/// Error returned by [`Ancestry::ahead_behind`].
+
///
+
/// [`Ancestry::ahead_behind`]: super::Ancestry::ahead_behind
+
#[derive(Debug, Error)]
+
#[non_exhaustive]
+
pub enum AheadBehindError {
+
    /// One of the commits was not found.
+
    #[error("commit '{oid}' was not found")]
+
    CommitNotFound { oid: Oid },
+
    /// An error from the underlying git library.
+
    #[error(transparent)]
+
    Backend(Box<dyn std::error::Error + Send + Sync + 'static>),
+
}
+

+
impl AheadBehindError {
+
    pub fn backend<E>(err: E) -> Self
+
    where
+
        E: std::error::Error + Send + Sync + 'static,
+
    {
+
        Self::Backend(Box::new(err))
+
    }
+
}