diff --git a/radicle/src/git.rs b/radicle/src/git.rs index c19a1d16..2ea87608 100644 --- a/radicle/src/git.rs +++ b/radicle/src/git.rs @@ -430,188 +430,3 @@ pub mod url { } } } - -/// Parsing and formatting of commit objects. -/// This module exists to work with commits that have multiple signature headers. -pub mod commit { - use std::str::FromStr; - - /// A parsed commit object. - /// Contains the full commit header and body. - /// - /// Can be created with the [`FromStr`] instance, and formatted with the [`ToString`] - /// instance. - #[derive(Debug)] - pub struct CommitObject { - headers: Vec<(String, String)>, - message: String, - } - - impl CommitObject { - /// Get the commit message. - pub fn message(&self) -> &str { - self.message.as_str() - } - - /// Iterate over the headers, in order. - pub fn headers(&self) -> impl Iterator { - self.headers.iter().map(|(k, v)| (k.as_str(), v.as_str())) - } - - /// Iterate over matching header values. - pub fn values<'a>(&'a self, name: &'a str) -> impl Iterator + '_ { - self.headers - .iter() - .filter(move |(k, _)| k == name) - .map(|(_, v)| v.as_str()) - } - - /// Push a header to the end of the headers section. - pub fn push_header(&mut self, name: &str, value: &str) { - self.headers - .push((name.to_owned(), value.trim().to_owned())); - } - } - - #[derive(thiserror::Error, Debug)] - pub enum ParseError { - #[error("invalid git commit object format")] - InvalidFormat, - } - - impl TryFrom for CommitObject { - type Error = ParseError; - - fn try_from(value: git2::Buf) -> Result { - value.as_str().ok_or(ParseError::InvalidFormat)?.parse() - } - } - - impl FromStr for CommitObject { - type Err = ParseError; - - fn from_str(buffer: &str) -> Result { - let mut headers = Vec::new(); - let (header, message) = buffer.split_once("\n\n").ok_or(ParseError::InvalidFormat)?; - - for line in header.lines() { - if let Some(rest) = line.strip_prefix(' ') { - let value: &mut String = headers - .last_mut() - .map(|(_, v)| v) - .ok_or(ParseError::InvalidFormat)?; - value.push('\n'); - value.push_str(rest); - } else if let Some((name, value)) = line.split_once(' ') { - headers.push((name.to_owned(), value.to_owned())); - } else { - return Err(ParseError::InvalidFormat); - } - } - - Ok(Self { - headers, - message: message.to_owned(), - }) - } - } - - impl ToString for CommitObject { - fn to_string(&self) -> String { - let mut buf = String::new(); - - for (name, value) in &self.headers { - buf.push_str(name); - buf.push(' '); - buf.push_str(value.replace('\n', "\n ").as_str()); - buf.push('\n'); - } - buf.push('\n'); - buf.push_str(self.message.as_str()); - buf - } - } - - #[cfg(test)] - mod test { - use super::*; - - const UNSIGNED: &str = "\ -tree c66cc435f83ed0fba90ed4500e9b4b96e9bd001b -parent af06ad645133f580a87895353508053c5de60716 -author Alexis Sellier 1664467633 +0200 -committer Alexis Sellier 1664786099 +0200 - -Add SSH functionality with new `radicle-ssh` - -We borrow code from `thrussh`, refactored to be runtime-less. -"; - - const SIGNATURE: &str = "\ ------BEGIN SSH SIGNATURE----- -U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgvjrQogRxxLjzzWns8+mKJAGzEX -4fm2ALoN7pyvD2ttQAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 -AAAAQIQvhIewOgGfnXLgR5Qe1ZEr2vjekYXTdOfNWICi6ZiosgfZnIqV0enCPC4arVqQg+ -GPp0HqxaB911OnSAr6bwU= ------END SSH SIGNATURE----- -"; - - const SIGNED: &str = "\ -tree c66cc435f83ed0fba90ed4500e9b4b96e9bd001b -parent af06ad645133f580a87895353508053c5de60716 -author Alexis Sellier 1664467633 +0200 -committer Alexis Sellier 1664786099 +0200 -other e6fe3c97619deb8ab4198620f9a7eb79d98363dd -gpgsig -----BEGIN SSH SIGNATURE----- - U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgvjrQogRxxLjzzWns8+mKJAGzEX - 4fm2ALoN7pyvD2ttQAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 - AAAAQIQvhIewOgGfnXLgR5Qe1ZEr2vjekYXTdOfNWICi6ZiosgfZnIqV0enCPC4arVqQg+ - GPp0HqxaB911OnSAr6bwU= - -----END SSH SIGNATURE----- -gpgsig -----BEGIN SSH SIGNATURE----- - U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgvjrQogRxxLjzzWns8+mKJAGzEX - 4fm2ALoN7pyvD2ttQAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 - AAAAQIQvhIewOgGfnXLgR5Qe1ZEr2vjekYXTdOfNWICi6ZiosgfZnIqV0enCPC4arVqQg+ - GPp0HqxaB911OnSAr6bwU= - -----END SSH SIGNATURE----- - -Add SSH functionality with new `radicle-ssh` - -We borrow code from `thrussh`, refactored to be runtime-less. -"; - - #[test] - fn test_push_header() { - let mut commit = CommitObject::from_str(UNSIGNED).unwrap(); - commit.push_header("other", "e6fe3c97619deb8ab4198620f9a7eb79d98363dd"); - commit.push_header("gpgsig", SIGNATURE); - commit.push_header("gpgsig", SIGNATURE); - - assert_eq!(commit.to_string(), SIGNED); - } - - #[test] - fn test_get_header() { - let commit = CommitObject::from_str(SIGNED).unwrap(); - - assert_eq!( - commit.values("gpgsig").collect::>(), - vec![SIGNATURE.trim(), SIGNATURE.trim()] - ); - assert_eq!( - commit.values("parent").collect::>(), - vec![String::from("af06ad645133f580a87895353508053c5de60716")], - ); - assert!(commit.values("unknown").next().is_none()); - } - - #[test] - fn test_conversion() { - assert_eq!(CommitObject::from_str(SIGNED).unwrap().to_string(), SIGNED); - assert_eq!( - CommitObject::from_str(UNSIGNED).unwrap().to_string(), - UNSIGNED - ); - } - } -}