cob: Include commit timestamp in `Entry`

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-12-01 12:29:52 +01:00
parent 86beae2ebb
commit d061afdab1
No known key found for this signature in database
7 changed files with 54 additions and 14 deletions

View File

@ -9,6 +9,7 @@ use git_commit::{self as commit, Commit};
use git_ext::Oid; use git_ext::Oid;
use git_trailers::OwnedTrailer; use git_trailers::OwnedTrailer;
use crate::history::entry::Timestamp;
use crate::{ use crate::{
change::{self, store, Change}, change::{self, store, Change},
history::entry, history::entry,
@ -114,7 +115,7 @@ impl change::Storage for git2::Repository {
Signature::from((*key, sig)) Signature::from((*key, sig))
}; };
let id = write_commit(self, resource, tips, message, signature.clone(), tree)?; let (id, timestamp) = write_commit(self, resource, tips, message, signature.clone(), tree)?;
Ok(Change { Ok(Change {
id, id,
revision: revision.into(), revision: revision.into(),
@ -122,11 +123,13 @@ impl change::Storage for git2::Repository {
resource, resource,
manifest, manifest,
contents, contents,
timestamp,
}) })
} }
fn load(&self, id: Self::ObjectId) -> Result<Change, Self::LoadError> { fn load(&self, id: Self::ObjectId) -> Result<Change, Self::LoadError> {
let commit = Commit::read(self, id.into())?; let commit = Commit::read(self, id.into())?;
let timestamp = git2::Time::from(commit.committer().time).seconds() as u64;
let resource = parse_resource_trailer(commit.trailers())?; let resource = parse_resource_trailer(commit.trailers())?;
let mut signatures = Signatures::try_from(&commit)? let mut signatures = Signatures::try_from(&commit)?
.into_iter() .into_iter()
@ -149,6 +152,7 @@ impl change::Storage for git2::Repository {
resource, resource,
manifest, manifest,
contents, contents,
timestamp,
}) })
} }
} }
@ -208,7 +212,7 @@ fn write_commit<O>(
message: String, message: String,
signature: Signature, signature: Signature,
tree: git2::Tree, tree: git2::Tree,
) -> Result<Oid, error::Create> ) -> Result<(Oid, Timestamp), error::Create>
where where
O: AsRef<git2::Oid>, O: AsRef<git2::Oid>,
{ {
@ -221,14 +225,14 @@ where
{ {
let author = repo.signature()?; let author = repo.signature()?;
let timestamp = author.when().seconds() as Timestamp;
let mut headers = commit::Headers::new(); let mut headers = commit::Headers::new();
headers.push( headers.push(
"gpgsig", "gpgsig",
&String::from_utf8(crypto::ssh::ExtendedSignature::from(signature).to_armored())?, &String::from_utf8(crypto::ssh::ExtendedSignature::from(signature).to_armored())?,
); );
let author = commit::Author::try_from(&author)?; let author = commit::Author::try_from(&author)?;
let oid = Commit::new(
let commit = Commit::new(
tree.id(), tree.id(),
parents, parents,
author.clone(), author.clone(),
@ -236,11 +240,10 @@ where
headers, headers,
message, message,
trailers, trailers,
); )
commit .write(repo)?;
.write(repo)
.map(Oid::from) Ok((Oid::from(oid), timestamp))
.map_err(error::Create::from)
} }
} }

View File

@ -7,7 +7,10 @@ use std::{error::Error, fmt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{history::Contents, signatures, TypeName}; use crate::{
history::{Contents, Timestamp},
signatures, TypeName,
};
pub trait Storage { pub trait Storage {
type CreateError: Error + Send + Sync + 'static; type CreateError: Error + Send + Sync + 'static;
@ -59,6 +62,8 @@ pub struct Change<Resource, Id, Signature> {
pub manifest: Manifest, pub manifest: Manifest,
/// The contents that describe `Change`. /// The contents that describe `Change`.
pub contents: Contents, pub contents: Contents,
/// Timestamp of change.
pub timestamp: Timestamp,
} }
impl<Resource, Id, S> fmt::Display for Change<Resource, Id, S> impl<Resource, Id, S> fmt::Display for Change<Resource, Id, S>

View File

@ -70,6 +70,7 @@ fn evaluate_change(
change.resource, change.resource,
child_commits.iter().cloned(), child_commits.iter().cloned(),
change.contents().clone(), change.contents().clone(),
change.timestamp,
)) ))
} }

View File

