Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
radicle/git/repository: define ObjectReader and ObjectWriter traits
Fintan Halpenny committed 24 days ago
commit 0af7b750737995d9368864df9db36d721e6c5305
parent 7a13facaca3a4b8761c04e9f924c8810e16d0ead
5 files changed +374 -0
modified crates/radicle/src/git/repository.rs
@@ -13,4 +13,5 @@ pub mod object;
pub mod reference;
pub mod types;

+
pub use object::{ObjectReader, ObjectWriter};
pub use types::{Blob, Commit, ObjectKind, TreeEntry};
modified crates/radicle/src/git/repository/object.rs
@@ -1 +1,177 @@
//! Git object database abstraction.
+
//!
+
//! The module provides two traits:
+
//! - [`ObjectReader`] for reading objects, and
+
//! - [`ObjectWriter`] for writing objects
+

+
pub mod error;
+

+
use std::path::Path;
+

+
use radicle_oid::Oid;
+

+
use super::types::{Blob, Commit, TreeEntry};
+

+
/// A handle for reading Git objects from the Git object database.
+
pub trait ObjectReader {
+
    /// Find a blob by its [`Oid`].
+
    ///
+
    /// Returns `None` if the blob does not exist.
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`Backend`]: error::read::BlobError::Backend
+
    fn blob(&self, oid: Oid) -> Result<Option<Blob>, error::read::BlobError>;
+

+
    /// Find a blob by its [`Oid`], returning an error if it does not exist.
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`NotFound`]: The blob identified by `oid` does not exist.
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`NotFound`]: error::read::BlobError::NotFound
+
    /// [`Backend`]: error::read::BlobError::Backend
+
    fn try_blob(&self, oid: Oid) -> Result<Blob, error::read::BlobError> {
+
        self.blob(oid)?
+
            .ok_or(error::read::BlobError::NotFound { oid })
+
    }
+

+
    /// Find a blob at a `path` within a commit's tree.
+
    ///
+
    /// Returns `None` if the path does not exist in the commit's tree.
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`CommitNotFound`]: The commit identified by `commit` does not exist.
+
    /// - [`Tree`]: Failed to get the commit's tree.
+
    /// - [`TreeEntry`]: Failed to look up the entry at `path` in the tree.
+
    /// - [`Object`]: The entry was found but failed to resolve to an object.
+
    /// - [`TypeMismatch`]: The resolved object is not a blob.
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`CommitNotFound`]: error::read::BlobAtError::CommitNotFound
+
    /// [`Tree`]: error::read::BlobAtError::Tree
+
    /// [`TreeEntry`]: error::read::BlobAtError::TreeEntry
+
    /// [`Object`]: error::read::BlobAtError::Object
+
    /// [`TypeMismatch`]: error::read::BlobAtError::TypeMismatch
+
    /// [`Backend`]: error::read::BlobAtError::Backend
+
    fn blob_at<P>(&self, commit: Oid, path: &P) -> Result<Option<Blob>, error::read::BlobAtError>
+
    where
+
        P: AsRef<Path>;
+

+
    /// Find a blob at a `path` within a commit's tree, returning an error if
+
    /// the path does not exist.
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`CommitNotFound`]: The commit identified by `commit` does not exist.
+
    /// - [`MissingBlob`]: The path does not exist in the commit's tree.
+
    /// - [`Tree`]: Failed to get the commit's tree.
+
    /// - [`TreeEntry`]: Failed to look up the entry at `path` in the tree.
+
    /// - [`Object`]: The entry was found but failed to resolve to an object.
+
    /// - [`TypeMismatch`]: The resolved object is not a blob.
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`CommitNotFound`]: error::read::BlobAtError::CommitNotFound
+
    /// [`MissingBlob`]: error::read::BlobAtError::MissingBlob
+
    /// [`Tree`]: error::read::BlobAtError::Tree
+
    /// [`TreeEntry`]: error::read::BlobAtError::TreeEntry
+
    /// [`Object`]: error::read::BlobAtError::Object
+
    /// [`TypeMismatch`]: error::read::BlobAtError::TypeMismatch
+
    /// [`Backend`]: error::read::BlobAtError::Backend
+
    fn try_blob_at<P>(&self, commit: Oid, path: &P) -> Result<Blob, error::read::BlobAtError>
+
    where
+
        P: AsRef<Path>,
+
    {
+
        self.blob_at(commit, path)?
+
            .ok_or_else(|| error::read::BlobAtError::MissingBlob {
+
                commit,
+
                path: path.as_ref().to_path_buf(),
+
            })
+
    }
+

+
    /// Read a commit by its [`Oid`].
+
    ///
+
    /// Returns `None` if the commit does not exist.
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`Parse`]: The object was found but could not be parsed as a commit.
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`Parse`]: error::read::CommitError::Parse
+
    /// [`Backend`]: error::read::CommitError::Backend
