From d061afdab11ab6135c860f5288ad9d80faf1af17 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Thu, 1 Dec 2022 12:29:52 +0100 Subject: [PATCH] cob: Include commit timestamp in `Entry` Signed-off-by: Alexis Sellier --- radicle-cob/src/backend/git/change.rs | 21 +++++++++++-------- radicle-cob/src/change/store.rs | 7 ++++++- radicle-cob/src/change_graph/evaluation.rs | 1 + radicle-cob/src/history.rs | 16 +++++++++++++- radicle-cob/src/history/entry.rs | 12 +++++++++++ .../src/object/collaboration/create.rs | 1 + .../src/object/collaboration/update.rs | 10 ++++++--- 7 files changed, 54 insertions(+), 14 deletions(-) diff --git a/radicle-cob/src/backend/git/change.rs b/radicle-cob/src/backend/git/change.rs index 344d50f9..bb43cd58 100644 --- a/radicle-cob/src/backend/git/change.rs +++ b/radicle-cob/src/backend/git/change.rs @@ -9,6 +9,7 @@ use git_commit::{self as commit, Commit}; use git_ext::Oid; use git_trailers::OwnedTrailer; +use crate::history::entry::Timestamp; use crate::{ change::{self, store, Change}, history::entry, @@ -114,7 +115,7 @@ impl change::Storage for git2::Repository { 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 { id, revision: revision.into(), @@ -122,11 +123,13 @@ impl change::Storage for git2::Repository { resource, manifest, contents, + timestamp, }) } fn load(&self, id: Self::ObjectId) -> Result { 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 mut signatures = Signatures::try_from(&commit)? .into_iter() @@ -149,6 +152,7 @@ impl change::Storage for git2::Repository { resource, manifest, contents, + timestamp, }) } } @@ -208,7 +212,7 @@ fn write_commit( message: String, signature: Signature, tree: git2::Tree, -) -> Result +) -> Result<(Oid, Timestamp), error::Create> where O: AsRef, { @@ -221,14 +225,14 @@ where { let author = repo.signature()?; + let timestamp = author.when().seconds() as Timestamp; let mut headers = commit::Headers::new(); headers.push( "gpgsig", &String::from_utf8(crypto::ssh::ExtendedSignature::from(signature).to_armored())?, ); let author = commit::Author::try_from(&author)?; - - let commit = Commit::new( + let oid = Commit::new( tree.id(), parents, author.clone(), @@ -236,11 +240,10 @@ where headers, message, trailers, - ); - commit - .write(repo) - .map(Oid::from) - .map_err(error::Create::from) + ) + .write(repo)?; + + Ok((Oid::from(oid), timestamp)) } } diff --git a/radicle-cob/src/change/store.rs b/radicle-cob/src/change/store.rs index de1ff3c9..da0dae6d 100644 --- a/radicle-cob/src/change/store.rs +++ b/radicle-cob/src/change/store.rs @@ -7,7 +7,10 @@ use std::{error::Error, fmt}; use serde::{Deserialize, Serialize}; -use crate::{history::Contents, signatures, TypeName}; +use crate::{ + history::{Contents, Timestamp}, + signatures, TypeName, +}; pub trait Storage { type CreateError: Error + Send + Sync + 'static; @@ -59,6 +62,8 @@ pub struct Change { pub manifest: Manifest, /// The contents that describe `Change`. pub contents: Contents, + /// Timestamp of change. + pub timestamp: Timestamp, } impl fmt::Display for Change diff --git a/radicle-cob/src/change_graph/evaluation.rs b/radicle-cob/src/change_graph/evaluation.rs index 81a15037..1597f0fe 100644 --- a/radicle-cob/src/change_graph/evaluation.rs +++ b/radicle-cob/src/change_graph/evaluation.rs @@ -70,6 +70,7 @@ fn evaluate_change( change.resource, child_commits.iter().cloned(), change.contents().clone(), + change.timestamp, )) } diff --git a/radicle-cob/src/history.rs b/radicle-cob/src/history.rs index 94364f40..95c89ca8 100644 --- a/radicle-cob/src/history.rs +++ b/radicle-cob/src/history.rs @@ -15,7 +15,7 @@ use radicle_crypto::PublicKey; use crate::pruning_fold; 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. #[derive(Clone, Debug)] @@ -44,6 +44,7 @@ impl History { actor: PublicKey, resource: Oid, contents: Contents, + timestamp: Timestamp, ) -> Self where Id: Into, @@ -55,6 +56,7 @@ impl History { resource, children: vec![], contents, + timestamp, }; let mut entries = HashMap::new(); entries.insert(id, EntryWithClock::from(root_entry)); @@ -85,6 +87,16 @@ impl History { .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 /// graph of this history. This is analagous to /// [`std::iter::Iterator::fold`] in that it folds every change into an @@ -120,6 +132,7 @@ impl History { new_actor: PublicKey, new_resource: Oid, new_contents: Contents, + new_timestamp: Timestamp, ) where Id: Into, { @@ -131,6 +144,7 @@ impl History { new_resource, std::iter::empty::(), new_contents, + new_timestamp, ); let new_ix = self.graph.add_node(EntryWithClock { entry: new_entry, diff --git a/radicle-cob/src/history/entry.rs b/radicle-cob/src/history/entry.rs index a82a5253..2d685bd5 100644 --- a/radicle-cob/src/history/entry.rs +++ b/radicle-cob/src/history/entry.rs @@ -16,6 +16,9 @@ pub type Contents = Vec; /// Logical clock used to track causality in change graph. pub type Clock = u64; +/// Local time in seconds since epoch. +pub type Timestamp = u64; + /// A unique identifier for a history entry. #[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, PartialOrd, Ord)] pub struct EntryId(Oid); @@ -51,6 +54,8 @@ pub struct Entry { pub(super) children: Vec, /// The contents of this entry. pub(super) contents: Contents, + /// The entry timestamp, as seconds since epoch. + pub(super) timestamp: Timestamp, } impl Entry { @@ -60,6 +65,7 @@ impl Entry { resource: Oid, children: ChildIds, contents: Contents, + timestamp: Timestamp, ) -> Self where Id1: Into, @@ -72,6 +78,7 @@ impl Entry { resource, children: children.into_iter().map(|id| id.into()).collect(), contents, + timestamp, } } @@ -90,6 +97,11 @@ impl Entry { &self.actor } + /// The entry timestamp. + pub fn timestamp(&self) -> Timestamp { + self.timestamp + } + /// The contents of this change pub fn contents(&self) -> &Contents { &self.contents diff --git a/radicle-cob/src/object/collaboration/create.rs b/radicle-cob/src/object/collaboration/create.rs index afb125c1..2dddd714 100644 --- a/radicle-cob/src/object/collaboration/create.rs +++ b/radicle-cob/src/object/collaboration/create.rs @@ -76,6 +76,7 @@ where init_change.signature.key, resource.content_id(), contents.clone(), + init_change.timestamp, ); let object_id = init_change.id().into(); diff --git a/radicle-cob/src/object/collaboration/update.rs b/radicle-cob/src/object/collaboration/update.rs index dffd50a3..e9943949 100644 --- a/radicle-cob/src/object/collaboration/update.rs +++ b/radicle-cob/src/object/collaboration/update.rs @@ -81,9 +81,13 @@ where message, }, )?; - object - .history - .extend(change.id, change.signature.key, change.resource, changes); + object.history.extend( + change.id, + change.signature.key, + change.resource, + changes, + change.timestamp, + ); storage .update(identifier, typename, &object_id, &change) .map_err(|err| error::Update::Refs { err: Box::new(err) })?;