Radish alpha
r
rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt
Git libraries for Radicle
Radicle
Git
Merge remote-tracking branch 'origin/git2-trailers'
Fintan Halpenny committed 3 years ago
commit a3a63e4d45b3586a9b4aedac6485f90eb5005911
parent 2456fcd
4 files changed +228 -23
modified git-commit/Cargo.toml
@@ -8,7 +8,7 @@ authors = [
  "Fintan Halpenny <fintan.halpenny@gmail.com>",
]
description = "A small library for parsing, displaying and creating a git commit"
-
keywords = ["git", "radicle"]
+
keywords = ["git", "git-commit", "git-trailers", "radicle"]

[dependencies]
thiserror = "1"
@@ -17,7 +17,3 @@ thiserror = "1"
version = "0.16.1"
default-features = false
features = ["vendored-libgit2"]
-

-
[dependencies.git-trailers]
-
version = "0.1.0"
-
path = "../git-trailers"
modified git-commit/src/lib.rs
@@ -17,7 +17,6 @@ use std::{
};

use git2::{ObjectType, Oid};
-
use git_trailers::{self as trailers, OwnedTrailer, Trailer};

pub mod author;
pub use author::Author;
@@ -25,6 +24,9 @@ pub use author::Author;
pub mod headers;
pub use headers::{Headers, Signature};

+
pub mod trailers;
+
pub use trailers::{OwnedTrailer, Trailer, Trailers};
+

/// A git commit in its object description form, i.e. the output of
/// `git cat-file` for a commit object.
#[derive(Debug)]
@@ -162,8 +164,6 @@ pub mod error {
        InvalidFormat,
        #[error("missing '{0}' while parsing commit")]
        Missing(&'static str),
-
        #[error(transparent)]
-
        Token(#[from] git_trailers::InvalidToken),
        #[error("error occurred while checking for git-trailers: {0}")]
        Trailers(#[source] git2::Error),
        #[error(transparent)]
@@ -243,19 +243,14 @@ impl FromStr for Commit {
            }
        }

-
        let (message, trailers) = message.lines().fold(
-
            (Vec::new(), Vec::new()),
-
            |(mut message, mut trailers), line| match trailers::parser::trailer(line, ": ") {
-
                Ok((_, trailer)) => {
-
                    trailers.push(trailer.into());
-
                    (message, trailers)
-
                },
-
                Err(_) => {
-
                    message.push(line);
-
                    (message, trailers)
-
                },
-
            },
-
        );
+
        let trailers = trailers::Trailers::parse(message).map_err(error::Parse::Trailers)?;
+

+
        let message = message
+
            .strip_suffix(&trailers.to_string(": "))
+
            .unwrap_or(message)
+
            .to_string();
+

+
        let trailers = trailers.iter().map(OwnedTrailer::from).collect();

        Ok(Self {
            tree,
@@ -263,7 +258,7 @@ impl FromStr for Commit {
            author: author.ok_or(error::Parse::Missing("author"))?,
            committer: committer.ok_or(error::Parse::Missing("committer"))?,
            headers,
-
            message: message.join("\n"),
+
            message,
            trailers,
        })
    }
added git-commit/src/trailers.rs
@@ -0,0 +1,214 @@
+
use std::{borrow::Cow, fmt, fmt::Write, ops::Deref, str::FromStr};
+

+
use git2::{MessageTrailersStrs, MessageTrailersStrsIterator};
+

+
/// A Git commit's set of trailers that are left in the commit's
+
/// message.
+
///
+
/// Trailers are key/value pairs in the last paragraph of a message,
+
/// not including any patches or conflicts that may be present.
+
///
+
/// # Usage
+
///
+
/// To construct `Trailers`, you can use [`Trailers::parse`] or its
+
/// `FromStr` implementation.
+
///
+
/// To iterate over the trailers, you can use [`Trailers::iter`].
+
///
+
/// To render the trailers to a `String`, you can use
+
/// [`Trailers::to_string`] or its `Display` implementation (note that
+
/// it will default to using `": "` as the separator.
+
///
+
/// # Examples
+
///
+
/// ```text
+
/// Add new functionality
+
///
+
/// Making code better with new functionality.
+
///
+
/// X-Signed-Off-By: Alex Sellier
+
/// X-Co-Authored-By: Fintan Halpenny
+
/// ```
+
///
+
/// The trailers in the above example are:
+
///
+
/// ```text
+
/// X-Signed-Off-By: Alex Sellier
+
/// X-Co-Authored-By: Fintan Halpenny
+
/// ```
+
pub struct Trailers {
+
    inner: MessageTrailersStrs,
+
}
+

+
impl Trailers {
+
    pub fn parse(message: &str) -> Result<Self, git2::Error> {
+
        Ok(Self {
+
            inner: git2::message_trailers_strs(message)?,
+
        })
+
    }
+

+
    pub fn iter(&self) -> Iter<'_> {
+
        Iter {
+
            inner: self.inner.iter(),
+
        }
+
    }
+

+
    pub fn to_string<'a, S>(&self, sep: S) -> String
+
    where
+
        S: Separator<'a>,
+
    {
+
        let mut buf = String::new();
+
        for (i, trailer) in self.iter().enumerate() {
+
            if i > 0 {
+
                writeln!(buf).ok();
+
            }
+

+
            write!(buf, "{}", trailer.display(sep.sep_for(&trailer.token))).ok();
+
        }
+
        writeln!(buf).ok();
+
        buf
+
    }
+
}
+

+
impl fmt::Display for Trailers {
+
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+
        f.write_str(&self.to_string(": "))
+
    }
+
}
+

+
pub trait Separator<'a> {
+
    fn sep_for(&self, token: &Token) -> &'a str;
+
}
+

