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() {
|
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!(
|
||||||
|
|
|
||||||
|
|
@ -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_
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue