node: Propagate ref updates
Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
parent
cb4939fd75
commit
db6932d58c
|
|
@ -128,8 +128,12 @@ fn fetch<W: Write, H: Handle>(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!(
|
||||
|
|
|
|||
|
|
@ -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<RefUpdate>,
|
||||
},
|
||||
/// 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_
|
||||
|
|
|
|||
|
|
@ -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<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.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Remotes<V>(HashMap<RemoteId, Remote<V>>);
|
||||
|
|
@ -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<Vec<RefUpdate>, git2::Error>;
|
||||
fn raw(&self) -> &git2::Repository;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<git::RefString> = Lazy::new(|| git::refname!("heads/radicle/id"));
|
||||
pub static REMOTES_GLOB: Lazy<refspec::PatternString> =
|
||||
|
|
@ -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<Vec<RefUpdate>, 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::<Vec<_>>();
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ use crate::test::crypto::MockSigner;
|
|||
|
||||
pub fn storage<P: AsRef<Path>>(path: P) -> Storage {
|
||||
let path = path.as_ref();
|
||||
let proj_ids = arbitrary::set::<Id>(3..5);
|
||||
let signers = arbitrary::set::<MockSigner>(1..3);
|
||||
let proj_ids = arbitrary::set::<Id>(3..=3);
|
||||
let signers = arbitrary::set::<MockSigner>(3..=3);
|
||||
let mut storages = signers
|
||||
.into_iter()
|
||||
.map(|s| Storage::open(path, s).unwrap())
|
||||
|
|
|
|||
|
|
@ -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<Vec<RefUpdate>, git2::Error> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn raw(&self) -> &git2::Repository {
|
||||
|
|
|
|||
Loading…
Reference in New Issue