+
    fn commit(&self, oid: Oid) -> Result<Option<Commit>, error::read::CommitError>;
+

+
    /// Read a commit by its [`Oid`], returning an error if it does not exist.
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`NotFound`]: The commit identified by `oid` does not exist.
+
    /// - [`Parse`]: The object was found but could not be parsed as a commit.
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`NotFound`]: error::read::CommitError::NotFound
+
    /// [`Parse`]: error::read::CommitError::Parse
+
    /// [`Backend`]: error::read::CommitError::Backend
+
    fn try_commit(&self, oid: Oid) -> Result<Commit, error::read::CommitError> {
+
        self.commit(oid)?
+
            .ok_or(error::read::CommitError::NotFound { oid })
+
    }
+

+
    /// Check whether an object exists in the object database.
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`Backend`]: error::read::ExistsError::Backend
+
    fn exists(&self, oid: Oid) -> Result<bool, error::read::ExistsError>;
+
}
+

+
/// Write Git objects to the Git object database.
+
///
+
/// Every method returns the content-addressed [`Oid`] of the written object.
+
pub trait ObjectWriter {
+
    /// Write a blob given its raw bytes content.
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`Backend`]: error::write::BlobError::Backend
+
    fn write_blob(&self, content: &[u8]) -> Result<Oid, error::write::BlobError>;
+

+
    /// Write a tree from a set of entries.
+
    ///
+
    /// [`TreeEntry::Blob`] entries have their content written as blobs first.
+
    /// [`TreeEntry::BlobRef`] entries reference existing blobs by [`Oid`].
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`MissingBlob`]: A [`TreeEntry::BlobRef`] references an [`Oid`] that
+
    ///   does not exist in the object database.
+
    /// - [`WriteBlob`]: Failed to write a blob for a [`TreeEntry::Blob`] entry.
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`MissingBlob`]: error::write::TreeError::MissingBlob
+
    /// [`WriteBlob`]: error::write::TreeError::WriteBlob
+
    /// [`Backend`]: error::write::TreeError::Backend
+
    fn write_tree(&self, entries: &[TreeEntry]) -> Result<Oid, error::write::TreeError>;
+

+
    /// Write a commit from raw bytes.
+
    ///
+
    /// The caller is responsible for producing valid Git commit bytes
+
    /// (e.g. via [`radicle_git_metadata`]).  This is necessary for signed
+
    /// commits where the exact byte representation must be controlled.
+
    ///
+
    /// # Errors
+
    ///
+
    /// - [`Backend`]: An unexpected error from the underlying git library.
+
    ///
+
    /// [`Backend`]: error::write::CommitError::Backend
+
    fn write_commit(&self, bytes: &[u8]) -> Result<Oid, error::write::CommitError>;
+
}
added crates/radicle/src/git/repository/object/error.rs
@@ -0,0 +1,4 @@
+
//! Errors for Git object operations, namespaced by read and write.
+

+
pub mod read;
+
pub mod write;
added crates/radicle/src/git/repository/object/error/read.rs
@@ -0,0 +1,123 @@
+
//! Errors returned by [`ObjectReader`] methods.
+
//!
+
//! [`ObjectReader`]: super::super::ObjectReader
+

+
use std::path::PathBuf;
+

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

+
use crate::git::repository::types::ObjectKind;
+

