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
|
||||
|
||||
- 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,
|
||||
non-replayed commit. This only occurs if duplicate signatures are found and
|
||||
the process needs to find the first legitimate commit of the namespace.
|
||||
|
|
|
|||
|
|
@ -2,12 +2,17 @@ use std::path::PathBuf;
|
|||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::node::SyncSettings;
|
||||
use radicle::identity::doc::RepoId;
|
||||
use radicle::identity::IdError;
|
||||
use radicle::node::policy::Scope;
|
||||
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;
|
||||
|
||||
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"
|
||||
#[arg(long, value_parser = humantime::parse_duration, default_value = "9s")]
|
||||
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 {
|
||||
|
|
@ -42,6 +54,9 @@ impl From<SyncArgs> for SyncSettings {
|
|||
SyncSettings {
|
||||
timeout: args.timeout,
|
||||
seeds: args.seeds.into_iter().collect(),
|
||||
signed_references_minimum_feature_level: args
|
||||
.signed_refs_feature_level
|
||||
.map(refs::FeatureLevel::from),
|
||||
..SyncSettings::default()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -324,7 +324,12 @@ pub fn fetch(
|
|||
}
|
||||
if let Some((nid, addr)) = fetcher.next_fetch() {
|
||||
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) {
|
||||
std::ops::ControlFlow::Continue(update) => {
|
||||
spinner.emit_progress(&update);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,13 @@ use clap::{Parser, Subcommand, ValueEnum};
|
|||
use radicle::{
|
||||
node::{sync, NodeId},
|
||||
prelude::RepoId,
|
||||
storage::refs,
|
||||
};
|
||||
|
||||
use crate::common_args::{
|
||||
SignedReferencesFeatureLevel, SignedReferencesFeatureLevelParser,
|
||||
ABOUT_FETCH_SIGNED_REFERENCES_FEATURE_LEVEL_MINIMUM,
|
||||
};
|
||||
use crate::node::SyncSettings;
|
||||
|
||||
const ABOUT: &str = "Sync repositories to the network";
|
||||
|
|
@ -136,6 +141,14 @@ pub(super) struct SyncArgs {
|
|||
/// <RID> is ignored with `--inventory`
|
||||
#[arg(long, short)]
|
||||
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 {
|
||||
|
|
@ -220,9 +233,13 @@ impl From<SyncArgs> for SyncMode {
|
|||
} else {
|
||||
assert!(!args.inventory);
|
||||
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()
|
||||
.timeout(args.timeout())
|
||||
.replicas(args.replication());
|
||||
.timeout(timeout)
|
||||
.replicas(replicas)
|
||||
.minimum_feature_level(feature_level);
|
||||
if !args.seeds.is_empty() {
|
||||
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 terminal;
|
||||
|
||||
mod common_args;
|
||||
mod warning;
|
||||
|
||||
extern crate radicle_localtime as localtime;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::io::Write;
|
|||
|
||||
use radicle::node::sync;
|
||||
use radicle::node::{Handle as _, NodeId};
|
||||
use radicle::storage::{ReadRepository, RepositoryError};
|
||||
use radicle::storage::{refs, ReadRepository, RepositoryError};
|
||||
use radicle::{Node, Profile};
|
||||
|
||||
use crate::terminal as term;
|
||||
|
|
@ -21,6 +21,8 @@ pub struct SyncSettings {
|
|||
pub seeds: BTreeSet<NodeId>,
|
||||
/// How long to wait for syncing to complete.
|
||||
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 {
|
||||
|
|
@ -31,6 +33,13 @@ impl SyncSettings {
|
|||
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.
|
||||
#[must_use]
|
||||
pub fn replicas(mut self, replicas: sync::ReplicationFactor) -> Self {
|
||||
|
|
@ -69,6 +78,7 @@ impl Default for SyncSettings {
|
|||
replicas: sync::ReplicationFactor::default(),
|
||||
seeds: BTreeSet::new(),
|
||||
timeout: DEFAULT_SYNC_TIMEOUT,
|
||||
signed_references_minimum_feature_level: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ fn test_cob_deletion() {
|
|||
|
||||
radicle::assert_matches!(
|
||||
bob.handle
|
||||
.fetch(rid, alice.id, radicle::node::DEFAULT_TIMEOUT)
|
||||
.fetch(rid, alice.id, radicle::node::DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
radicle::node::FetchResult::Success { .. }
|
||||
);
|
||||
|
|
|
|||
|
|
@ -87,7 +87,9 @@ fn rad_id_threshold() {
|
|||
alice.connect(&seed).connect(&bob);
|
||||
bob.connect(&seed);
|
||||
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")
|
||||
.unwrap()
|
||||
|
|
@ -323,10 +325,12 @@ fn rad_id_collaboration() {
|
|||
.connect(&distrustful)
|
||||
.converge([&seed, &distrustful]);
|
||||
|
||||
seed.handle.fetch(acme, alice.id, DEFAULT_TIMEOUT).unwrap();
|
||||
seed.handle
|
||||
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap();
|
||||
distrustful
|
||||
.handle
|
||||
.fetch(acme, alice.id, DEFAULT_TIMEOUT)
|
||||
.fetch(acme, alice.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap();
|
||||
|
||||
formula(&environment.tempdir(), "examples/rad-id-collaboration.md")
|
||||
|
|
|
|||
|
|
@ -87,7 +87,9 @@ fn rad_clean() {
|
|||
bob.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.announce(acme, 1, bob.home.path()).unwrap();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use std::io::LineWriter;
|
|||
use std::path::PathBuf;
|
||||
use std::{io, net, time};
|
||||
|
||||
use radicle::storage::refs;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
#[cfg(windows)]
|
||||
|
|
@ -112,8 +113,20 @@ where
|
|||
CommandResult::ok().to_writer(writer).ok();
|
||||
}
|
||||
},
|
||||
Command::Fetch { rid, nid, timeout } => {
|
||||
fetch(rid, nid, timeout, writer, &mut handle)?;
|
||||
Command::Fetch {
|
||||
rid,
|
||||
nid,
|
||||
timeout,
|
||||
signed_references_minimum_feature_level,
|
||||
} => {
|
||||
fetch(
|
||||
rid,
|
||||
nid,
|
||||
timeout,
|
||||
signed_references_minimum_feature_level,
|
||||
writer,
|
||||
&mut handle,
|
||||
)?;
|
||||
}
|
||||
Command::Config => {
|
||||
let config = handle.config()?;
|
||||
|
|
@ -248,10 +261,11 @@ fn fetch<W: Write, H: Handle<Error = runtime::HandleError>>(
|
|||
id: RepoId,
|
||||
node: NodeId,
|
||||
timeout: time::Duration,
|
||||
signed_references_minimum_feature_level: Option<refs::FeatureLevel>,
|
||||
mut writer: W,
|
||||
handle: &mut H,
|
||||
) -> Result<(), CommandError> {
|
||||
match handle.fetch(id, node, timeout) {
|
||||
match handle.fetch(id, node, timeout, signed_references_minimum_feature_level) {
|
||||
Ok(result) => {
|
||||
json::to_writer(&mut writer, &result)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use radicle::node::events::{Event, Events};
|
|||
use radicle::node::policy;
|
||||
use radicle::node::{Config, NodeId};
|
||||
use radicle::node::{ConnectOptions, ConnectResult, Seeds};
|
||||
use radicle::storage::refs;
|
||||
use serde_json::json;
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -229,9 +230,16 @@ impl radicle::node::Handle for Handle {
|
|||
id: RepoId,
|
||||
from: NodeId,
|
||||
timeout: time::Duration,
|
||||
signed_references_minimum_feature_level: Option<refs::FeatureLevel>,
|
||||
) -> Result<FetchResult, Error> {
|
||||
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()??)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::time;
|
|||
|
||||
use radicle::crypto::PublicKey;
|
||||
use radicle::git::Oid;
|
||||
use radicle::storage::refs::RefsAt;
|
||||
use radicle::storage::refs::{self, RefsAt};
|
||||
|
||||
use crate::identity::RepoId;
|
||||
use crate::node::{Alias, Config, ConnectOptions, ConnectResult, Event, FetchResult, Seeds};
|
||||
|
|
@ -69,6 +69,7 @@ impl radicle::node::Handle for Handle {
|
|||
_id: RepoId,
|
||||
_from: NodeId,
|
||||
_timeout: time::Duration,
|
||||
_signed_references_minimum_feature_level: Option<refs::FeatureLevel>,
|
||||
) -> Result<FetchResult, Self::Error> {
|
||||
Ok(FetchResult::Success {
|
||||
updated: vec![],
|
||||
|
|
|
|||
|
|
@ -1496,15 +1496,15 @@ fn test_queued_fetch_max_capacity() {
|
|||
alice.connect_to(&bob);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// The first fetch is initiated.
|
||||
|
|
@ -1618,15 +1618,15 @@ fn test_queued_fetch_from_command_same_rid() {
|
|||
alice.connect_to(&carol);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// Peers Alice will fetch from.
|
||||
|
|
|
|||
|
|
@ -189,7 +189,10 @@ fn test_replication() {
|
|||
let seeds = alice.handle.seeds_for(acme, None).unwrap();
|
||||
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());
|
||||
|
||||
let updated = match result {
|
||||
|
|
@ -266,7 +269,10 @@ fn test_replication_ref_in_sigrefs() {
|
|||
converge([&alice, &bob]);
|
||||
|
||||
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 { .. });
|
||||
|
||||
|
|
@ -325,7 +331,10 @@ fn test_replication_invalid() {
|
|||
|
||||
alice.handle.follow(*carol.public_key(), None).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.
|
||||
assert!(result.is_success());
|
||||
|
|
@ -355,7 +364,10 @@ fn test_migrated_clone() {
|
|||
let updated = bob.handle.seed(acme, Scope::All).unwrap();
|
||||
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());
|
||||
|
||||
log::debug!(target: "test", "Fetch complete with {}", alice.id);
|
||||
|
|
@ -366,7 +378,10 @@ fn test_migrated_clone() {
|
|||
std::fs::remove_dir_all(path).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());
|
||||
|
||||
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());
|
||||
|
||||
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());
|
||||
|
||||
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
|
|
@ -451,7 +472,10 @@ fn test_fetch_followed_remotes() {
|
|||
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());
|
||||
|
||||
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.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());
|
||||
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
||||
rad::fork_remote(acme, &alice.id, &carol, &bob.storage).unwrap();
|
||||
|
|
@ -494,7 +521,10 @@ fn test_missing_remote() {
|
|||
Title::new("Missing Remote").unwrap(),
|
||||
"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());
|
||||
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.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());
|
||||
|
||||
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
||||
|
|
@ -529,7 +562,10 @@ fn test_fetch_preserve_owned_refs() {
|
|||
.unwrap();
|
||||
|
||||
// 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();
|
||||
assert_eq!(updated, vec![]);
|
||||
|
||||
|
|
@ -562,7 +598,10 @@ fn test_clone() {
|
|||
let seeds = alice.handle.seeds_for(acme, None).unwrap();
|
||||
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());
|
||||
|
||||
rad::fork(acme, &alice.signer, &alice.storage).unwrap();
|
||||
|
|
@ -616,11 +655,17 @@ fn test_fetch_up_to_date() {
|
|||
transport::local::register(alice.storage.clone());
|
||||
|
||||
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());
|
||||
|
||||
// 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!(
|
||||
result.success(),
|
||||
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());
|
||||
|
||||
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());
|
||||
|
||||
// Bob stops seeding the repository
|
||||
assert!(bob.handle.unseed(acme).unwrap());
|
||||
|
||||
// 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 { .. });
|
||||
}
|
||||
|
||||
|
|
@ -896,9 +947,14 @@ fn test_non_fastforward_sigrefs() {
|
|||
converge([&alice, &bob, &eve]);
|
||||
|
||||
// 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.
|
||||
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
|
||||
.storage
|
||||
.repository(rid)
|
||||
|
|
@ -940,7 +996,10 @@ fn test_non_fastforward_sigrefs() {
|
|||
"Updated sigrefs are harshing my vibes",
|
||||
);
|
||||
// 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
|
||||
.storage
|
||||
.repository(rid)
|
||||
|
|
@ -972,7 +1031,7 @@ fn test_non_fastforward_sigrefs() {
|
|||
}
|
||||
|
||||
assert_matches!(
|
||||
alice.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
alice.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT, None).unwrap(),
|
||||
FetchResult::Success { updated, .. }
|
||||
if updated.iter().all(|u| u.is_skipped())
|
||||
);
|
||||
|
|
@ -999,11 +1058,15 @@ fn test_outdated_sigrefs() {
|
|||
eve.connect(&alice);
|
||||
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());
|
||||
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());
|
||||
rad::fork(rid, &eve.signer, &eve.storage).unwrap();
|
||||
|
||||
|
|
@ -1011,13 +1074,18 @@ fn test_outdated_sigrefs() {
|
|||
.handle
|
||||
.follow(eve.id, Some(Alias::new("eve")))
|
||||
.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();
|
||||
assert!(repo.remote(&eve.id).is_ok());
|
||||
|
||||
log::debug!(target: "test", "Bob fetches from Eve..");
|
||||
assert_matches!(
|
||||
bob.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
bob.handle
|
||||
.fetch(rid, eve.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
FetchResult::Success { .. }
|
||||
);
|
||||
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
|
||||
log::debug!(target: "test", "Alice fetches from Eve..");
|
||||
assert_matches!(
|
||||
alice.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
alice
|
||||
.handle
|
||||
.fetch(rid, eve.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
FetchResult::Success { .. }
|
||||
);
|
||||
let repo = alice.storage.repository(rid).unwrap();
|
||||
|
|
@ -1059,7 +1130,10 @@ fn test_outdated_sigrefs() {
|
|||
.follow(bob.id, Some(Alias::new("bob")))
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
alice.handle.fetch(rid, bob.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
alice
|
||||
.handle
|
||||
.fetch(rid, bob.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
FetchResult::Success { .. }
|
||||
);
|
||||
|
||||
|
|
@ -1093,11 +1167,15 @@ fn test_outdated_delegate_sigrefs() {
|
|||
eve.connect(&alice);
|
||||
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());
|
||||
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());
|
||||
rad::fork(rid, &eve.signer, &eve.storage).unwrap();
|
||||
|
||||
|
|
@ -1105,13 +1183,18 @@ fn test_outdated_delegate_sigrefs() {
|
|||
.handle
|
||||
.follow(eve.id, Some(Alias::new("eve")))
|
||||
.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();
|
||||
assert!(repo.remote(&eve.id).is_ok());
|
||||
|
||||
log::debug!(target: "test", "Bob fetches from Eve..");
|
||||
assert_matches!(
|
||||
bob.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
bob.handle
|
||||
.fetch(rid, eve.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
FetchResult::Success { .. }
|
||||
);
|
||||
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
|
||||
log::debug!(target: "test", "Alice fetches from Eve..");
|
||||
assert_matches!(
|
||||
eve.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
eve.handle
|
||||
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
FetchResult::Success { .. }
|
||||
);
|
||||
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();
|
||||
assert_matches!(
|
||||
eve.handle.fetch(rid, bob.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
eve.handle
|
||||
.fetch(rid, bob.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
FetchResult::Success { .. }
|
||||
);
|
||||
|
||||
|
|
@ -1175,7 +1262,9 @@ fn missing_default_branch() {
|
|||
alice.connect(&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());
|
||||
|
||||
// Fetching from still works despite not having
|
||||
|
|
@ -1185,7 +1274,10 @@ fn missing_default_branch() {
|
|||
Title::new("Hello, Acme").unwrap(),
|
||||
"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();
|
||||
|
|
@ -1207,7 +1299,9 @@ fn missing_default_branch() {
|
|||
|
||||
// Fetching from her will still succeed.
|
||||
assert_matches!(
|
||||
bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
bob.handle
|
||||
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
FetchResult::Success { .. }
|
||||
);
|
||||
let repo = bob.storage.repository(rid).unwrap();
|
||||
|
|
@ -1240,7 +1334,9 @@ fn missing_delegate_default_branch() {
|
|||
converge([&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
|
||||
.wait(
|
||||
|e| {
|
||||
|
|
@ -1312,7 +1408,9 @@ fn missing_delegate_default_branch() {
|
|||
// a) Bob's default branch is still missing
|
||||
// b) Bob's issue is there
|
||||
assert_matches!(
|
||||
seed.handle.fetch(rid, bob.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
seed.handle
|
||||
.fetch(rid, bob.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
FetchResult::Success { .. }
|
||||
);
|
||||
{
|
||||
|
|
@ -1323,7 +1421,10 @@ fn missing_delegate_default_branch() {
|
|||
|
||||
// Do the same for Alice
|
||||
assert_matches!(
|
||||
alice.handle.fetch(rid, seed.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
alice
|
||||
.handle
|
||||
.fetch(rid, seed.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
FetchResult::Success { .. }
|
||||
);
|
||||
{
|
||||
|
|
@ -1334,7 +1435,9 @@ fn missing_delegate_default_branch() {
|
|||
|
||||
// Check that Bob can still fetch from the seed
|
||||
assert_matches!(
|
||||
bob.handle.fetch(rid, seed.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
bob.handle
|
||||
.fetch(rid, seed.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
FetchResult::Success { .. }
|
||||
);
|
||||
}
|
||||
|
|
@ -1360,11 +1463,15 @@ fn test_background_foreground_fetch() {
|
|||
alice.connect(&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());
|
||||
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());
|
||||
rad::fork(rid, &eve.signer, &eve.storage).unwrap();
|
||||
|
||||
|
|
@ -1373,7 +1480,10 @@ fn test_background_foreground_fetch() {
|
|||
.handle
|
||||
.follow(eve.id, Some(Alias::new("eve")))
|
||||
.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();
|
||||
assert!(repo.remote(&eve.id).is_ok());
|
||||
let repo = alice.storage.repository(rid).unwrap();
|
||||
|
|
@ -1414,7 +1524,10 @@ fn test_background_foreground_fetch() {
|
|||
// interfere
|
||||
log::debug!(target: "test", "Alice fetches from Eve..");
|
||||
assert_matches!(
|
||||
alice.handle.fetch(rid, eve.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
alice
|
||||
.handle
|
||||
.fetch(rid, eve.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
FetchResult::Success { .. }
|
||||
);
|
||||
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();
|
||||
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());
|
||||
|
||||
let FetchResult::Failed { reason } = result else {
|
||||
|
|
@ -1548,7 +1664,10 @@ fn test_fetch_emits_canonical_ref_update() {
|
|||
bob.handle.seed(rid, Scope::All).unwrap();
|
||||
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());
|
||||
|
||||
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.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
|
||||
.fetch(rid, alice.id, DEFAULT_TIMEOUT)
|
||||
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap();
|
||||
// Alice pushes new references to her laptop
|
||||
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.handle.seed(rid, Scope::All).unwrap();
|
||||
eve.handle
|
||||
.fetch(rid, alice_laptop.id, DEFAULT_TIMEOUT)
|
||||
.fetch(rid, alice_laptop.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap();
|
||||
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
|
||||
// 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!(!has_issue(&bob, &issue));
|
||||
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
|
||||
// since Bob now knows that Alice's Laptop is a delegate, the issue should
|
||||
// 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!(has_issue(&bob, &issue));
|
||||
let repo = bob.storage.repository(rid).unwrap();
|
||||
|
|
@ -1744,7 +1870,7 @@ fn test_block_prevents_fetch() {
|
|||
|
||||
let result = alice
|
||||
.handle
|
||||
.fetch(rid, bob.id, time::Duration::from_secs(5))
|
||||
.fetch(rid, bob.id, time::Duration::from_secs(5), None)
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(result, FetchResult::Failed { .. });
|
||||
|
|
@ -1768,7 +1894,9 @@ fn fetch_does_not_contain_rad_sigrefs_parent() {
|
|||
alice.connect(&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());
|
||||
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..");
|
||||
assert_matches!(
|
||||
bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap(),
|
||||
bob.handle
|
||||
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap(),
|
||||
FetchResult::Success { .. }
|
||||
);
|
||||
let repo = bob.storage.repository(rid).unwrap();
|
||||
|
|
|
|||
|
|
@ -863,9 +863,13 @@ where
|
|||
resp.err(e).ok();
|
||||
}
|
||||
},
|
||||
Command::Fetch(rid, seed, timeout, resp) => {
|
||||
// TODO(finto): pass through feature-level
|
||||
let config = self.fetch_config().with_timeout(timeout);
|
||||
Command::Fetch(rid, seed, timeout, signed_references_minimum_feature_level, resp) => {
|
||||
let feature_level = signed_references_minimum_feature_level
|
||||
.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));
|
||||
}
|
||||
Command::Seed(rid, scope, resp) => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use radicle::node::policy::Scope;
|
|||
use radicle::node::FetchResult;
|
||||
use radicle::node::Seeds;
|
||||
use radicle::node::{Address, Alias, Config, ConnectOptions};
|
||||
use radicle::storage::refs;
|
||||
use radicle::storage::refs::RefsAt;
|
||||
use radicle_core::{NodeId, RepoId};
|
||||
use thiserror::Error;
|
||||
|
|
@ -90,7 +91,13 @@ pub enum Command {
|
|||
/// sync status for given namespaces.
|
||||
Seeds(RepoId, HashSet<PublicKey>, Responder<Seeds>),
|
||||
/// 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(RepoId, Scope, Responder<bool>),
|
||||
/// Unseed the given repository.
|
||||
|
|
@ -150,9 +157,19 @@ impl Command {
|
|||
rid: RepoId,
|
||||
node_id: NodeId,
|
||||
duration: time::Duration,
|
||||
signed_references_minimum_feature_level: Option<refs::FeatureLevel>,
|
||||
) -> (Self, Receiver<Result<FetchResult>>) {
|
||||
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>>) {
|
||||
|
|
@ -191,7 +208,10 @@ impl fmt::Debug for Command {
|
|||
Self::Config(_) => write!(f, "Config"),
|
||||
Self::ListenAddrs(_) => write!(f, "ListenAddrs"),
|
||||
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::Unseed(id, _) => write!(f, "Unseed({id})"),
|
||||
Self::Follow(id, _, _) => write!(f, "Follow({id})"),
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ use crate::crypto::PublicKey;
|
|||
use crate::git;
|
||||
use crate::identity::RepoId;
|
||||
use crate::profile;
|
||||
use crate::storage::refs::RefsAt;
|
||||
use crate::storage::refs::{FeatureLevel, RefsAt};
|
||||
use crate::storage::RefUpdate;
|
||||
|
||||
pub use address::KnownAddress;
|
||||
|
|
@ -975,6 +975,7 @@ pub trait Handle: Clone + Sync + Send {
|
|||
id: RepoId,
|
||||
from: NodeId,
|
||||
timeout: time::Duration,
|
||||
signed_references_minimum_feature_level: Option<FeatureLevel>,
|
||||
) -> Result<FetchResult, Self::Error>;
|
||||
/// Start seeding the given repo. May update the scope. Does nothing if the
|
||||
/// repo is already seeded.
|
||||
|
|
@ -1244,6 +1245,7 @@ impl Handle for Node {
|
|||
rid: RepoId,
|
||||
from: NodeId,
|
||||
timeout: time::Duration,
|
||||
signed_references_minimum_feature_level: Option<FeatureLevel>,
|
||||
) -> Result<FetchResult, Error> {
|
||||
let result = self
|
||||
.call(
|
||||
|
|
@ -1251,6 +1253,7 @@ impl Handle for Node {
|
|||
rid,
|
||||
nid: from,
|
||||
timeout,
|
||||
signed_references_minimum_feature_level,
|
||||
},
|
||||
DEFAULT_TIMEOUT.max(timeout),
|
||||
)?
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use serde_json as json;
|
|||
|
||||
use crate::crypto::PublicKey;
|
||||
use crate::identity::RepoId;
|
||||
use crate::storage::refs;
|
||||
|
||||
use super::events::Event;
|
||||
use super::NodeId;
|
||||
|
|
@ -96,6 +97,7 @@ pub enum Command {
|
|||
rid: RepoId,
|
||||
nid: NodeId,
|
||||
timeout: time::Duration,
|
||||
signed_references_minimum_feature_level: Option<refs::FeatureLevel>,
|
||||
},
|
||||
|
||||
/// Seed the given repository.
|
||||
|
|
|
|||
Loading…
Reference in New Issue