mirror of
https://seed.radicle.dev/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt.git
synced 2026-10-30 08:20:48 +08:00
Serialize instances for Commit BranchType BranchName and Branch
Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
@@ -37,11 +37,14 @@ default-features = false
|
||||
features = []
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
pretty_assertions = "1.3.0"
|
||||
proptest = "0.9"
|
||||
criterion = "0.3"
|
||||
serde_json = "1"
|
||||
|
||||
[dev-dependencies.test-helpers]
|
||||
path = "../test/test-helpers"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0"
|
||||
flate2 = "1"
|
||||
|
||||
@@ -18,7 +18,11 @@
|
||||
use crate::vcs::git::{self, error::Error, ext, reference::Ref};
|
||||
use std::{cmp::Ordering, convert::TryFrom, fmt, str};
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The branch type we want to filter on.
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
pub enum BranchType {
|
||||
/// Local branches that are under `refs/heads/*`
|
||||
@@ -51,6 +55,7 @@ impl From<git2::BranchType> for BranchType {
|
||||
|
||||
/// A newtype wrapper over `String` to separate out the fact that a caller wants
|
||||
/// to fetch a branch.
|
||||
#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct BranchName(pub(crate) String);
|
||||
|
||||
@@ -89,6 +94,7 @@ impl BranchName {
|
||||
///
|
||||
/// **Note**: The `PartialOrd` and `Ord` implementations compare on `BranchName`
|
||||
/// only.
|
||||
#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Branch {
|
||||
/// Name identifier of the `Branch`.
|
||||
@@ -191,3 +197,34 @@ impl<'repo> TryFrom<git2::Reference<'repo>> for Branch {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use proptest::prelude::*;
|
||||
use test_helpers::roundtrip;
|
||||
|
||||
use super::{Branch, BranchName, BranchType};
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn prop_test_branch(branch in branch_strategy()) {
|
||||
roundtrip::json(branch)
|
||||
}
|
||||
}
|
||||
|
||||
fn branch_strategy() -> impl Strategy<Value = Branch> {
|
||||
prop_oneof![
|
||||
any::<String>().prop_map(|name| Branch {
|
||||
name: BranchName::new(&name),
|
||||
locality: BranchType::Local
|
||||
}),
|
||||
(any::<String>(), any::<String>()).prop_map(|(name, remote_name)| Branch {
|
||||
name: BranchName::new(&name),
|
||||
locality: BranchType::Remote {
|
||||
name: Some(remote_name),
|
||||
},
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,11 @@ use crate::vcs::git::error::Error;
|
||||
use git2::Oid;
|
||||
use std::{convert::TryFrom, str};
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
use serde::{de, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// `Author` is the static information of a [`git2::Signature`].
|
||||
#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Author {
|
||||
/// Name of the author.
|
||||
@@ -27,9 +31,30 @@ pub struct Author {
|
||||
/// Email of the author.
|
||||
pub email: String,
|
||||
/// Time the action was taken, e.g. time of commit.
|
||||
#[serde(
|
||||
serialize_with = "serialize_time",
|
||||
deserialize_with = "deserialize_time"
|
||||
)]
|
||||
pub time: git2::Time,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
fn deserialize_time<'de, D>(deserializer: D) -> Result<git2::Time, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let seconds: i64 = Deserialize::deserialize(deserializer)?;
|
||||
Ok(git2::Time::new(seconds, 0))
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
fn serialize_time<S>(t: &git2::Time, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_i64(t.seconds())
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Author {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use std::cmp::Ordering;
|
||||
@@ -61,9 +86,12 @@ impl<'repo> TryFrom<git2::Signature<'repo>> for Author {
|
||||
/// `Commit` is the static information of a [`git2::Commit`]. To get back the
|
||||
/// original `Commit` in the repository we can use the [`Oid`] to retrieve
|
||||
/// it.
|
||||
#[cfg_attr(feature = "serialize", derive(Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Commit {
|
||||
// TODO: Replace git2::Oid with git_ext::Oid (https://github.com/radicle-dev/radicle-git/issues/5)
|
||||
/// Object ID of the Commit, i.e. the SHA1 digest.
|
||||
#[serde(deserialize_with = "deserialize_oid")]
|
||||
pub id: Oid,
|
||||
/// The author of the commit.
|
||||
pub author: Author,
|
||||
@@ -74,9 +102,73 @@ pub struct Commit {
|
||||
/// The summary message of the commit.
|
||||
pub summary: String,
|
||||
/// The parents of this commit.
|
||||
#[serde(deserialize_with = "deserialize_vec_oid")]
|
||||
pub parents: Vec<Oid>,
|
||||
}
|
||||
|
||||
impl Commit {
|
||||
/// Returns the commit description text. This is the text after the one-line
|
||||
/// summary.
|
||||
#[must_use]
|
||||
pub fn description(&self) -> &str {
|
||||
self.message
|
||||
.strip_prefix(&self.summary)
|
||||
.unwrap_or(&self.message)
|
||||
.trim()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl Serialize for Commit {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_struct("Commit", 7)?;
|
||||
state.serialize_field("id", &self.id.to_string())?;
|
||||
state.serialize_field("author", &self.author)?;
|
||||
state.serialize_field("committer", &self.committer)?;
|
||||
state.serialize_field("summary", &self.summary)?;
|
||||
state.serialize_field("message", &self.message)?;
|
||||
state.serialize_field("description", &self.description())?;
|
||||
state.serialize_field(
|
||||
"parents",
|
||||
&self
|
||||
.parents
|
||||
.iter()
|
||||
.map(|oid| oid.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
fn deserialize_oid<'de, D>(deserializer: D) -> Result<Oid, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let oid: &str = Deserialize::deserialize(deserializer)?;
|
||||
Oid::from_str(oid).map_err(|_| {
|
||||
serde::de::Error::invalid_type(
|
||||
serde::de::Unexpected::Str(oid),
|
||||
&"a SHA1 hash not longer than 40 hex characters",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
fn deserialize_vec_oid<'de, D>(deserializer: D) -> Result<Vec<Oid>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let oids: Vec<&str> = Deserialize::deserialize(deserializer)?;
|
||||
oids.iter()
|
||||
.map(|key| Oid::from_str(key))
|
||||
.collect::<Result<Vec<Oid>, _>>()
|
||||
.map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
impl<'repo> TryFrom<git2::Commit<'repo>> for Commit {
|
||||
type Error = Error;
|
||||
|
||||
@@ -100,3 +192,39 @@ impl<'repo> TryFrom<git2::Commit<'repo>> for Commit {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use git2::Oid;
|
||||
use proptest::prelude::*;
|
||||
use test_helpers::roundtrip;
|
||||
|
||||
use super::{Author, Commit};
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
proptest! {
|
||||
#[test]
|
||||
fn prop_test_commits(commit in commits_strategy()) {
|
||||
roundtrip::json(commit)
|
||||
}
|
||||
}
|
||||
|
||||
fn commits_strategy() -> impl Strategy<Value = Commit> {
|
||||
("[a-fA-F0-9]{40}", any::<String>(), any::<i64>()).prop_map(|(id, text, time)| Commit {
|
||||
id: Oid::from_str(&id).unwrap(),
|
||||
author: Author {
|
||||
name: text.clone(),
|
||||
email: text.clone(),
|
||||
time: git2::Time::new(time, 0),
|
||||
},
|
||||
committer: Author {
|
||||
name: text.clone(),
|
||||
email: text.clone(),
|
||||
time: git2::Time::new(time, 0),
|
||||
},
|
||||
message: text.clone(),
|
||||
summary: text,
|
||||
parents: vec![Oid::from_str(&id).unwrap(), Oid::from_str(&id).unwrap()],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user