+
/// Error returned by [`ObjectReader::blob`].
+
///
+
/// [`ObjectReader::blob`]: super::super::ObjectReader::blob
+
#[derive(Debug, Error)]
+
#[non_exhaustive]
+
pub enum BlobError {
+
    /// The blob was not found.
+
    #[error("failed to find blob '{oid}'")]
+
    NotFound { oid: Oid },
+
    /// An error from the underlying git library.
+
    #[error(transparent)]
+
    Backend(Box<dyn std::error::Error + Send + Sync + 'static>),
+
}
+

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

+
/// Error returned by [`ObjectReader::blob_at`].
+
///
+
/// [`ObjectReader::blob_at`]: super::super::ObjectReader::blob_at
+
#[derive(Debug, Error)]
+
#[non_exhaustive]
+
pub enum BlobAtError {
+
    /// Failed to find the commit.
+
    #[error("failed to find commit '{commit}' to retrieve blob at {path:?}")]
+
    CommitNotFound { commit: Oid, path: PathBuf },
+
    /// Failed to get the associated tree of the commit.
+
    #[error("failed to get associated tree of the commit '{commit}'")]
+
    Tree {
+
        commit: Oid,
+
        source: Box<dyn std::error::Error + Send + Sync + 'static>,
+
    },
+
    /// Failed to get the entry at `path` in the commit's tree.
+
    #[error("failed to get tree entry {path:?} in the commit '{commit}'")]
+
    TreeEntry {
+
        commit: Oid,
+
        path: PathBuf,
+
        source: Box<dyn std::error::Error + Send + Sync + 'static>,
+
    },
+
    /// Failed to resolve the object at the given path.
+
    #[error("failed to resolve the object at {path:?} in the commit '{commit}'")]
+
    Object {
+
        commit: Oid,
+
        path: PathBuf,
+
        source: Box<dyn std::error::Error + Send + Sync + 'static>,
+
    },
+
    /// The object exists but is not a blob.
+
    #[error("object {oid} has type `{actual}`, expected `{expected}`")]
+
    TypeMismatch {
+
        oid: Oid,
+
        expected: ObjectKind,
+
        actual: String,
+
    },
+
    /// The path does not exist in the commit's tree.
+
    #[error("the blob identified by {path:?} does not exist in the commit '{commit}'")]
+
    MissingBlob { commit: Oid, path: PathBuf },
+
    /// An error from the underlying git library.
+
    #[error(transparent)]
+
    Backend(Box<dyn std::error::Error + Send + Sync + 'static>),
+
}
+

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

+
/// Error returned by [`ObjectReader::commit`].
+
///
+
/// [`ObjectReader::commit`]: super::super::ObjectReader::commit
+
#[derive(Debug, Error)]
+
#[non_exhaustive]
+
pub enum CommitError {
+
    /// The commit was not found.
+
    #[error("failed to find commit '{oid}'")]
+
    NotFound { oid: Oid },
+
    /// Failed to parse the raw commit bytes.
+
    #[error("failed to parse commit '{oid}': {source}")]
+
    Parse {
+
        oid: Oid,
+
        source: radicle_git_metadata::commit::ParseError,
+
    },
+
    /// An error from the underlying git library.
+
    #[error(transparent)]
+
    Backend(Box<dyn std::error::Error + Send + Sync + 'static>),
+
}
+

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

+
/// Error returned by [`ObjectReader::exists`].
+
///
+
/// [`ObjectReader::exists`]: super::super::ObjectReader::exists
+
#[derive(Debug, Error)]
+
#[non_exhaustive]
+
pub enum ExistsError {
+
    /// An error from the underlying git library.
+
    #[error(transparent)]
+
    Backend(Box<dyn std::error::Error + Send + Sync + 'static>),
+
}
+

+
impl ExistsError {
+
    pub fn backend<E: std::error::Error + Send + Sync + 'static>(err: E) -> Self {
+
        Self::Backend(Box::new(err))
+
    }
+
}
added crates/radicle/src/git/repository/object/error/write.rs
@@ -0,0 +1,70 @@
+
//! Errors returned by [`ObjectWriter`] methods.
+
//!
+
//! [`ObjectWriter`]: super::super::ObjectWriter
+

+
use std::path::PathBuf;
+

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

+
/// Error returned by [`ObjectWriter::write_blob`].
+
///
+
/// [`ObjectWriter::write_blob`]: super::super::ObjectWriter::write_blob
+
#[derive(Debug, Error)]
+
#[non_exhaustive]
+
pub enum BlobError {
+
    /// An error from the underlying git library.
+
    #[error(transparent)]
+
    Backend(Box<dyn std::error::Error + Send + Sync + 'static>),
+
}
+

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

+
/// Error returned by [`ObjectWriter::write_tree`].
+
///
+
/// [`ObjectWriter::write_tree`]: super::super::ObjectWriter::write_tree
+
#[derive(Debug, Error)]
+
#[non_exhaustive]
+
pub enum TreeError {
+
    /// A `BlobRef` entry references an OID that does not exist.
+
    #[error("blob reference '{oid}' does not exist in the object database")]
+
    MissingBlob { oid: Oid },
+
    /// Failed to write blob contents for a [`TreeEntry::Blob`] entry.
+
    ///
+
    /// [`TreeEntry::Blob`]: crate::git::repository::types::TreeEntry::Blob
+
    #[error("failed to write blob contents to {path:?}")]
+
    WriteBlob {
+
        path: PathBuf,
+
        source: Box<dyn std::error::Error + Send + Sync + 'static>,
+
    },
+
    /// An error from the underlying git library.
+
    #[error(transparent)]
+
    Backend(Box<dyn std::error::Error + Send + Sync + 'static>),
+
}
+

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

+
/// Error returned by [`ObjectWriter::write_commit`].
+
///
+
/// [`ObjectWriter::write_commit`]: super::super::ObjectWriter::write_commit
+
#[derive(Debug, Error)]
+
#[non_exhaustive]
+
pub enum CommitError {
+
    /// An error from the underlying git library.
+
    #[error(transparent)]
+
    Backend(Box<dyn std::error::Error + Send + Sync + 'static>),
+
}
+

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