radicle: Teach `rad sync` and `rad clone` to accept feature levels
Teach the `rad sync` and `rad clone` commands to accept the `--signed-refs-feature-level` option. This option configures that fetch to use the minimum feature level provided when fetching from other nodes. This overrides the value of `node.fetch.signedReferences.featureLevel.minimum`, and should only be used in scenarios where it is necessary to downgrade the security of a fetch for backwards compatibility. To achieve this, the `Handle::fetch` method introduces an optional parameter for threading through the `FeatureLevel`.
This commit is contained in:
parent
ef4ddf06c3
commit
33db6637b4
|
|
@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## New Features
|
## New Features
|
||||||
|
|
||||||
|
- Teach the `rad sync` and `rad clone` commands to accept the
|
||||||
|
`--signed-refs-feature-level` option. This option configures that fetch to use
|
||||||
|
the minimum feature level provided when fetching from other nodes. This
|
||||||
|
overrides the value of `node.fetch.signedReferences.featureLevel.minimum`, and
|
||||||
|
should only be used in scenarios where it is necessary to tolerate a lower
|
||||||
|
minimum feature level for a fetch to achieve backwards compatibility.
|
||||||
- Fix the signed references reading process by correctly choosing the first,
|
- Fix the signed references reading process by correctly choosing the first,
|
||||||
non-replayed commit. This only occurs if duplicate signatures are found and
|
non-replayed commit. This only occurs if duplicate signatures are found and
|
||||||
the process needs to find the first legitimate commit of the namespace.
|
the process needs to find the first legitimate commit of the namespace.
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,17 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use crate::node::SyncSettings;
|
|
||||||
use radicle::identity::doc::RepoId;
|
use radicle::identity::doc::RepoId;
|
||||||
use radicle::identity::IdError;
|
use radicle::identity::IdError;
|
||||||
use radicle::node::policy::Scope;
|
use radicle::node::policy::Scope;
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
|
use radicle::storage::refs;
|
||||||
|
|
||||||
|
use crate::common_args::{
|
||||||
|
SignedReferencesFeatureLevel, SignedReferencesFeatureLevelParser,
|
||||||
|
ABOUT_FETCH_SIGNED_REFERENCES_FEATURE_LEVEL_MINIMUM,
|
||||||
|
};
|
||||||
|
use crate::node::SyncSettings;
|
||||||
use crate::terminal;
|
use crate::terminal;
|
||||||
|
|
||||||
const ABOUT: &str = "Clone a Radicle repository";
|
const ABOUT: &str = "Clone a Radicle repository";
|
||||||
|
|
@ -35,6 +40,13 @@ pub(super) struct SyncArgs {
|
||||||
/// Valid arguments are for example "10s", "5min" or "2h 37min"
|
/// Valid arguments are for example "10s", "5min" or "2h 37min"
|
||||||
#[arg(long, value_parser = humantime::parse_duration, default_value = "9s")]
|
#[arg(long, value_parser = humantime::parse_duration, default_value = "9s")]
|
||||||
timeout: std::time::Duration,
|
timeout: std::time::Duration,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
value_parser = SignedReferencesFeatureLevelParser,
|
||||||
|
help = ABOUT_FETCH_SIGNED_REFERENCES_FEATURE_LEVEL_MINIMUM
|
||||||
|
)]
|
||||||
|
signed_refs_feature_level: Option<SignedReferencesFeatureLevel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SyncArgs> for SyncSettings {
|
impl From<SyncArgs> for SyncSettings {
|
||||||
|
|
@ -42,6 +54,9 @@ impl From<SyncArgs> for SyncSettings {
|
||||||
SyncSettings {
|
SyncSettings {
|
||||||
timeout: args.timeout,
|
timeout: args.timeout,
|
||||||
seeds: args.seeds.into_iter().collect(),
|
seeds: args.seeds.into_iter().collect(),
|
||||||
|
signed_references_minimum_feature_level: args
|
||||||
|
.signed_refs_feature_level
|
||||||
|
.map(refs::FeatureLevel::from),
|
||||||
..SyncSettings::default()
|
..SyncSettings::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -324,7 +324,12 @@ pub fn fetch(
|
||||||
}
|
}
|
||||||
if let Some((nid, addr)) = fetcher.next_fetch() {
|
if let Some((nid, addr)) = fetcher.next_fetch() {
|
||||||
spinner.emit_fetching(&nid, &addr, &progress);
|
spinner.emit_fetching(&nid, &addr, &progress);
|
||||||
let result = node.fetch(rid, nid, settings.timeout)?;
|
let result = node.fetch(
|
||||||
|
rid,
|
||||||
|
nid,
|
||||||
|
settings.timeout,
|
||||||
|
settings.signed_references_minimum_feature_level,
|
||||||
|
)?;
|
||||||
match fetcher.fetch_complete(nid, result) {
|
match fetcher.fetch_complete(nid, result) {
|
||||||
std::ops::ControlFlow::Continue(update) => {
|
std::ops::ControlFlow::Continue(update) => {
|
||||||
spinner.emit_progress(&update);
|
spinner.emit_progress(&update);
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,13 @@ use clap::{Parser, Subcommand, ValueEnum};
|
||||||
use radicle::{
|
use radicle::{
|
||||||
node::{sync, NodeId},
|
node::{sync, NodeId},
|
||||||
prelude::RepoId,
|
prelude::RepoId,
|
||||||
|
storage::refs,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::common_args::{
|
||||||
|
SignedReferencesFeatureLevel, SignedReferencesFeatureLevelParser,
|
||||||
|
ABOUT_FETCH_SIGNED_REFERENCES_FEATURE_LEVEL_MINIMUM,
|
||||||
|
};
|
||||||
use crate::node::SyncSettings;
|
use crate::node::SyncSettings;
|
||||||
|
|
||||||
const ABOUT: &str = "Sync repositories to the network";
|
const ABOUT: &str = "Sync repositories to the network";
|
||||||
|
|
@ -136,6 +141,14 @@ pub(super) struct SyncArgs {
|
||||||
/// <RID> is ignored with `--inventory`
|
/// <RID> is ignored with `--inventory`
|
||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
inventory: bool,
|
inventory: bool,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
requires = "fetch",
|
||||||
|
value_parser = SignedReferencesFeatureLevelParser,
|
||||||
|
help = ABOUT_FETCH_SIGNED_REFERENCES_FEATURE_LEVEL_MINIMUM
|
||||||
|
)]
|
||||||
|
signed_refs_feature_level: Option<SignedReferencesFeatureLevel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyncArgs {
|
impl SyncArgs {
|
||||||
|
|
@ -220,9 +233,13 @@ impl From<SyncArgs> for SyncMode {
|
||||||
} else {
|
} else {
|
||||||
assert!(!args.inventory);
|
assert!(!args.inventory);
|
||||||
let direction = args.direction();
|
let direction = args.direction();
|
||||||
|
let timeout = args.timeout();
|
||||||
|
let replicas = args.replication();
|
||||||
|
let feature_level = args.signed_refs_feature_level.map(refs::FeatureLevel::from);
|
||||||
let mut settings = SyncSettings::default()
|
let mut settings = SyncSettings::default()
|
||||||
.timeout(args.timeout())
|
.timeout(timeout)
|
||||||
.replicas(args.replication());
|
.replicas(replicas)
|
||||||
|
.minimum_feature_level(feature_level);
|
||||||
if !args.seeds.is_empty() {
|
if !args.seeds.is_empty() {
|
||||||
settings.seeds = args.seeds.into_iter().collect();
|
settings.seeds = args.seeds.into_iter().collect();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use radicle::storage::refs;
|
||||||
|
|
||||||
|
pub const ABOUT_FETCH_SIGNED_REFERENCES_FEATURE_LEVEL_MINIMUM: &str = r#"
|
||||||
|
Perform the fetch using the provided Signed References minimum feature
|
||||||
|
level.
|
||||||
|
|
||||||
|
The options for this value provide the following behavior:
|
||||||
|
|
||||||
|
`parent`: Only the namespaces that use the parent feature level will be
|
||||||
|
accepted. This prevents graft attacks and replay attacks from
|
||||||
|
occurring. This provides the most security.
|
||||||
|
|
||||||
|
`root`: Only the namespaces that use the root feature level will be
|
||||||
|
accepted. This prevents graft attacks from occurring. This option
|
||||||
|
should be only used if you trust the node you are fetching from, and
|
||||||
|
want to bypass security for backwards compatibility.
|
||||||
|
|
||||||
|
`none`: All namespaces will be fetched regardless of feature level
|
||||||
|
detected, and provides no security against graft attacks or replay
|
||||||
|
attacks. This option should be only used if you trust the node you are
|
||||||
|
fetching from, and want to bypass security for backwards compatibility.
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct SignedReferencesFeatureLevel {
|
||||||
|
inner: refs::FeatureLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SignedReferencesFeatureLevel> for refs::FeatureLevel {
|
||||||
|
fn from(SignedReferencesFeatureLevel { inner }: SignedReferencesFeatureLevel) -> Self {
|
||||||
|
inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for SignedReferencesFeatureLevel {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let inner = match s {
|
||||||
|
"none" => refs::FeatureLevel::None,
|
||||||
|
"root" => refs::FeatureLevel::Root,
|
||||||
|
"parent" => refs::FeatureLevel::Parent,
|
||||||
|
_ => return Err("invalid feature level"),
|
||||||
|
};
|
||||||
|
Ok(Self { inner })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SignedReferencesFeatureLevelParser;
|
||||||
|
|
||||||
|
impl clap::builder::TypedValueParser for SignedReferencesFeatureLevelParser {
|
||||||
|
type Value = SignedReferencesFeatureLevel;
|
||||||
|
|
||||||
|
fn parse_ref(
|
||||||
|
&self,
|
||||||
|
cmd: &clap::Command,
|
||||||
|
arg: Option<&clap::Arg>,
|
||||||
|
value: &std::ffi::OsStr,
|
||||||
|
) -> Result<Self::Value, clap::Error> {
|
||||||
|
<SignedReferencesFeatureLevel as std::str::FromStr>::from_str.parse_ref(cmd, arg, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn possible_values(
|
||||||
|
&self,
|
||||||
|
) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
|
||||||
|
use clap::builder::PossibleValue;
|
||||||
|
Some(Box::new(
|
||||||
|
[
|
||||||
|
PossibleValue::new("parent"),
|
||||||
|
PossibleValue::new("root"),
|
||||||
|
PossibleValue::new("none"),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ pub mod pager;
|
||||||
pub mod project;
|
pub mod project;
|
||||||
pub mod terminal;
|
pub mod terminal;
|
||||||
|
|
||||||
|
mod common_args;
|
||||||
mod warning;
|
mod warning;
|
||||||
|
|
||||||
extern crate radicle_localtime as localtime;
|
extern crate radicle_localtime as localtime;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::io::Write;
|
||||||
|
|
||||||
use radicle::node::sync;
|
use radicle::node::sync;
|
||||||
use radicle::node::{Handle as _, NodeId};
|
use radicle::node::{Handle as _, NodeId};
|
||||||
use radicle::storage::{ReadRepository, RepositoryError};
|
use radicle::storage::{refs, ReadRepository, RepositoryError};
|
||||||
use radicle::{Node, Profile};
|
use radicle::{Node, Profile};
|
||||||
|
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
|
|
@ -21,6 +21,8 @@ pub struct SyncSettings {
|
||||||
pub seeds: BTreeSet<NodeId>,
|
pub seeds: BTreeSet<NodeId>,
|
||||||
/// How long to wait for syncing to complete.
|
/// How long to wait for syncing to complete.
|
||||||
pub timeout: time::Duration,
|
pub timeout: time::Duration,
|
||||||
|
/// The minimum feature level to accept when fetching signed references.
|
||||||
|
pub signed_references_minimum_feature_level: Option<refs::FeatureLevel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyncSettings {
|
impl SyncSettings {
|
||||||
|
|
@ -31,6 +33,13 @@ impl SyncSettings {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set minimum feature level for fetching signed references.
|
||||||
|
#[must_use]
|
||||||
|
pub fn minimum_feature_level(mut self, feature_level: Option<refs::FeatureLevel>) -> Self {
|
||||||
|
self.signed_references_minimum_feature_level = feature_level;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set replicas.
|
/// Set replicas.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn replicas(mut self, replicas: sync::ReplicationFactor) -> Self {
|
pub fn replicas(mut self, replicas: sync::ReplicationFactor) -> Self {
|
||||||
|
|
@ -69,6 +78,7 @@ impl Default for SyncSettings {
|
||||||
replicas: sync::ReplicationFactor::default(),
|
replicas: sync::ReplicationFactor::default(),
|
||||||
seeds: BTreeSet::new(),
|
seeds: BTreeSet::new(),
|
||||||
timeout: DEFAULT_SYNC_TIMEOUT,
|
timeout: DEFAULT_SYNC_TIMEOUT,
|
||||||
|
signed_references_minimum_feature_level: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,7 @@ fn test_cob_deletion() {
|
||||||
|
|
||||||
radicle::assert_matches!(
|
radicle::assert_matches!(
|
||||||
bob.handle
|
bob.handle
|
||||||
.fetch(rid, alice.id, radicle::node::DEFAULT_TIMEOUT)
|
.fetch(rid, alice.id, radicle::node::DEFAULT_TIMEOUT, None)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
radicle::node::FetchResult::Success { .. }
|
radicle::node::FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,9 @@ fn rad_id_threshold() {
|
||||||
alice.connect(&seed).connect(&bob);
|
alice.connect(&seed).connect(&bob);
|
||||||
bob.connect(&seed);
|
bob.connect(&seed);
|
||||||
alice.routes_to(&[(acme, seed.id)]);
|
alice.routes_to(&[(acme, seed.id)]);
|
||||||
seed.handle.fetch(acme, alice.id, DEFAULT_TIMEOUT).unwrap();
|
seed.handle
|
||||||
|
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
formula(&environment.tempdir(), "examples/rad-id-threshold.md")
|
formula(&environment.tempdir(), "examples/rad-id-threshold.md")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -323,10 +325,12 @@ fn rad_id_collaboration() {
|
||||||
.connect(&distrustful)
|
.connect(&distrustful)
|
||||||
.converge([&seed, &distrustful]);
|
.converge([&seed, &distrustful]);
|
||||||
|
|
||||||
seed.handle.fetch(acme, alice.id, DEFAULT_TIMEOUT).unwrap();
|
seed.handle
|
||||||
|
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
distrustful
|
distrustful
|
||||||
.handle
|
.handle
|
||||||
.fetch(acme, alice.id, DEFAULT_TIMEOUT)
|
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
formula(&environment.tempdir(), "examples/rad-id-collaboration.md")
|
formula(&environment.tempdir(), "examples/rad-id-collaboration.md")
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,9 @@ fn rad_clean() {
|
||||||
bob.connect(&alice).converge([&alice]);
|
bob.connect(&alice).converge([&alice]);
|
||||||
eve.connect(&alice).converge([&alice]);
|
eve.connect(&alice).converge([&alice]);
|
||||||
|
|
||||||
eve.handle.fetch(acme, alice.id, DEFAULT_TIMEOUT).unwrap();
|
eve.handle
|
||||||
|
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
bob.fork(acme, bob.home.path()).unwrap();
|
bob.fork(acme, bob.home.path()).unwrap();
|
||||||
bob.announce(acme, 1, bob.home.path()).unwrap();
|
bob.announce(acme, 1, bob.home.path()).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use std::io::LineWriter;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{io, net, time};
|
use std::{io, net, time};
|
||||||
|
|
||||||
|
use radicle::storage::refs;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::net::{UnixListener, UnixStream};
|
use std::os::unix::net::{UnixListener, UnixStream};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
|
@ -112,8 +113,20 @@ where
|
||||||
CommandResult::ok().to_writer(writer).ok();
|
CommandResult::ok().to_writer(writer).ok();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Command::Fetch { rid, nid, timeout } => {
|
Command::Fetch {
|
||||||
fetch(rid, nid, timeout, writer, &mut handle)?;
|
rid,
|
||||||
|
nid,
|
||||||
|
timeout,
|
||||||
|
signed_references_minimum_feature_level,
|
||||||
|
} => {
|
||||||
|
fetch(
|
||||||
|
rid,
|
||||||
|
nid,
|
||||||
|
timeout,
|
||||||
|
signed_references_minimum_feature_level,
|
||||||
|
writer,
|
||||||
|
&mut handle,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
Command::Config => {
|
Command::Config => {
|
||||||
let config = handle.config()?;
|
let config = handle.config()?;
|
||||||
|
|
@ -248,10 +261,11 @@ fn fetch<W: Write, H: Handle<Error = runtime::HandleError>>(
|
||||||
id: RepoId,
|
id: RepoId,
|
||||||
node: NodeId,
|
node: NodeId,
|
||||||
timeout: time::Duration,
|
timeout: time::Duration,
|
||||||
|
signed_references_minimum_feature_level: Option<refs::FeatureLevel>,
|
||||||
mut writer: W,
|
mut writer: W,
|
||||||
handle: &mut H,
|
handle: &mut H,
|
||||||
) -> Result<(), CommandError> {
|
) -> Result<(), CommandError> {
|
||||||
match handle.fetch(id, node, timeout) {
|
match handle.fetch(id, node, timeout, signed_references_minimum_feature_level) {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
json::to_writer(&mut writer, &result)?;
|
json::to_writer(&mut writer, &result)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ use radicle::node::events::{Event, Events};
|
||||||
use radicle::node::policy;
|
use radicle::node::policy;
|
||||||
use radicle::node::{Config, NodeId};
|
use radicle::node::{Config, NodeId};
|
||||||
use radicle::node::{ConnectOptions, ConnectResult, Seeds};
|
use radicle::node::{ConnectOptions, ConnectResult, Seeds};
|
||||||
|
use radicle::storage::refs;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -229,9 +230,16 @@ impl radicle::node::Handle for Handle {
|
||||||
id: RepoId,
|
id: RepoId,
|
||||||
from: NodeId,
|
from: NodeId,
|
||||||
timeout: time::Duration,
|
timeout: time::Duration,
|
||||||
|
signed_references_minimum_feature_level: Option<refs::FeatureLevel>,
|
||||||
) -> Result<FetchResult, Error> {
|
) -> Result<FetchResult, Error> {
|
||||||
let (responder, receiver) = service::command::Responder::oneshot();
|
let (responder, receiver) = service::command::Responder::oneshot();
|
||||||
self.command(service::Command::Fetch(id, from, timeout, responder))?;
|
self.command(service::Command::Fetch(
|
||||||
|
id,
|
||||||
|
from,
|
||||||
|
timeout,
|
||||||
|
signed_references_minimum_feature_level,
|
||||||
|
responder,
|
||||||
|
))?;
|
||||||
Ok(receiver.recv()??)
|
Ok(receiver.recv()??)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::time;
|
||||||
|
|
||||||
use radicle::crypto::PublicKey;
|
use radicle::crypto::PublicKey;
|
||||||
use radicle::git::Oid;
|
use radicle::git::Oid;
|
||||||
use radicle::storage::refs::RefsAt;
|
use radicle::storage::refs::{self, RefsAt};
|
||||||
|
|
||||||
use crate::identity::RepoId;
|
use crate::identity::RepoId;
|
||||||
use crate::node::{Alias, Config, ConnectOptions, ConnectResult, Event, FetchResult, Seeds};
|
use crate::node::{Alias, Config, ConnectOptions, ConnectResult, Event, FetchResult, Seeds};
|
||||||
|
|
@ -69,6 +69,7 @@ impl radicle::node::Handle for Handle {
|
||||||
_id: RepoId,
|
_id: RepoId,
|
||||||
_from: NodeId,
|
_from: NodeId,
|
||||||
_timeout: time::Duration,
|
_timeout: time::Duration,
|
||||||
|
_signed_references_minimum_feature_level: Option<refs::FeatureLevel>,
|
||||||
) -> Result<FetchResult, Self::Error> {
|
) -> Result<FetchResult, Self::Error> {
|
||||||
Ok(FetchResult::Success {
|
Ok(FetchResult::Success {
|
||||||
updated: vec![],
|
updated: vec![],
|
||||||
|
|
|
||||||
|
|
@ -1496,15 +1496,15 @@ fn test_queued_fetch_max_capacity() {
|
||||||
alice.connect_to(&bob);
|
alice.connect_to(&bob);
|
||||||
|
|
||||||
// Send the first fetch.
|
// Send the first fetch.
|
||||||
let (cmd, _recv1) = Command::fetch(rid1, bob.id, DEFAULT_TIMEOUT);
|
let (cmd, _recv1) = Command::fetch(rid1, bob.id, DEFAULT_TIMEOUT, None);
|
||||||
alice.command(cmd);
|
alice.command(cmd);
|
||||||
|
|
||||||
// Send the 2nd fetch that will be queued.
|
// Send the 2nd fetch that will be queued.
|
||||||
let (cmd, _recv2) = Command::fetch(rid2, bob.id, DEFAULT_TIMEOUT);
|
let (cmd, _recv2) = Command::fetch(rid2, bob.id, DEFAULT_TIMEOUT, None);
|
||||||
alice.command(cmd);
|
alice.command(cmd);
|
||||||
|
|
||||||
// Send the 3rd fetch that will be queued.
|
// Send the 3rd fetch that will be queued.
|
||||||
let (cmd, _recv3) = Command::fetch(rid3, bob.id, DEFAULT_TIMEOUT);
|
let (cmd, _recv3) = Command::fetch(rid3, bob.id, DEFAULT_TIMEOUT, None);
|
||||||
alice.command(cmd);
|
alice.command(cmd);
|
||||||
|
|
||||||
// The first fetch is initiated.
|
// The first fetch is initiated.
|
||||||
|
|
@ -1618,15 +1618,15 @@ fn test_queued_fetch_from_command_same_rid() {
|
||||||
alice.connect_to(&carol);
|
alice.connect_to(&carol);
|
||||||
|
|
||||||
// Send the first fetch.
|
// Send the first fetch.
|
||||||
let (cmd, _recv1) = Command::fetch(rid1, bob.id, DEFAULT_TIMEOUT);
|
let (cmd, _recv1) = Command::fetch(rid1, bob.id, DEFAULT_TIMEOUT, None);
|
||||||
alice.command(cmd);
|
alice.command(cmd);
|
||||||
|
|
||||||
// Send the 2nd fetch that will be queued.
|
// Send the 2nd fetch that will be queued.
|
||||||
let (cmd, _recv2) = Command::fetch(rid1, eve.id, DEFAULT_TIMEOUT);
|
let (cmd, _recv2) = Command::fetch(rid1, eve.id, DEFAULT_TIMEOUT, None);
|
||||||
alice.command(cmd);
|
alice.command(cmd);
|
||||||
|
|
||||||
// Send the 3rd fetch that will be queued.
|
// Send the 3rd fetch that will be queued.
|
||||||
let (cmd, _recv3) = Command::fetch(rid1, carol.id, DEFAULT_TIMEOUT);
|
let (cmd, _recv3) = Command::fetch(rid1, carol.id, DEFAULT_TIMEOUT, None);
|
||||||
alice.command(cmd);
|
alice.command(cmd);
|
||||||
|
|
||||||
// Peers Alice will fetch from.
|
// Peers Alice will fetch from.
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,10 @@ fn test_replication() {
|
||||||
let seeds = alice.handle.seeds_for(acme, None).unwrap();
|
let seeds = alice.handle.seeds_for(acme, None).unwrap();
|
||||||
assert!(seeds.is_connected(&bob.id));
|
assert!(seeds.is_connected(&bob.id));
|
||||||
|
|
||||||
let result = alice.handle.fetch(acme, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let result = alice
|
||||||
|
.handle
|
||||||
|
.fetch(acme, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
|
|
||||||
let updated = match result {
|
let updated = match result {
|
||||||
|
|
@ -266,7 +269,10 @@ fn test_replication_ref_in_sigrefs() {
|
||||||
converge([&alice, &bob]);
|
converge([&alice, &bob]);
|
||||||
|
|
||||||
alice.handle.seed(acme, Scope::All).unwrap();
|
alice.handle.seed(acme, Scope::All).unwrap();
|
||||||
let result = alice.handle.fetch(acme, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let result = alice
|
||||||
|
.handle
|
||||||
|
.fetch(acme, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_matches!(result, FetchResult::Success { .. });
|
assert_matches!(result, FetchResult::Success { .. });
|
||||||
|
|
||||||
|
|
@ -325,7 +331,10 @@ fn test_replication_invalid() {
|
||||||
|
|
||||||
alice.handle.follow(*carol.public_key(), None).unwrap();
|
alice.handle.follow(*carol.public_key(), None).unwrap();
|
||||||
alice.handle.seed(acme, Scope::Followed).unwrap();
|
alice.handle.seed(acme, Scope::Followed).unwrap();
|
||||||
let result = alice.handle.fetch(acme, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let result = alice
|
||||||
|
.handle
|
||||||
|
.fetch(acme, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Fetch is successful despite not fetching Carol's refs, since she isn't a delegate.
|
// Fetch is successful despite not fetching Carol's refs, since she isn't a delegate.
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
|
|
@ -355,7 +364,10 @@ fn test_migrated_clone() {
|
||||||
let updated = bob.handle.seed(acme, Scope::All).unwrap();
|
let updated = bob.handle.seed(acme, Scope::All).unwrap();
|
||||||
assert!(updated);
|
assert!(updated);
|
||||||
|
|
||||||
let result = bob.handle.fetch(acme, alice.id, DEFAULT_TIMEOUT).unwrap();
|
let result = bob
|
||||||
|
.handle
|
||||||
|
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
|
|
||||||
log::debug!(target: "test", "Fetch complete with {}", alice.id);
|
log::debug!(target: "test", "Fetch complete with {}", alice.id);
|
||||||
|
|
@ -366,7 +378,10 @@ fn test_migrated_clone() {
|
||||||
std::fs::remove_dir_all(path).unwrap();
|
std::fs::remove_dir_all(path).unwrap();
|
||||||
}
|
}
|
||||||
assert!(!alice.storage.contains(&acme).unwrap());
|
assert!(!alice.storage.contains(&acme).unwrap());
|
||||||
let result = alice.handle.fetch(acme, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let result = alice
|
||||||
|
.handle
|
||||||
|
.fetch(acme, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
|
|
||||||
let alice_repo = alice.storage.repository(acme).unwrap();
|
let alice_repo = alice.storage.repository(acme).unwrap();
|
||||||
|
|
@ -405,13 +420,19 @@ fn test_dont_fetch_owned_refs() {
|
||||||
|
|
||||||
assert!(bob.handle.seed(acme, Scope::Followed).unwrap());
|
assert!(bob.handle.seed(acme, Scope::Followed).unwrap());
|
||||||
|
|
||||||
let result = bob.handle.fetch(acme, alice.id, DEFAULT_TIMEOUT).unwrap();
|
let result = bob
|
||||||
|
.handle
|
||||||
|
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
|
|
||||||
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
||||||
|
|
||||||
alice.issue(acme, Title::new("Don't fetch self").unwrap(), "Use ^");
|
alice.issue(acme, Title::new("Don't fetch self").unwrap(), "Use ^");
|
||||||
let result = alice.handle.fetch(acme, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let result = alice
|
||||||
|
.handle
|
||||||
|
.fetch(acme, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success())
|
assert!(result.is_success())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -451,7 +472,10 @@ fn test_fetch_followed_remotes() {
|
||||||
assert!(bob.handle.follow(*nid, None).unwrap());
|
assert!(bob.handle.follow(*nid, None).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = bob.handle.fetch(acme, alice.id, DEFAULT_TIMEOUT).unwrap();
|
let result = bob
|
||||||
|
.handle
|
||||||
|
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
|
|
||||||
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
||||||
|
|
@ -484,7 +508,10 @@ fn test_missing_remote() {
|
||||||
|
|
||||||
assert!(bob.handle.seed(acme, Scope::Followed).unwrap());
|
assert!(bob.handle.seed(acme, Scope::Followed).unwrap());
|
||||||
assert!(bob.handle.follow(*carol.public_key(), None).unwrap());
|
assert!(bob.handle.follow(*carol.public_key(), None).unwrap());
|
||||||
let result = bob.handle.fetch(acme, alice.id, DEFAULT_TIMEOUT).unwrap();
|
let result = bob
|
||||||
|
.handle
|
||||||
|
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
||||||
rad::fork_remote(acme, &alice.id, &carol, &bob.storage).unwrap();
|
rad::fork_remote(acme, &alice.id, &carol, &bob.storage).unwrap();
|
||||||
|
|
@ -494,7 +521,10 @@ fn test_missing_remote() {
|
||||||
Title::new("Missing Remote").unwrap(),
|
Title::new("Missing Remote").unwrap(),
|
||||||
"Fixing the missing remote issue",
|
"Fixing the missing remote issue",
|
||||||
);
|
);
|
||||||
let result = bob.handle.fetch(acme, alice.id, DEFAULT_TIMEOUT).unwrap();
|
let result = bob
|
||||||
|
.handle
|
||||||
|
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
||||||
}
|
}
|
||||||
|
|
@ -514,7 +544,10 @@ fn test_fetch_preserve_owned_refs() {
|
||||||
assert!(bob.handle.seed(acme, Scope::Followed).unwrap());
|
assert!(bob.handle.seed(acme, Scope::Followed).unwrap());
|
||||||
assert!(bob.handle.follow(alice.id, None).unwrap());
|
assert!(bob.handle.follow(alice.id, None).unwrap());
|
||||||
|
|
||||||
let result = bob.handle.fetch(acme, alice.id, DEFAULT_TIMEOUT).unwrap();
|
let result = bob
|
||||||
|
.handle
|
||||||
|
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
|
|
||||||
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
||||||
|
|
@ -529,7 +562,10 @@ fn test_fetch_preserve_owned_refs() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Fetch shouldn't prune any of our own refs.
|
// Fetch shouldn't prune any of our own refs.
|
||||||
let result = alice.handle.fetch(acme, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let result = alice
|
||||||
|
.handle
|
||||||
|
.fetch(acme, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
let (updated, _) = result.success().unwrap();
|
let (updated, _) = result.success().unwrap();
|
||||||
assert_eq!(updated, vec![]);
|
assert_eq!(updated, vec![]);
|
||||||
|
|
||||||
|
|
@ -562,7 +598,10 @@ fn test_clone() {
|
||||||
let seeds = alice.handle.seeds_for(acme, None).unwrap();
|
let seeds = alice.handle.seeds_for(acme, None).unwrap();
|
||||||
assert!(seeds.is_connected(&bob.id));
|
assert!(seeds.is_connected(&bob.id));
|
||||||
|
|
||||||
let result = alice.handle.fetch(acme, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let result = alice
|
||||||
|
.handle
|
||||||
|
.fetch(acme, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
|
|
||||||
rad::fork(acme, &alice.signer, &alice.storage).unwrap();
|
rad::fork(acme, &alice.signer, &alice.storage).unwrap();
|
||||||
|
|
@ -616,11 +655,17 @@ fn test_fetch_up_to_date() {
|
||||||
transport::local::register(alice.storage.clone());
|
transport::local::register(alice.storage.clone());
|
||||||
|
|
||||||
let _ = alice.handle.seed(acme, Scope::All).unwrap();
|
let _ = alice.handle.seed(acme, Scope::All).unwrap();
|
||||||
let result = alice.handle.fetch(acme, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let result = alice
|
||||||
|
.handle
|
||||||
|
.fetch(acme, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
|
|
||||||
// Fetch again! This time, everything's up to date.
|
// Fetch again! This time, everything's up to date.
|
||||||
let result = alice.handle.fetch(acme, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let result = alice
|
||||||
|
.handle
|
||||||
|
.fetch(acme, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
result.success(),
|
result.success(),
|
||||||
Some((updates, _fetched)) if updates.iter().all(|update| matches!(update, RefUpdate::Skipped { .. }))
|
Some((updates, _fetched)) if updates.iter().all(|update| matches!(update, RefUpdate::Skipped { .. }))
|
||||||
|
|
@ -643,14 +688,20 @@ fn test_fetch_unseeded() {
|
||||||
transport::local::register(alice.storage.clone());
|
transport::local::register(alice.storage.clone());
|
||||||
|
|
||||||
let _ = alice.handle.seed(acme, Scope::All).unwrap();
|
let _ = alice.handle.seed(acme, Scope::All).unwrap();
|
||||||
let result = alice.handle.fetch(acme, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let result = alice
|
||||||
|
.handle
|
||||||
|
.fetch(acme, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
|
|
||||||
// Bob stops seeding the repository
|
// Bob stops seeding the repository
|
||||||
assert!(bob.handle.unseed(acme).unwrap());
|
assert!(bob.handle.unseed(acme).unwrap());
|
||||||
|
|
||||||
// Alice attempts to fetch but is unauthorized
|
// Alice attempts to fetch but is unauthorized
|
||||||
let result = alice.handle.fetch(acme, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let result = alice
|
||||||
|
.handle
|
||||||
|
.fetch(acme, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert_matches!(result, FetchResult::Failed { .. });
|
assert_matches!(result, FetchResult::Failed { .. });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -896,9 +947,14 @@ fn test_non_fastforward_sigrefs() {
|
||||||
converge([&alice, &bob, &eve]);
|
converge([&alice, &bob, &eve]);
|
||||||
|
|
||||||
// Eve fetches the initial project from Bob.
|
// Eve fetches the initial project from Bob.
|
||||||
eve.handle.fetch(rid, bob.id, DEFAULT_TIMEOUT).unwrap();
|
eve.handle
|
||||||
|
.fetch(rid, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
// Alice fetches it too.
|
// Alice fetches it too.
|
||||||
let old_bob = alice.handle.fetch(rid, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let old_bob = alice
|
||||||
|
.handle
|
||||||
|
.fetch(rid, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
let bob_sigrefs = bob
|
let bob_sigrefs = bob
|
||||||
.storage
|
.storage
|
||||||
.repository(rid)
|
.repository(rid)
|
||||||
|
|
@ -940,7 +996,10 @@ fn test_non_fastforward_sigrefs() {
|
||||||
"Updated sigrefs are harshing my vibes",
|
"Updated sigrefs are harshing my vibes",
|
||||||
);
|
);
|
||||||
// Alice fetches from Bob.
|
// Alice fetches from Bob.
|
||||||
let new_bob = alice.handle.fetch(rid, bob.id, DEFAULT_TIMEOUT).unwrap();
|
let new_bob = alice
|
||||||
|
.handle
|
||||||
|
.fetch(rid, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
let bob_sigrefs = bob
|
let bob_sigrefs = bob
|
||||||
.storage
|
.storage
|
||||||
.repository(rid)
|
.repository(rid)
|
||||||
|
|
@ -972,7 +1031,7 @@ fn test_non_fastforward_sigrefs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
alice.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap(),
|
alice.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT, None).unwrap(),
|
||||||
FetchResult::Success { updated, .. }
|
FetchResult::Success { updated, .. }
|
||||||
if updated.iter().all(|u| u.is_skipped())
|
if updated.iter().all(|u| u.is_skipped())
|
||||||
);
|
);
|
||||||
|
|
@ -999,11 +1058,15 @@ fn test_outdated_sigrefs() {
|
||||||
eve.connect(&alice);
|
eve.connect(&alice);
|
||||||
converge([&alice, &bob, &eve]);
|
converge([&alice, &bob, &eve]);
|
||||||
|
|
||||||
bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
bob.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(bob.storage.contains(&rid).unwrap());
|
assert!(bob.storage.contains(&rid).unwrap());
|
||||||
rad::fork(rid, &bob.signer, &bob.storage).unwrap();
|
rad::fork(rid, &bob.signer, &bob.storage).unwrap();
|
||||||
|
|
||||||
eve.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
eve.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(eve.storage.contains(&rid).unwrap());
|
assert!(eve.storage.contains(&rid).unwrap());
|
||||||
rad::fork(rid, &eve.signer, &eve.storage).unwrap();
|
rad::fork(rid, &eve.signer, &eve.storage).unwrap();
|
||||||
|
|
||||||
|
|
@ -1011,13 +1074,18 @@ fn test_outdated_sigrefs() {
|
||||||
.handle
|
.handle
|
||||||
.follow(eve.id, Some(Alias::new("eve")))
|
.follow(eve.id, Some(Alias::new("eve")))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
alice.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap();
|
alice
|
||||||
|
.handle
|
||||||
|
.fetch(rid, eve.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
let repo = alice.storage.repository(rid).unwrap();
|
let repo = alice.storage.repository(rid).unwrap();
|
||||||
assert!(repo.remote(&eve.id).is_ok());
|
assert!(repo.remote(&eve.id).is_ok());
|
||||||
|
|
||||||
log::debug!(target: "test", "Bob fetches from Eve..");
|
log::debug!(target: "test", "Bob fetches from Eve..");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
bob.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap(),
|
bob.handle
|
||||||
|
.fetch(rid, eve.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap(),
|
||||||
FetchResult::Success { .. }
|
FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
let repo = bob.storage.repository(rid).unwrap();
|
let repo = bob.storage.repository(rid).unwrap();
|
||||||
|
|
@ -1038,7 +1106,10 @@ fn test_outdated_sigrefs() {
|
||||||
// Get the current state of eve's refs in alice's storage
|
// Get the current state of eve's refs in alice's storage
|
||||||
log::debug!(target: "test", "Alice fetches from Eve..");
|
log::debug!(target: "test", "Alice fetches from Eve..");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
alice.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap(),
|
alice
|
||||||
|
.handle
|
||||||
|
.fetch(rid, eve.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap(),
|
||||||
FetchResult::Success { .. }
|
FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
let repo = alice.storage.repository(rid).unwrap();
|
let repo = alice.storage.repository(rid).unwrap();
|
||||||
|
|
@ -1059,7 +1130,10 @@ fn test_outdated_sigrefs() {
|
||||||
.follow(bob.id, Some(Alias::new("bob")))
|
.follow(bob.id, Some(Alias::new("bob")))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
alice.handle.fetch(rid, bob.id, DEFAULT_TIMEOUT).unwrap(),
|
alice
|
||||||
|
.handle
|
||||||
|
.fetch(rid, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap(),
|
||||||
FetchResult::Success { .. }
|
FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1093,11 +1167,15 @@ fn test_outdated_delegate_sigrefs() {
|
||||||
eve.connect(&alice);
|
eve.connect(&alice);
|
||||||
converge([&alice, &bob, &eve]);
|
converge([&alice, &bob, &eve]);
|
||||||
|
|
||||||
bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
bob.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(bob.storage.contains(&rid).unwrap());
|
assert!(bob.storage.contains(&rid).unwrap());
|
||||||
rad::fork(rid, &bob.signer, &bob.storage).unwrap();
|
rad::fork(rid, &bob.signer, &bob.storage).unwrap();
|
||||||
|
|
||||||
eve.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
eve.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(eve.storage.contains(&rid).unwrap());
|
assert!(eve.storage.contains(&rid).unwrap());
|
||||||
rad::fork(rid, &eve.signer, &eve.storage).unwrap();
|
rad::fork(rid, &eve.signer, &eve.storage).unwrap();
|
||||||
|
|
||||||
|
|
@ -1105,13 +1183,18 @@ fn test_outdated_delegate_sigrefs() {
|
||||||
.handle
|
.handle
|
||||||
.follow(eve.id, Some(Alias::new("eve")))
|
.follow(eve.id, Some(Alias::new("eve")))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
alice.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap();
|
alice
|
||||||
|
.handle
|
||||||
|
.fetch(rid, eve.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
let repo = alice.storage.repository(rid).unwrap();
|
let repo = alice.storage.repository(rid).unwrap();
|
||||||
assert!(repo.remote(&eve.id).is_ok());
|
assert!(repo.remote(&eve.id).is_ok());
|
||||||
|
|
||||||
log::debug!(target: "test", "Bob fetches from Eve..");
|
log::debug!(target: "test", "Bob fetches from Eve..");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
bob.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap(),
|
bob.handle
|
||||||
|
.fetch(rid, eve.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap(),
|
||||||
FetchResult::Success { .. }
|
FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
let repo = bob.storage.repository(rid).unwrap();
|
let repo = bob.storage.repository(rid).unwrap();
|
||||||
|
|
@ -1132,7 +1215,9 @@ fn test_outdated_delegate_sigrefs() {
|
||||||
// Get the current state of eve's refs in alice's storage
|
// Get the current state of eve's refs in alice's storage
|
||||||
log::debug!(target: "test", "Alice fetches from Eve..");
|
log::debug!(target: "test", "Alice fetches from Eve..");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
eve.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap(),
|
eve.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap(),
|
||||||
FetchResult::Success { .. }
|
FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
let repo = eve.storage.repository(rid).unwrap();
|
let repo = eve.storage.repository(rid).unwrap();
|
||||||
|
|
@ -1145,7 +1230,9 @@ fn test_outdated_delegate_sigrefs() {
|
||||||
|
|
||||||
eve.handle.follow(bob.id, Some(Alias::new("bob"))).unwrap();
|
eve.handle.follow(bob.id, Some(Alias::new("bob"))).unwrap();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
eve.handle.fetch(rid, bob.id, DEFAULT_TIMEOUT).unwrap(),
|
eve.handle
|
||||||
|
.fetch(rid, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap(),
|
||||||
FetchResult::Success { .. }
|
FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1175,7 +1262,9 @@ fn missing_default_branch() {
|
||||||
alice.connect(&bob);
|
alice.connect(&bob);
|
||||||
converge([&alice, &bob]);
|
converge([&alice, &bob]);
|
||||||
|
|
||||||
bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
bob.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(bob.storage.contains(&rid).unwrap());
|
assert!(bob.storage.contains(&rid).unwrap());
|
||||||
|
|
||||||
// Fetching from still works despite not having
|
// Fetching from still works despite not having
|
||||||
|
|
@ -1185,7 +1274,10 @@ fn missing_default_branch() {
|
||||||
Title::new("Hello, Acme").unwrap(),
|
Title::new("Hello, Acme").unwrap(),
|
||||||
"Popping in to say hello",
|
"Popping in to say hello",
|
||||||
);
|
);
|
||||||
alice.handle.fetch(rid, bob.id, DEFAULT_TIMEOUT).unwrap();
|
alice
|
||||||
|
.handle
|
||||||
|
.fetch(rid, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
{
|
{
|
||||||
let repo = bob.storage.repository(rid).unwrap();
|
let repo = bob.storage.repository(rid).unwrap();
|
||||||
|
|
@ -1207,7 +1299,9 @@ fn missing_default_branch() {
|
||||||
|
|
||||||
// Fetching from her will still succeed.
|
// Fetching from her will still succeed.
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap(),
|
bob.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap(),
|
||||||
FetchResult::Success { .. }
|
FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
let repo = bob.storage.repository(rid).unwrap();
|
let repo = bob.storage.repository(rid).unwrap();
|
||||||
|
|
@ -1240,7 +1334,9 @@ fn missing_delegate_default_branch() {
|
||||||
converge([&seed]);
|
converge([&seed]);
|
||||||
bob.connect(&seed);
|
bob.connect(&seed);
|
||||||
|
|
||||||
bob.handle.fetch(rid, seed.id, DEFAULT_TIMEOUT).unwrap();
|
bob.handle
|
||||||
|
.fetch(rid, seed.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
bob_events
|
bob_events
|
||||||
.wait(
|
.wait(
|
||||||
|e| {
|
|e| {
|
||||||
|
|
@ -1312,7 +1408,9 @@ fn missing_delegate_default_branch() {
|
||||||
// a) Bob's default branch is still missing
|
// a) Bob's default branch is still missing
|
||||||
// b) Bob's issue is there
|
// b) Bob's issue is there
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
seed.handle.fetch(rid, bob.id, DEFAULT_TIMEOUT).unwrap(),
|
seed.handle
|
||||||
|
.fetch(rid, bob.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap(),
|
||||||
FetchResult::Success { .. }
|
FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
{
|
{
|
||||||
|
|
@ -1323,7 +1421,10 @@ fn missing_delegate_default_branch() {
|
||||||
|
|
||||||
// Do the same for Alice
|
// Do the same for Alice
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
alice.handle.fetch(rid, seed.id, DEFAULT_TIMEOUT).unwrap(),
|
alice
|
||||||
|
.handle
|
||||||
|
.fetch(rid, seed.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap(),
|
||||||
FetchResult::Success { .. }
|
FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
{
|
{
|
||||||
|
|
@ -1334,7 +1435,9 @@ fn missing_delegate_default_branch() {
|
||||||
|
|
||||||
// Check that Bob can still fetch from the seed
|
// Check that Bob can still fetch from the seed
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
bob.handle.fetch(rid, seed.id, DEFAULT_TIMEOUT).unwrap(),
|
bob.handle
|
||||||
|
.fetch(rid, seed.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap(),
|
||||||
FetchResult::Success { .. }
|
FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1360,11 +1463,15 @@ fn test_background_foreground_fetch() {
|
||||||
alice.connect(&eve);
|
alice.connect(&eve);
|
||||||
converge([&alice, &bob, &eve]);
|
converge([&alice, &bob, &eve]);
|
||||||
|
|
||||||
bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
bob.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(bob.storage.contains(&rid).unwrap());
|
assert!(bob.storage.contains(&rid).unwrap());
|
||||||
rad::fork(rid, &bob.signer, &bob.storage).unwrap();
|
rad::fork(rid, &bob.signer, &bob.storage).unwrap();
|
||||||
|
|
||||||
eve.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
eve.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(eve.storage.contains(&rid).unwrap());
|
assert!(eve.storage.contains(&rid).unwrap());
|
||||||
rad::fork(rid, &eve.signer, &eve.storage).unwrap();
|
rad::fork(rid, &eve.signer, &eve.storage).unwrap();
|
||||||
|
|
||||||
|
|
@ -1373,7 +1480,10 @@ fn test_background_foreground_fetch() {
|
||||||
.handle
|
.handle
|
||||||
.follow(eve.id, Some(Alias::new("eve")))
|
.follow(eve.id, Some(Alias::new("eve")))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
alice.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap();
|
alice
|
||||||
|
.handle
|
||||||
|
.fetch(rid, eve.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
let repo = alice.storage.repository(rid).unwrap();
|
let repo = alice.storage.repository(rid).unwrap();
|
||||||
assert!(repo.remote(&eve.id).is_ok());
|
assert!(repo.remote(&eve.id).is_ok());
|
||||||
let repo = alice.storage.repository(rid).unwrap();
|
let repo = alice.storage.repository(rid).unwrap();
|
||||||
|
|
@ -1414,7 +1524,10 @@ fn test_background_foreground_fetch() {
|
||||||
// interfere
|
// interfere
|
||||||
log::debug!(target: "test", "Alice fetches from Eve..");
|
log::debug!(target: "test", "Alice fetches from Eve..");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
alice.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap(),
|
alice
|
||||||
|
.handle
|
||||||
|
.fetch(rid, eve.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap(),
|
||||||
FetchResult::Success { .. }
|
FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
let repo = alice.storage.repository(rid).unwrap();
|
let repo = alice.storage.repository(rid).unwrap();
|
||||||
|
|
@ -1514,7 +1627,10 @@ fn test_channel_reader_limit() {
|
||||||
let updated = bob.handle.seed(acme, Scope::All).unwrap();
|
let updated = bob.handle.seed(acme, Scope::All).unwrap();
|
||||||
assert!(updated);
|
assert!(updated);
|
||||||
|
|
||||||
let result = bob.handle.fetch(acme, alice.id, DEFAULT_TIMEOUT).unwrap();
|
let result = bob
|
||||||
|
.handle
|
||||||
|
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(!result.is_success());
|
assert!(!result.is_success());
|
||||||
|
|
||||||
let FetchResult::Failed { reason } = result else {
|
let FetchResult::Failed { reason } = result else {
|
||||||
|
|
@ -1548,7 +1664,10 @@ fn test_fetch_emits_canonical_ref_update() {
|
||||||
bob.handle.seed(rid, Scope::All).unwrap();
|
bob.handle.seed(rid, Scope::All).unwrap();
|
||||||
alice.connect(&bob);
|
alice.connect(&bob);
|
||||||
|
|
||||||
let result = bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
let result = bob
|
||||||
|
.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(result.is_success());
|
assert!(result.is_success());
|
||||||
|
|
||||||
let default_branch: git::fmt::Qualified = {
|
let default_branch: git::fmt::Qualified = {
|
||||||
|
|
@ -1606,12 +1725,14 @@ fn test_non_fastforward_identity_doc() {
|
||||||
|
|
||||||
// Bob and Eve have the same state for the repository
|
// Bob and Eve have the same state for the repository
|
||||||
bob.handle.seed(rid, Scope::Followed).unwrap();
|
bob.handle.seed(rid, Scope::Followed).unwrap();
|
||||||
bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
bob.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
alice_laptop.handle.seed(rid, Scope::All).unwrap();
|
alice_laptop.handle.seed(rid, Scope::All).unwrap();
|
||||||
alice_laptop
|
alice_laptop
|
||||||
.handle
|
.handle
|
||||||
.fetch(rid, alice.id, DEFAULT_TIMEOUT)
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Alice pushes new references to her laptop
|
// Alice pushes new references to her laptop
|
||||||
let issue = alice_laptop.issue(
|
let issue = alice_laptop.issue(
|
||||||
|
|
@ -1623,7 +1744,7 @@ fn test_non_fastforward_identity_doc() {
|
||||||
// Eve will fetch these references since her scope is "all"
|
// Eve will fetch these references since her scope is "all"
|
||||||
eve.handle.seed(rid, Scope::All).unwrap();
|
eve.handle.seed(rid, Scope::All).unwrap();
|
||||||
eve.handle
|
eve.handle
|
||||||
.fetch(rid, alice_laptop.id, DEFAULT_TIMEOUT)
|
.fetch(rid, alice_laptop.id, DEFAULT_TIMEOUT, None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(has_issue(&eve, &issue));
|
assert!(has_issue(&eve, &issue));
|
||||||
|
|
||||||
|
|
@ -1651,7 +1772,10 @@ fn test_non_fastforward_identity_doc() {
|
||||||
//
|
//
|
||||||
// Bob does not have the issue because Alice does not have the updates from
|
// Bob does not have the issue because Alice does not have the updates from
|
||||||
// Alice's Laptop.
|
// Alice's Laptop.
|
||||||
let result = bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
let result = bob
|
||||||
|
.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(matches!(result, FetchResult::Success { .. }));
|
assert!(matches!(result, FetchResult::Success { .. }));
|
||||||
assert!(!has_issue(&bob, &issue));
|
assert!(!has_issue(&bob, &issue));
|
||||||
let repo = bob.storage.repository(rid).unwrap();
|
let repo = bob.storage.repository(rid).unwrap();
|
||||||
|
|
@ -1662,7 +1786,9 @@ fn test_non_fastforward_identity_doc() {
|
||||||
// Bob fetches from Eve, the identity document should remain the same, but
|
// Bob fetches from Eve, the identity document should remain the same, but
|
||||||
// since Bob now knows that Alice's Laptop is a delegate, the issue should
|
// since Bob now knows that Alice's Laptop is a delegate, the issue should
|
||||||
// be fetched.
|
// be fetched.
|
||||||
bob.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap();
|
bob.handle
|
||||||
|
.fetch(rid, eve.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(matches!(result, FetchResult::Success { .. }));
|
assert!(matches!(result, FetchResult::Success { .. }));
|
||||||
assert!(has_issue(&bob, &issue));
|
assert!(has_issue(&bob, &issue));
|
||||||
let repo = bob.storage.repository(rid).unwrap();
|
let repo = bob.storage.repository(rid).unwrap();
|
||||||
|
|
@ -1744,7 +1870,7 @@ fn test_block_prevents_fetch() {
|
||||||
|
|
||||||
let result = alice
|
let result = alice
|
||||||
.handle
|
.handle
|
||||||
.fetch(rid, bob.id, time::Duration::from_secs(5))
|
.fetch(rid, bob.id, time::Duration::from_secs(5), None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_matches!(result, FetchResult::Failed { .. });
|
assert_matches!(result, FetchResult::Failed { .. });
|
||||||
|
|
@ -1768,7 +1894,9 @@ fn fetch_does_not_contain_rad_sigrefs_parent() {
|
||||||
alice.connect(&bob);
|
alice.connect(&bob);
|
||||||
converge([&alice, &bob]);
|
converge([&alice, &bob]);
|
||||||
|
|
||||||
bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
|
bob.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap();
|
||||||
assert!(bob.storage.contains(&rid).unwrap());
|
assert!(bob.storage.contains(&rid).unwrap());
|
||||||
rad::fork(rid, &bob.signer, &bob.storage).unwrap();
|
rad::fork(rid, &bob.signer, &bob.storage).unwrap();
|
||||||
|
|
||||||
|
|
@ -1782,7 +1910,9 @@ fn fetch_does_not_contain_rad_sigrefs_parent() {
|
||||||
|
|
||||||
log::debug!(target: "test", "Bob fetches from Alice..");
|
log::debug!(target: "test", "Bob fetches from Alice..");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap(),
|
bob.handle
|
||||||
|
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||||
|
.unwrap(),
|
||||||
FetchResult::Success { .. }
|
FetchResult::Success { .. }
|
||||||
);
|
);
|
||||||
let repo = bob.storage.repository(rid).unwrap();
|
let repo = bob.storage.repository(rid).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -863,9 +863,13 @@ where
|
||||||
resp.err(e).ok();
|
resp.err(e).ok();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Command::Fetch(rid, seed, timeout, resp) => {
|
Command::Fetch(rid, seed, timeout, signed_references_minimum_feature_level, resp) => {
|
||||||
// TODO(finto): pass through feature-level
|
let feature_level = signed_references_minimum_feature_level
|
||||||
let config = self.fetch_config().with_timeout(timeout);
|
.unwrap_or(self.config.fetch.feature_level_min());
|
||||||
|
let config = self
|
||||||
|
.fetch_config()
|
||||||
|
.with_timeout(timeout)
|
||||||
|
.with_minimum_feature_level(feature_level);
|
||||||
self.fetch(rid, seed, vec![], config, Some(resp));
|
self.fetch(rid, seed, vec![], config, Some(resp));
|
||||||
}
|
}
|
||||||
Command::Seed(rid, scope, resp) => {
|
Command::Seed(rid, scope, resp) => {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use radicle::node::policy::Scope;
|
||||||
use radicle::node::FetchResult;
|
use radicle::node::FetchResult;
|
||||||
use radicle::node::Seeds;
|
use radicle::node::Seeds;
|
||||||
use radicle::node::{Address, Alias, Config, ConnectOptions};
|
use radicle::node::{Address, Alias, Config, ConnectOptions};
|
||||||
|
use radicle::storage::refs;
|
||||||
use radicle::storage::refs::RefsAt;
|
use radicle::storage::refs::RefsAt;
|
||||||
use radicle_core::{NodeId, RepoId};
|
use radicle_core::{NodeId, RepoId};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -90,7 +91,13 @@ pub enum Command {
|
||||||
/// sync status for given namespaces.
|
/// sync status for given namespaces.
|
||||||
Seeds(RepoId, HashSet<PublicKey>, Responder<Seeds>),
|
Seeds(RepoId, HashSet<PublicKey>, Responder<Seeds>),
|
||||||
/// Fetch the given repository from the network.
|
/// Fetch the given repository from the network.
|
||||||
Fetch(RepoId, NodeId, time::Duration, Responder<FetchResult>),
|
Fetch(
|
||||||
|
RepoId,
|
||||||
|
NodeId,
|
||||||
|
time::Duration,
|
||||||
|
Option<refs::FeatureLevel>,
|
||||||
|
Responder<FetchResult>,
|
||||||
|
),
|
||||||
/// Seed the given repository.
|
/// Seed the given repository.
|
||||||
Seed(RepoId, Scope, Responder<bool>),
|
Seed(RepoId, Scope, Responder<bool>),
|
||||||
/// Unseed the given repository.
|
/// Unseed the given repository.
|
||||||
|
|
@ -150,9 +157,19 @@ impl Command {
|
||||||
rid: RepoId,
|
rid: RepoId,
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
duration: time::Duration,
|
duration: time::Duration,
|
||||||
|
signed_references_minimum_feature_level: Option<refs::FeatureLevel>,
|
||||||
) -> (Self, Receiver<Result<FetchResult>>) {
|
) -> (Self, Receiver<Result<FetchResult>>) {
|
||||||
let (responder, receiver) = Responder::oneshot();
|
let (responder, receiver) = Responder::oneshot();
|
||||||
(Self::Fetch(rid, node_id, duration, responder), receiver)
|
(
|
||||||
|
Self::Fetch(
|
||||||
|
rid,
|
||||||
|
node_id,
|
||||||
|
duration,
|
||||||
|
signed_references_minimum_feature_level,
|
||||||
|
responder,
|
||||||
|
),
|
||||||
|
receiver,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn seed(rid: RepoId, scope: Scope) -> (Self, Receiver<Result<bool>>) {
|
pub fn seed(rid: RepoId, scope: Scope) -> (Self, Receiver<Result<bool>>) {
|
||||||
|
|
@ -191,7 +208,10 @@ impl fmt::Debug for Command {
|
||||||
Self::Config(_) => write!(f, "Config"),
|
Self::Config(_) => write!(f, "Config"),
|
||||||
Self::ListenAddrs(_) => write!(f, "ListenAddrs"),
|
Self::ListenAddrs(_) => write!(f, "ListenAddrs"),
|
||||||
Self::Seeds(id, _, _) => write!(f, "Seeds({id})"),
|
Self::Seeds(id, _, _) => write!(f, "Seeds({id})"),
|
||||||
Self::Fetch(id, node, _, _) => write!(f, "Fetch({id}, {node})"),
|
Self::Fetch(id, node, _, feature_level, _) => match feature_level {
|
||||||
|
Some(feature_level) => write!(f, "Fetch({id}, {node} {feature_level})"),
|
||||||
|
None => write!(f, "Fetch({id}, {node})"),
|
||||||
|
},
|
||||||
Self::Seed(id, scope, _) => write!(f, "Seed({id}, {scope})"),
|
Self::Seed(id, scope, _) => write!(f, "Seed({id}, {scope})"),
|
||||||
Self::Unseed(id, _) => write!(f, "Unseed({id})"),
|
Self::Unseed(id, _) => write!(f, "Unseed({id})"),
|
||||||
Self::Follow(id, _, _) => write!(f, "Follow({id})"),
|
Self::Follow(id, _, _) => write!(f, "Follow({id})"),
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ use crate::crypto::PublicKey;
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::identity::RepoId;
|
use crate::identity::RepoId;
|
||||||
use crate::profile;
|
use crate::profile;
|
||||||
use crate::storage::refs::RefsAt;
|
use crate::storage::refs::{FeatureLevel, RefsAt};
|
||||||
use crate::storage::RefUpdate;
|
use crate::storage::RefUpdate;
|
||||||
|
|
||||||
pub use address::KnownAddress;
|
pub use address::KnownAddress;
|
||||||
|
|
@ -975,6 +975,7 @@ pub trait Handle: Clone + Sync + Send {
|
||||||
id: RepoId,
|
id: RepoId,
|
||||||
from: NodeId,
|
from: NodeId,
|
||||||
timeout: time::Duration,
|
timeout: time::Duration,
|
||||||
|
signed_references_minimum_feature_level: Option<FeatureLevel>,
|
||||||
) -> Result<FetchResult, Self::Error>;
|
) -> Result<FetchResult, Self::Error>;
|
||||||
/// Start seeding the given repo. May update the scope. Does nothing if the
|
/// Start seeding the given repo. May update the scope. Does nothing if the
|
||||||
/// repo is already seeded.
|
/// repo is already seeded.
|
||||||
|
|
@ -1244,6 +1245,7 @@ impl Handle for Node {
|
||||||
rid: RepoId,
|
rid: RepoId,
|
||||||
from: NodeId,
|
from: NodeId,
|
||||||
timeout: time::Duration,
|
timeout: time::Duration,
|
||||||
|
signed_references_minimum_feature_level: Option<FeatureLevel>,
|
||||||
) -> Result<FetchResult, Error> {
|
) -> Result<FetchResult, Error> {
|
||||||
let result = self
|
let result = self
|
||||||
.call(
|
.call(
|
||||||
|
|
@ -1251,6 +1253,7 @@ impl Handle for Node {
|
||||||
rid,
|
rid,
|
||||||
nid: from,
|
nid: from,
|
||||||
timeout,
|
timeout,
|
||||||
|
signed_references_minimum_feature_level,
|
||||||
},
|
},
|
||||||
DEFAULT_TIMEOUT.max(timeout),
|
DEFAULT_TIMEOUT.max(timeout),
|
||||||
)?
|
)?
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ use serde_json as json;
|
||||||
|
|
||||||
use crate::crypto::PublicKey;
|
use crate::crypto::PublicKey;
|
||||||
use crate::identity::RepoId;
|
use crate::identity::RepoId;
|
||||||
|
use crate::storage::refs;
|
||||||
|
|
||||||
use super::events::Event;
|
use super::events::Event;
|
||||||
use super::NodeId;
|
use super::NodeId;
|
||||||
|
|
@ -96,6 +97,7 @@ pub enum Command {
|
||||||
rid: RepoId,
|
rid: RepoId,
|
||||||
nid: NodeId,
|
nid: NodeId,
|
||||||
timeout: time::Duration,
|
timeout: time::Duration,
|
||||||
|
signed_references_minimum_feature_level: Option<refs::FeatureLevel>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Seed the given repository.
|
/// Seed the given repository.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue