node: Propagate ref updates

Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
Alexis Sellier 2022-09-09 14:33:04 +02:00
parent cb4939fd75
commit db6932d58c
No known key found for this signature in database
6 changed files with 109 additions and 28 deletions

View File

@ -128,8 +128,12 @@ fn fetch<W: Write, H: Handle>(id: Id, mut writer: W, handle: &H) -> Result<(), D
for result in results.iter() { for result in results.iter() {
match result { match result {
FetchResult::Fetched { from } => { FetchResult::Fetched { from, updated } => {
writeln!(writer, "ok: {} fetched from {}", &id, from)?; writeln!(writer, "ok: {} fetched from {}", &id, from)?;
for update in updated {
writeln!(writer, "{}", update)?;
}
} }
FetchResult::Error { from, error } => { FetchResult::Error { from, error } => {
writeln!( writeln!(

View File

@ -27,8 +27,8 @@ use crate::protocol::config::ProjectTracking;
use crate::protocol::message::Message; use crate::protocol::message::Message;
use crate::protocol::peer::{Peer, PeerError, PeerState}; use crate::protocol::peer::{Peer, PeerError, PeerState};
use crate::protocol::wire::Encode; use crate::protocol::wire::Encode;
use crate::storage::{self, ReadRepository, WriteRepository}; use crate::storage;
use crate::storage::{Inventory, WriteStorage}; use crate::storage::{Inventory, ReadRepository, RefUpdate, WriteRepository, WriteStorage};
pub use crate::protocol::config::{Config, Network}; pub use crate::protocol::config::{Config, Network};
@ -82,7 +82,10 @@ pub enum FetchLookup {
#[derive(Debug)] #[derive(Debug)]
pub enum FetchResult { pub enum FetchResult {
/// Successful fetch from a seed. /// Successful fetch from a seed.
Fetched { from: net::SocketAddr }, Fetched {
from: net::SocketAddr,
updated: Vec<RefUpdate>,
},
/// Error fetching the resource from a seed. /// Error fetching the resource from a seed.
Error { Error {
from: net::SocketAddr, from: net::SocketAddr,
@ -412,8 +415,13 @@ where
path: format!("/{}", id).into(), path: format!("/{}", id).into(),
..Url::default() ..Url::default()
}) { }) {
Ok(()) => { Ok(updated) => {
results_.send(FetchResult::Fetched { from: peer.addr }).ok(); results_
.send(FetchResult::Fetched {
from: peer.addr,
updated,
})
.ok();
} }
Err(err) => { Err(err) => {
results_ results_

View File

@ -2,10 +2,10 @@ pub mod git;
pub mod refs; pub mod refs;
use std::collections::hash_map; use std::collections::hash_map;
use std::io;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::path::Path; use std::path::Path;
use std::{fmt, io};
use radicle_git_ext as git_ext; use radicle_git_ext as git_ext;
use thiserror::Error; use thiserror::Error;
@ -15,7 +15,7 @@ pub use radicle_git_ext::Oid;
use crate::collections::HashMap; use crate::collections::HashMap;
use crate::crypto::{self, PublicKey, Unverified, Verified}; use crate::crypto::{self, PublicKey, Unverified, Verified};
use crate::git::Url; use crate::git::Url;
use crate::git::{RefError, RefStr}; use crate::git::{RefError, RefStr, RefString};
use crate::identity; use crate::identity;
use crate::identity::{Id, IdError, Project}; use crate::identity::{Id, IdError, Project};
use crate::storage::refs::Refs; use crate::storage::refs::Refs;
@ -48,6 +48,51 @@ pub enum Error {
pub type RemoteId = PublicKey; 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<Oid>, new: impl Into<Oid>) -> 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. /// Project remotes. Tracks the git state of a project.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Remotes<V>(HashMap<RemoteId, Remote<V>>); pub struct Remotes<V>(HashMap<RemoteId, Remote<V>>);
@ -192,7 +237,7 @@ pub trait ReadRepository<'r> {
} }
pub trait WriteRepository<'r>: ReadRepository<'r> { pub trait WriteRepository<'r>: ReadRepository<'r> {
fn fetch(&mut self, url: &Url) -> Result<(), git2::Error>; fn fetch(&mut self, url: &Url) -> Result<Vec<RefUpdate>, git2::Error>;
fn raw(&self) -> &git2::Repository; fn raw(&self) -> &git2::Repository;
} }

View File

@ -18,7 +18,7 @@ use crate::storage::{
Error, Inventory, ReadRepository, ReadStorage, Remote, WriteRepository, WriteStorage, Error, Inventory, ReadRepository, ReadStorage, Remote, WriteRepository, WriteStorage,
}; };
use super::RemoteId; use super::{RefUpdate, RemoteId};
pub static RADICLE_ID_REF: Lazy<git::RefString> = Lazy::new(|| git::refname!("heads/radicle/id")); pub static RADICLE_ID_REF: Lazy<git::RefString> = Lazy::new(|| git::refname!("heads/radicle/id"));
pub static REMOTES_GLOB: Lazy<refspec::PatternString> = pub static REMOTES_GLOB: Lazy<refspec::PatternString> =
@ -308,9 +308,8 @@ impl<'r> ReadRepository<'r> for Repository {
impl<'r> WriteRepository<'r> for Repository { impl<'r> WriteRepository<'r> for Repository {
/// Fetch all remotes of a project from the given URL. /// 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<Vec<RefUpdate>, git2::Error> {
// TODO: Have function to fetch specific remotes. // TODO: Have function to fetch specific remotes.
// TODO: Return meaningful info on success.
// //
// Repository layout should look like this: // Repository layout should look like this:
// //
@ -322,15 +321,32 @@ impl<'r> WriteRepository<'r> for Repository {
// //
let url = url.to_string(); let url = url.to_string();
let refs: &[&str] = &["refs/remotes/*:refs/remotes/*"]; let refs: &[&str] = &["refs/remotes/*:refs/remotes/*"];
let mut updates = Vec::new();
let mut callbacks = git2::RemoteCallbacks::new();
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
});
{
let mut remote = self.backend.remote_anonymous(&url)?; let mut remote = self.backend.remote_anonymous(&url)?;
let mut opts = git2::FetchOptions::default(); let mut opts = git2::FetchOptions::default();
opts.remote_callbacks(callbacks);
// TODO: Make sure we verify before pruning, as pruning may get us into // TODO: Make sure we verify before pruning, as pruning may get us into
// a state we can't roll back. // a state we can't roll back.
opts.prune(git2::FetchPrune::On); opts.prune(git2::FetchPrune::On);
remote.fetch(refs, Some(&mut opts), None)?; remote.fetch(refs, Some(&mut opts), None)?;
}
Ok(()) Ok(updates)
} }
fn raw(&self) -> &git2::Repository { fn raw(&self) -> &git2::Repository {
@ -392,20 +408,28 @@ mod tests {
let inventory = alice.inventory().unwrap(); let inventory = alice.inventory().unwrap();
let proj = inventory.first().unwrap(); let proj = inventory.first().unwrap();
let repo = alice.repository(proj).unwrap(); let repo = alice.repository(proj).unwrap();
let remotes = repo.remotes().unwrap(); let remotes = repo.remotes().unwrap().collect::<Vec<_>>();
let refname = git::refname!("heads/master"); let refname = git::refname!("heads/master");
// Have Bob fetch Alice's refs. // Have Bob fetch Alice's refs.
bob.repository(proj) let updates = bob
.repository(proj)
.unwrap() .unwrap()
.fetch(&git::Url { .fetch(&git::Url {
host: Some(alice.path().to_string_lossy().to_string()),
scheme: git_url::Scheme::File, 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() ..git::Url::default()
}) })
.unwrap(); .unwrap();
// Four refs are created for each remote.
assert_eq!(updates.len(), remotes.len() * 4);
for remote in remotes { for remote in remotes {
let (id, _) = remote.unwrap(); let (id, _) = remote.unwrap();
let alice_repo = alice.repository(proj).unwrap(); let alice_repo = alice.repository(proj).unwrap();

View File

@ -9,8 +9,8 @@ use crate::test::crypto::MockSigner;
pub fn storage<P: AsRef<Path>>(path: P) -> Storage { pub fn storage<P: AsRef<Path>>(path: P) -> Storage {
let path = path.as_ref(); let path = path.as_ref();
let proj_ids = arbitrary::set::<Id>(3..5); let proj_ids = arbitrary::set::<Id>(3..=3);
let signers = arbitrary::set::<MockSigner>(1..3); let signers = arbitrary::set::<MockSigner>(3..=3);
let mut storages = signers let mut storages = signers
.into_iter() .into_iter()
.map(|s| Storage::open(path, s).unwrap()) .map(|s| Storage::open(path, s).unwrap())

View File

@ -3,7 +3,7 @@ use git_url::Url;
use crate::crypto::{PublicKey, Verified}; use crate::crypto::{PublicKey, Verified};
use crate::git; use crate::git;
use crate::identity::{Id, Project}; use crate::identity::{Id, Project};
use crate::storage::refs; use crate::storage::{refs, RefUpdate};
use crate::storage::{ use crate::storage::{
Error, Inventory, ReadRepository, ReadStorage, Remote, RemoteId, WriteRepository, WriteStorage, Error, Inventory, ReadRepository, ReadStorage, Remote, RemoteId, WriteRepository, WriteStorage,
}; };
@ -122,8 +122,8 @@ impl ReadRepository<'_> for MockRepository {
} }
impl WriteRepository<'_> for MockRepository { impl WriteRepository<'_> for MockRepository {
fn fetch(&mut self, _url: &Url) -> Result<(), git2::Error> { fn fetch(&mut self, _url: &Url) -> Result<Vec<RefUpdate>, git2::Error> {
Ok(()) Ok(vec![])
} }
fn raw(&self) -> &git2::Repository { fn raw(&self) -> &git2::Repository {