Radish alpha
r
rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt
Git libraries for Radicle
Radicle
Git
change BlobContent to BlobRef that borrows its content from git2::Blob
Han Xu committed 3 years ago
commit 9f4dce4ffd00e4fc1bb6ceae7e0d72410e5877e1
parent ff3c66e
3 files changed +129 -57
modified radicle-surf/src/blob.rs
@@ -18,8 +18,6 @@
//! Represents git object type 'blob', i.e. actual file contents.
//! See git [doc](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) for more details.

-
use std::str;
-

use radicle_git_ext::Oid;
#[cfg(feature = "serde")]
use serde::{
@@ -30,45 +28,60 @@ use serde::{
use crate::Commit;

/// Represents a git blob object.
-
pub struct Blob {
+
///
+
/// The type parameter `T` could be [`BlobRef`] or [`BlobVec`].
+
pub struct Blob<T> {
    id: Oid,
-
    content: BlobContent,
+
    is_binary: bool,
    commit: Commit,
+
    content: T,
}

-
impl Blob {
-
    /// Returns the [`Blob`] for a file at `revision` under `path`.
-
    pub(crate) fn new(id: Oid, content: &[u8], commit: Commit) -> Self {
-
        let content = BlobContent::from(content);
-
        Self {
-
            id,
-
            content,
-
            commit,
-
        }
+
impl<T> Blob<T> {
+
    pub fn object_id(&self) -> Oid {
+
        self.id
    }

-
    /// Indicates if the content of the [`Blob`] is binary.
-
    #[must_use]
    pub fn is_binary(&self) -> bool {
-
        matches!(self.content, BlobContent::Binary(_))
+
        self.is_binary
    }

-
    pub fn object_id(&self) -> Oid {
-
        self.id
+
    /// Returns the commit that created this blob.
+
    pub fn commit(&self) -> &Commit {
+
        &self.commit
    }

-
    pub fn content(&self) -> &BlobContent {
+
    pub fn content(&self) -> &T {
        &self.content
    }
+
}

-
    /// Returns the commit that created this blob.
-
    pub fn commit(&self) -> &Commit {
-
        &self.commit
+
impl<'a> Blob<BlobRef<'a>> {
+
    /// Returns the [`Blob`] wrapping around an underlying `git2::Blob`.
+
    pub(crate) fn new(id: Oid, git2_blob: git2::Blob<'a>, commit: Commit) -> Self {
+
        let is_binary = git2_blob.is_binary();
+
        let content = BlobRef { inner: git2_blob };
+
        Self {
+
            id,
+
            is_binary,
+
            content,
+
            commit,
+
        }
+
    }
+

+
    /// Converts into a `Blob` with owned content bytes.
+
    pub fn to_owned(&self) -> Blob<BlobVec> {
+
        Blob::<BlobVec>::new(
+
            self.id,
+
            self.content.as_bytes().to_vec(),
+
            self.commit.clone(),
+
            self.is_binary,
+
        )
    }
}

#[cfg(feature = "serde")]
-
impl Serialize for Blob {
+
impl<'a> Serialize for Blob<BlobRef<'a>> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
@@ -82,54 +95,96 @@ impl Serialize for Blob {
    }
}

-
/// Variants of blob content.
-
#[derive(PartialEq, Eq)]
-
pub enum BlobContent {
-
    /// Content is plain text and can be passed as a string.
-
    Plain(String),
-
    /// Content is binary and needs special treatment.
-
    Binary(Vec<u8>),
+
/// Represents a blob with borrowed content bytes.
+
pub struct BlobRef<'a> {
+
    inner: git2::Blob<'a>,
}

-
impl BlobContent {
-
    /// Returns the size of this `BlobContent`.
+
impl<'a> BlobRef<'a> {
+
    /// Returns the size of the blob content.
    pub fn size(&self) -> usize {
-
        match self {
-
            Self::Plain(content) => content.len(),
-
            Self::Binary(bytes) => bytes.len(),
-
        }
+
        self.inner.size()
    }

    /// Returns the content as bytes.
    pub fn as_bytes(&self) -> &[u8] {
-
        match self {
-
            Self::Plain(content) => content.as_bytes(),
-
            Self::Binary(bytes) => &bytes[..],
-
        }
+
        self.inner.content()
    }
}

#[cfg(feature = "serde")]
-
impl Serialize for BlobContent {
+
impl<'a> Serialize for BlobRef<'a> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
-
        match self {
-
            Self::Plain(content) => serializer.serialize_str(content),
-
            Self::Binary(bytes) => {
-
                let encoded = base64::encode(bytes);
-
                serializer.serialize_str(&encoded)
-
            },
-
        }
+
        serialize_bytes(self.as_bytes(), serializer)
+
    }
+
}
+

+
/// Represents a blob with owned content bytes.
+
pub struct BlobVec {
+
    inner: Vec<u8>,
+
}
+

+
impl BlobVec {
+
    pub fn size(&self) -> usize {
+
        self.inner.len()
+
    }
+

+
    pub fn as_bytes(&self) -> &[u8] {
+
        self.inner.as_ref()
    }
}

-
impl From<&[u8]> for BlobContent {
-
    fn from(bytes: &[u8]) -> Self {
-
        match str::from_utf8(bytes) {
-
            Ok(utf8) => BlobContent::Plain(utf8.to_owned()),
-
            Err(_) => BlobContent::Binary(bytes.to_owned()),
+
impl Blob<BlobVec> {
+
    pub(crate) fn new(id: Oid, bytes: Vec<u8>, commit: Commit, is_binary: bool) -> Self {
+
        let content = BlobVec { inner: bytes };
+
        Self {
+
            id,
+
            is_binary,
+
            content,
+
            commit,
        }
    }
}
+

+
#[cfg(feature = "serde")]
+
impl Serialize for BlobVec {
+
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+
    where
+
        S: Serializer,
+
    {
+
        serialize_bytes(self.as_bytes(), serializer)
+
    }
+
}
+

+
#[cfg(feature = "serde")]
+
impl Serialize for Blob<BlobVec> {
+
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+
    where
+
        S: Serializer,
+
    {
+
        const FIELDS: usize = 5;
+
        let mut state = serializer.serialize_struct("Blob", FIELDS)?;
+
        state.serialize_field("binary", &self.is_binary())?;
+
        state.serialize_field("content", &self.content)?;
+
        state.serialize_field("lastCommit", &self.commit)?;
+
        state.end()
+
    }
+
}
+

+
/// Common serialization for a `Blob`'s content bytes.
+
#[cfg(feature = "serde")]
+
fn serialize_bytes<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
+
where
+
    S: Serializer,
+
{
+
    match std::str::from_utf8(bytes) {
+
        Ok(s) => serializer.serialize_str(s),
+
        Err(_) => {
+
            let encoded = base64::encode(bytes);
+
            serializer.serialize_str(&encoded)
+
        },
+
    }
+
}
modified radicle-surf/src/repo.rs
@@ -26,7 +26,7 @@ use git_ref_format::{refspec::QualifiedPattern, Qualified, RefStr, RefString};
use radicle_git_ext::Oid;

use crate::{
-
    blob::Blob,
+
    blob::{Blob, BlobRef},
    diff::Diff,
    fs::{Directory, File, FileContent},
    refs::{BranchNames, Branches, Categories, Namespaces, TagNames, Tags},
@@ -258,7 +258,11 @@ impl Repository {
    }

    /// Returns a [`Blob`] for `path` in `commit`.
-
    pub fn blob<C: ToCommit, P: AsRef<Path>>(&self, commit: C, path: &P) -> Result<Blob, Error> {
+
    pub fn blob<'a, C: ToCommit, P: AsRef<Path>>(
+
        &'a self,
+
        commit: C,
+
        path: &P,
+
    ) -> Result<Blob<BlobRef<'a>>, Error> {
        let commit = commit
            .to_commit(self)
            .map_err(|e| Error::ToCommit(e.into()))?;
@@ -266,8 +270,8 @@ impl Repository {
        let last_commit = self
            .last_commit(path, commit)?
            .ok_or_else(|| error::Repo::PathNotFound(path.as_ref().to_path_buf()))?;
-
        let content = file.content(self)?;
-
        Ok(Blob::new(file.id(), content.as_bytes(), last_commit))
+
        let git2_blob = self.find_blob(file.id())?;
+
        Ok(Blob::<BlobRef<'a>>::new(file.id(), git2_blob, last_commit))
    }

    /// Returns the last commit, if exists, for a `path` in the history of
modified radicle-surf/t/src/source.rs
@@ -163,6 +163,19 @@ fn repo_blob() {

    // Verify as_bytes.
    assert_eq!(content.as_bytes().len(), content.size());
+

+
    // Verify to_owned().
+
    let blob_owned = blob.to_owned();
+
    assert_eq!(blob_owned.content().size(), 6253);
+
    assert_eq!(blob_owned.content().as_bytes(), content.as_bytes());
+

+
    // Verify JSON output is the same.
+
    let json_ref = json!({ "content": content }).to_string();
+
    let json_owned = json!( {
+
      "content": blob_owned.content()
+
    })
+
    .to_string();
+
    assert_eq!(json_ref, json_owned);
}

#[test]