@ -15,7 +15,7 @@ use radicle_crypto::PublicKey;
use crate::pruning_fold; use crate::pruning_fold;
pub mod entry; pub mod entry;
pub use entry::{Clock, Contents, Entry, EntryId, EntryWithClock}; pub use entry::{Clock, Contents, Entry, EntryId, EntryWithClock, Timestamp};
/// The DAG of changes making up the history of a collaborative object. /// The DAG of changes making up the history of a collaborative object.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -44,6 +44,7 @@ impl History {
actor: PublicKey, actor: PublicKey,
resource: Oid, resource: Oid,
contents: Contents, contents: Contents,
timestamp: Timestamp,
) -> Self ) -> Self
where where
Id: Into<EntryId>, Id: Into<EntryId>,
@ -55,6 +56,7 @@ impl History {
resource, resource,
children: vec![], children: vec![],
contents, contents,
timestamp,
}; };
let mut entries = HashMap::new(); let mut entries = HashMap::new();
entries.insert(id, EntryWithClock::from(root_entry)); entries.insert(id, EntryWithClock::from(root_entry));
@ -85,6 +87,16 @@ impl History {
.unwrap_or_default() .unwrap_or_default()
} }
/// Get the current history timestamp.
/// This is the latest timestamp of any tip.
pub fn timestamp(&self) -> Clock {
self.graph
.externals(petgraph::Direction::Outgoing)
.map(|n| self.graph[n].timestamp())
.max()
.unwrap_or_default()
}
/// A topological (parents before children) traversal of the dependency /// A topological (parents before children) traversal of the dependency
/// graph of this history. This is analagous to /// graph of this history. This is analagous to
/// [`std::iter::Iterator::fold`] in that it folds every change into an /// [`std::iter::Iterator::fold`] in that it folds every change into an
@ -120,6 +132,7 @@ impl History {
new_actor: PublicKey, new_actor: PublicKey,
new_resource: Oid, new_resource: Oid,
new_contents: Contents, new_contents: Contents,
new_timestamp: Timestamp,
) where ) where
Id: Into<EntryId>, Id: Into<EntryId>,
{ {
@ -131,6 +144,7 @@ impl History {
new_resource, new_resource,
std::iter::empty::<git2::Oid>(), std::iter::empty::<git2::Oid>(),
new_contents, new_contents,
new_timestamp,
); );
let new_ix = self.graph.add_node(EntryWithClock { let new_ix = self.graph.add_node(EntryWithClock {
entry: new_entry, entry: new_entry,

View File

@ -16,6 +16,9 @@ pub type Contents = Vec<u8>;
/// Logical clock used to track causality in change graph. /// Logical clock used to track causality in change graph.
pub type Clock = u64; pub type Clock = u64;
/// Local time in seconds since epoch.
pub type Timestamp = u64;
/// A unique identifier for a history entry. /// A unique identifier for a history entry.
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
pub struct EntryId(Oid); pub struct EntryId(Oid);
@ -51,6 +54,8 @@ pub struct Entry {
pub(super) children: Vec<EntryId>, pub(super) children: Vec<EntryId>,
/// The contents of this entry. /// The contents of this entry.
pub(super) contents: Contents, pub(super) contents: Contents,
/// The entry timestamp, as seconds since epoch.
pub(super) timestamp: Timestamp,
} }
impl Entry { impl Entry {
@ -60,6 +65,7 @@ impl Entry {
resource: Oid, resource: Oid,
children: ChildIds, children: ChildIds,
contents: Contents, contents: Contents,
timestamp: Timestamp,
) -> Self ) -> Self
where where
Id1: Into<EntryId>, Id1: Into<EntryId>,
@ -72,6 +78,7 @@ impl Entry {
resource, resource,
children: children.into_iter().map(|id| id.into()).collect(), children: children.into_iter().map(|id| id.into()).collect(),
contents, contents,
timestamp,
} }
} }
@ -90,6 +97,11 @@ impl Entry {
&self.actor &self.actor
} }
/// The entry timestamp.
pub fn timestamp(&self) -> Timestamp {
self.timestamp
}
/// The contents of this change /// The contents of this change
pub fn contents(&self) -> &Contents { pub fn contents(&self) -> &Contents {
&self.contents &self.contents

View File

@ -76,6 +76,7 @@ where
init_change.signature.key, init_change.signature.key,
resource.content_id(), resource.content_id(),
contents.clone(), contents.clone(),
init_change.timestamp,
); );
let object_id = init_change.id().into(); let object_id = init_change.id().into();

View File

@ -81,9 +81,13 @@ where
message, message,
}, },
)?; )?;
object object.history.extend(
.history change.id,
.extend(change.id, change.signature.key, change.resource, changes); change.signature.key,
change.resource,
changes,
change.timestamp,
);
storage storage
.update(identifier, typename, &object_id, &change) .update(identifier, typename, &object_id, &change)
.map_err(|err| error::Update::Refs { err: Box::new(err) })?; .map_err(|err| error::Update::Refs { err: Box::new(err) })?;