+
impl<'a> Separator<'a> for &'a str {
+
    fn sep_for(&self, _: &Token) -> &'a str {
+
        self
+
    }
+
}
+

+
impl<'a, F> Separator<'a> for F
+
where
+
    F: Fn(&Token) -> &'a str,
+
{
+
    fn sep_for(&self, token: &Token) -> &'a str {
+
        self(token)
+
    }
+
}
+

+
impl FromStr for Trailers {
+
    type Err = git2::Error;
+

+
    fn from_str(s: &str) -> Result<Self, Self::Err> {
+
        Self::parse(s)
+
    }
+
}
+

+
pub struct Iter<'a> {
+
    inner: MessageTrailersStrsIterator<'a>,
+
}
+

+
impl<'a> Iterator for Iter<'a> {
+
    type Item = Trailer<'a>;
+

+
    fn next(&mut self) -> Option<Self::Item> {
+
        let (token, value) = self.inner.next()?;
+
        Some(Trailer {
+
            token: Token(token),
+
            value: Cow::Borrowed(value),
+
        })
+
    }
+
}
+

+
#[derive(Debug, Clone, Eq, PartialEq)]
+
pub struct Token<'a>(&'a str);
+

+
impl Deref for Token<'_> {
+
    type Target = str;
+

+
    fn deref(&self) -> &Self::Target {
+
        self.0
+
    }
+
}
+

+
pub struct Display<'a> {
+
    trailer: &'a Trailer<'a>,
+
    separator: &'a str,
+
}
+

+
impl<'a> fmt::Display for Display<'a> {
+
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+
        write!(
+
            f,
+
            "{}{}{}",
+
            self.trailer.token.deref(),
+
            self.separator,
+
            self.trailer.value,
+
        )
+
    }
+
}
+

+
/// A trailer is a key/value pair found in the last paragraph of a Git
+
/// commit message, not including any patches or conflicts that may be
+
/// present.
+
#[derive(Debug, Clone, Eq, PartialEq)]
+
pub struct Trailer<'a> {
+
    pub token: Token<'a>,
+
    pub value: Cow<'a, str>,
+
}
+

+
impl<'a> Trailer<'a> {
+
    pub fn display(&'a self, separator: &'a str) -> Display<'a> {
+
        Display {
+
            trailer: self,
+
            separator,
+
        }
+
    }
+

+
    pub fn to_owned(&self) -> OwnedTrailer {
+
        OwnedTrailer::from(self)
+
    }
+
}
+

+
/// A version of the [`Trailer`] which owns its token and
+
/// value. Useful for when you need to carry trailers around in a long
+
/// lived data structure.
+
#[derive(Debug)]
+
pub struct OwnedTrailer {
+
    pub token: OwnedToken,
+
    pub value: String,
+
}
+

+
#[derive(Debug)]
+
pub struct OwnedToken(String);
+

+
impl Deref for OwnedToken {
+
    type Target = str;
+

+
    fn deref(&self) -> &Self::Target {
+
        &self.0
+
    }
+
}
+

+
impl<'a> From<&Trailer<'a>> for OwnedTrailer {
+
    fn from(t: &Trailer<'a>) -> Self {
+
        OwnedTrailer {
+
            token: OwnedToken(t.token.0.to_string()),
+
            value: t.value.to_string(),
+
        }
+
    }
+
}
+

+
impl<'a> From<Trailer<'a>> for OwnedTrailer {
+
    fn from(t: Trailer<'a>) -> Self {
+
        (&t).into()
+
    }
+
}
+

+
impl<'a> From<&'a OwnedTrailer> for Trailer<'a> {
+
    fn from(t: &'a OwnedTrailer) -> Self {
+
        Trailer {
+
            token: Token(t.token.0.as_str()),
+
            value: Cow::from(&t.value),
+
        }
+
    }
+
}
modified git-commit/t/Cargo.toml
@@ -14,7 +14,7 @@ doc = false
[features]
test = []

-
[dependencies.git-commit]
+
[dev-dependencies.git-commit]
path = ".."

[dev-dependencies.git2]