From db6932d58ca22165fe8a438da519f1eee897b530 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Fri, 9 Sep 2022 14:33:04 +0200 Subject: [PATCH] node: Propagate ref updates Signed-off-by: Alexis Sellier --- node/src/control.rs | 6 ++++- node/src/protocol.rs | 18 ++++++++++---- node/src/storage.rs | 51 +++++++++++++++++++++++++++++++++++--- node/src/storage/git.rs | 52 ++++++++++++++++++++++++++++----------- node/src/test/fixtures.rs | 4 +-- node/src/test/storage.rs | 6 ++--- 6 files changed, 109 insertions(+), 28 deletions(-) diff --git a/node/src/control.rs b/node/src/control.rs index 8da83a47..abc020fc 100644 --- a/node/src/control.rs +++ b/node/src/control.rs @@ -128,8 +128,12 @@ fn fetch(id: Id, mut writer: W, handle: &H) -> Result<(), D for result in results.iter() { match result { - FetchResult::Fetched { from } => { + FetchResult::Fetched { from, updated } => { writeln!(writer, "ok: {} fetched from {}", &id, from)?; + + for update in updated { + writeln!(writer, "{}", update)?; + } } FetchResult::Error { from, error } => { writeln!( diff --git a/node/src/protocol.rs b/node/src/protocol.rs index 6712e55d..34008f1c 100644 --- a/node/src/protocol.rs +++ b/node/src/protocol.rs @@ -27,8 +27,8 @@ use crate::protocol::config::ProjectTracking; use crate::protocol::message::Message; use crate::protocol::peer::{Peer, PeerError, PeerState}; use crate::protocol::wire::Encode; -use crate::storage::{self, ReadRepository, WriteRepository}; -use crate::storage::{Inventory, WriteStorage}; +use crate::storage; +use crate::storage::{Inventory, ReadRepository, RefUpdate, WriteRepository, WriteStorage}; pub use crate::protocol::config::{Config, Network}; @@ -82,7 +82,10 @@ pub enum FetchLookup { #[derive(Debug)] pub enum FetchResult { /// Successful fetch from a seed. - Fetched { from: net::SocketAddr }, + Fetched { + from: net::SocketAddr, + updated: Vec, + }, /// Error fetching the resource from a seed. Error { from: net::SocketAddr, @@ -412,8 +415,13 @@ where path: format!("/{}", id).into(), ..Url::default() }) { - Ok(()) => { - results_.send(FetchResult::Fetched { from: peer.addr }).ok(); + Ok(updated) => { + results_ + .send(FetchResult::Fetched { + from: peer.addr, + updated, + }) + .ok(); } Err(err) => { results_ diff --git a/node/src/storage.rs b/node/src/storage.rs index 9410155b..f0929b9d 100644 --- a/node/src/storage.rs +++ b/node/src/storage.rs @@ -2,10 +2,10 @@ pub mod git; pub mod refs; use std::collections::hash_map; -use std::io; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::path::Path; +use std::{fmt, io}; use radicle_git_ext as git_ext; use thiserror::Error; @@ -15,7 +15,7 @@ pub use radicle_git_ext::Oid; use crate::collections::HashMap; use crate::crypto::{self, PublicKey, Unverified, Verified}; use crate::git::Url; -use crate::git::{RefError, RefStr}; +use crate::git::{RefError, RefStr, RefString}; use crate::identity; use crate::identity::{Id, IdError, Project}; use crate::storage::refs::Refs; @@ -48,6 +48,51 @@ pub enum Error { pub type RemoteId = PublicKey; +/// An update to a reference. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RefUpdate { + Updated { name: RefString, old: Oid, new: Oid }, + Created { name: RefString, oid: Oid }, + Deleted { name: RefString, oid: Oid }, + Skipped { name: RefString, oid: Oid }, +} + +impl RefUpdate { + pub fn from(name: RefString, old: impl Into, new: impl Into) -> Self { + let old = old.into(); + let new = new.into(); + + if old.is_zero() { + Self::Created { name, oid: new } + } else if new.is_zero() { + Self::Deleted { name, oid: old } + } else if old != new { + Self::Updated { name, old, new } + } else { + Self::Skipped { name, oid: old } + } + } +} + +impl fmt::Display for RefUpdate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Updated { name, old, new } => { + write!(f, "~ {:.7}..{:.7} {}", old, new, name) + } + Self::Created { name, oid } => { + write!(f, "* 0000000..{:.7} {}", oid, name) + } + Self::Deleted { name, oid } => { + write!(f, "- {:.7}..0000000 {}", oid, name) + } + Self::Skipped { name, oid } => { + write!(f, "= {:.7}..{:.7} {}", oid, oid, name) + } + } + } +} + /// Project remotes. Tracks the git state of a project. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Remotes(HashMap>); @@ -192,7 +237,7 @@ pub trait ReadRepository<'r> { } pub trait WriteRepository<'r>: ReadRepository<'r> { - fn fetch(&mut self, url: &Url) -> Result<(), git2::Error>; + fn fetch(&mut self, url: &Url) -> Result, git2::Error>; fn raw(&self) -> &git2::Repository; } diff --git a/node/src/storage/git.rs b/node/src/storage/git.rs index a86ea3dc..dfe9278e 100644 --- a/node/src/storage/git.rs +++ b/node/src/storage/git.rs @@ -18,7 +18,7 @@ use crate::storage::{ Error, Inventory, ReadRepository, ReadStorage, Remote, WriteRepository, WriteStorage, }; -use super::RemoteId; +use super::{RefUpdate, RemoteId}; pub static RADICLE_ID_REF: Lazy = Lazy::new(|| git::refname!("heads/radicle/id")); pub static REMOTES_GLOB: Lazy = @@ -308,9 +308,8 @@ impl<'r> ReadRepository<'r> for Repository { impl<'r> WriteRepository<'r> for Repository { /// Fetch all remotes of a project from the given URL. - fn fetch(&mut self, url: &git::Url) -> Result<(), git2::Error> { + fn fetch(&mut self, url: &git::Url) -> Result, git2::Error> { // TODO: Have function to fetch specific remotes. - // TODO: Return meaningful info on success. // // Repository layout should look like this: // @@ -322,15 +321,32 @@ impl<'r> WriteRepository<'r> for Repository { // let url = url.to_string(); let refs: &[&str] = &["refs/remotes/*:refs/remotes/*"]; - let mut remote = self.backend.remote_anonymous(&url)?; - let mut opts = git2::FetchOptions::default(); + let mut updates = Vec::new(); + let mut callbacks = git2::RemoteCallbacks::new(); - // TODO: Make sure we verify before pruning, as pruning may get us into - // a state we can't roll back. - opts.prune(git2::FetchPrune::On); - remote.fetch(refs, Some(&mut opts), None)?; + callbacks.update_tips(|name, old, new| { + if let Ok(name) = git::RefString::try_from(name) { + updates.push(RefUpdate::from(name, old, new)); + } else { + log::warn!("Invalid ref `{}` detected; aborting fetch", name); + return false; + } + // Returning `true` ensures the process is not aborted. + true + }); - Ok(()) + { + let mut remote = self.backend.remote_anonymous(&url)?; + let mut opts = git2::FetchOptions::default(); + opts.remote_callbacks(callbacks); + + // TODO: Make sure we verify before pruning, as pruning may get us into + // a state we can't roll back. + opts.prune(git2::FetchPrune::On); + remote.fetch(refs, Some(&mut opts), None)?; + } + + Ok(updates) } fn raw(&self) -> &git2::Repository { @@ -392,20 +408,28 @@ mod tests { let inventory = alice.inventory().unwrap(); let proj = inventory.first().unwrap(); let repo = alice.repository(proj).unwrap(); - let remotes = repo.remotes().unwrap(); + let remotes = repo.remotes().unwrap().collect::>(); let refname = git::refname!("heads/master"); // Have Bob fetch Alice's refs. - bob.repository(proj) + let updates = bob + .repository(proj) .unwrap() .fetch(&git::Url { - host: Some(alice.path().to_string_lossy().to_string()), scheme: git_url::Scheme::File, - path: format!("/{}", proj).into(), + path: alice + .path() + .join(proj.to_string()) + .to_string_lossy() + .into_owned() + .into(), ..git::Url::default() }) .unwrap(); + // Four refs are created for each remote. + assert_eq!(updates.len(), remotes.len() * 4); + for remote in remotes { let (id, _) = remote.unwrap(); let alice_repo = alice.repository(proj).unwrap(); diff --git a/node/src/test/fixtures.rs b/node/src/test/fixtures.rs index 832e6ed7..ef86c160 100644 --- a/node/src/test/fixtures.rs +++ b/node/src/test/fixtures.rs @@ -9,8 +9,8 @@ use crate::test::crypto::MockSigner; pub fn storage>(path: P) -> Storage { let path = path.as_ref(); - let proj_ids = arbitrary::set::(3..5); - let signers = arbitrary::set::(1..3); + let proj_ids = arbitrary::set::(3..=3); + let signers = arbitrary::set::(3..=3); let mut storages = signers .into_iter() .map(|s| Storage::open(path, s).unwrap()) diff --git a/node/src/test/storage.rs b/node/src/test/storage.rs index e25e3e4a..cbcf0589 100644 --- a/node/src/test/storage.rs +++ b/node/src/test/storage.rs @@ -3,7 +3,7 @@ use git_url::Url; use crate::crypto::{PublicKey, Verified}; use crate::git; use crate::identity::{Id, Project}; -use crate::storage::refs; +use crate::storage::{refs, RefUpdate}; use crate::storage::{ Error, Inventory, ReadRepository, ReadStorage, Remote, RemoteId, WriteRepository, WriteStorage, }; @@ -122,8 +122,8 @@ impl ReadRepository<'_> for MockRepository { } impl WriteRepository<'_> for MockRepository { - fn fetch(&mut self, _url: &Url) -> Result<(), git2::Error> { - Ok(()) + fn fetch(&mut self, _url: &Url) -> Result, git2::Error> { + Ok(vec![]) } fn raw(&self) -> &git2::Repository {