cli/sync: migrate to clap
Due to the idiosyncracies of this command there are a few things to note about
this migration.
The `rad sync` command acts as a default subcommand in itself, in that it has
options that do not apply to its subcommand, `rad sync status`. This means that
the options will print with `rad sync --help`, but they will not print with `rad
sync status`.
The `--inventory` flag is not compatible with any of the other flags and
options, thus they are all marked with `conflicts_with`. This cannot be done for
a positional argument, so `clap` will "helpfully" print out the usage, for
example, as:
error: the argument '--inventory' cannot be used with '--fetch'
Usage: rad sync --inventory [RID]
For more information, try '--help'.
Which is incorrect, since `RID` is ignored. This is explained in the `--help`
documentation for `--inventory`.
Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
This commit is contained in:
parent
d1e19a87b0
commit
3c89525003
|
|
@ -142,7 +142,10 @@ const COMMANDS: &[CommandItem] = &[
|
|||
name: "stats",
|
||||
about: crate::commands::stats::ABOUT,
|
||||
},
|
||||
CommandItem::Lexopt(crate::commands::sync::HELP),
|
||||
CommandItem::Clap {
|
||||
name: "sync",
|
||||
about: crate::commands::sync::ABOUT,
|
||||
},
|
||||
CommandItem::Clap {
|
||||
name: "watch",
|
||||
about: crate::commands::watch::ABOUT,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
mod args;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::OsString;
|
||||
use std::str::FromStr;
|
||||
use std::time;
|
||||
|
||||
use anyhow::{anyhow, Context as _};
|
||||
|
|
@ -23,266 +22,14 @@ use radicle_term::Element;
|
|||
use crate::node::SyncReporting;
|
||||
use crate::node::SyncSettings;
|
||||
use crate::terminal as term;
|
||||
use crate::terminal::args::{Args, Error, Help};
|
||||
use crate::terminal::format::Author;
|
||||
use crate::terminal::{Table, TableOptions};
|
||||
|
||||
pub const HELP: Help = Help {
|
||||
name: "sync",
|
||||
description: "Sync repositories to the network",
|
||||
version: env!("RADICLE_VERSION"),
|
||||
usage: r#"
|
||||
Usage
|
||||
pub use args::Args;
|
||||
pub(crate) use args::ABOUT;
|
||||
use args::{Command, SortBy, SyncDirection, SyncMode};
|
||||
|
||||
rad sync [--fetch | --announce] [<rid>] [<option>...]
|
||||
rad sync --inventory [<option>...]
|
||||
rad sync status [<rid>] [<option>...]
|
||||
|
||||
By default, the current repository is synchronized both ways.
|
||||
If an <rid> is specified, that repository is synced instead.
|
||||
|
||||
The process begins by fetching changes from connected seeds,
|
||||
followed by announcing local refs to peers, thereby prompting
|
||||
them to fetch from us.
|
||||
|
||||
When `--fetch` is specified, any number of seeds may be given
|
||||
using the `--seed` option, eg. `--seed <nid>@<addr>:<port>`.
|
||||
|
||||
When `--replicas` is specified, the given replication factor will try
|
||||
to be matched. For example, `--replicas 5` will sync with 5 seeds.
|
||||
|
||||
The synchronization process can be configured using `--replicas <min>` and
|
||||
`--replicas-max <max>`. If these options are used independently, then the
|
||||
replication factor is taken as the given `<min>`/`<max>` value. If the
|
||||
options are used together, then the replication factor has a minimum and
|
||||
maximum bound.
|
||||
|
||||
For fetching, the synchronization process will be considered successful if
|
||||
at least `<min>` seeds were fetched from *or* all preferred seeds were
|
||||
fetched from. If `<max>` is specified then the process will continue and
|
||||
attempt to sync with `<max>` seeds.
|
||||
|
||||
For reference announcing, the synchronization process will be considered
|
||||
successful if at least `<min>` seeds were pushed to *and* all preferred
|
||||
seeds were pushed to.
|
||||
|
||||
When `--fetch` or `--announce` are specified on their own, this command
|
||||
will only fetch or announce.
|
||||
|
||||
If `--inventory` is specified, the node's inventory is announced to
|
||||
the network. This mode does not take an `<rid>`.
|
||||
|
||||
Commands
|
||||
|
||||
status Display the sync status of a repository
|
||||
|
||||
Options
|
||||
|
||||
--sort-by <field> Sort the table by column (options: nid, alias, status)
|
||||
-f, --fetch Turn on fetching (default: true)
|
||||
-a, --announce Turn on ref announcing (default: true)
|
||||
-i, --inventory Turn on inventory announcing (default: false)
|
||||
--timeout <secs> How many seconds to wait while syncing
|
||||
--seed <nid> Sync with the given node (may be specified multiple times)
|
||||
-r, --replicas <count> Sync with a specific number of seeds
|
||||
--replicas-max <count> Sync with an upper bound number of seeds
|
||||
-v, --verbose Verbose output
|
||||
--debug Print debug information afer sync
|
||||
--help Print help
|
||||
"#,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub enum Operation {
|
||||
Synchronize(SyncMode),
|
||||
#[default]
|
||||
Status,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum SortBy {
|
||||
Nid,
|
||||
Alias,
|
||||
#[default]
|
||||
Status,
|
||||
}
|
||||
|
||||
impl FromStr for SortBy {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"nid" => Ok(Self::Nid),
|
||||
"alias" => Ok(Self::Alias),
|
||||
"status" => Ok(Self::Status),
|
||||
_ => Err("invalid `--sort-by` field"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SyncMode {
|
||||
Repo {
|
||||
settings: SyncSettings,
|
||||
direction: SyncDirection,
|
||||
},
|
||||
Inventory,
|
||||
}
|
||||
|
||||
impl Default for SyncMode {
|
||||
fn default() -> Self {
|
||||
Self::Repo {
|
||||
settings: SyncSettings::default(),
|
||||
direction: SyncDirection::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone)]
|
||||
pub enum SyncDirection {
|
||||
Fetch,
|
||||
Announce,
|
||||
#[default]
|
||||
Both,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Options {
|
||||
pub rid: Option<RepoId>,
|
||||
pub debug: bool,
|
||||
pub verbose: bool,
|
||||
pub sort_by: SortBy,
|
||||
pub op: Operation,
|
||||
}
|
||||
|
||||
impl Args for Options {
|
||||
fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
|
||||
use lexopt::prelude::*;
|
||||
|
||||
let mut parser = lexopt::Parser::from_args(args);
|
||||
let mut verbose = false;
|
||||
let mut timeout = time::Duration::from_secs(9);
|
||||
let mut rid = None;
|
||||
let mut fetch = false;
|
||||
let mut announce = false;
|
||||
let mut inventory = false;
|
||||
let mut debug = false;
|
||||
let mut replicas = None;
|
||||
let mut max_replicas = None;
|
||||
let mut seeds = BTreeSet::new();
|
||||
let mut sort_by = SortBy::default();
|
||||
let mut op: Option<Operation> = None;
|
||||
|
||||
while let Some(arg) = parser.next()? {
|
||||
match arg {
|
||||
Long("debug") => {
|
||||
debug = true;
|
||||
}
|
||||
Long("verbose") | Short('v') => {
|
||||
verbose = true;
|
||||
}
|
||||
Long("fetch") | Short('f') => {
|
||||
fetch = true;
|
||||
}
|
||||
Long("replicas") | Short('r') => {
|
||||
let val = parser.value()?;
|
||||
let count = term::args::number(&val)?;
|
||||
|
||||
if count == 0 {
|
||||
anyhow::bail!("value for `--replicas` must be greater than zero");
|
||||
}
|
||||
replicas = Some(count);
|
||||
}
|
||||
Long("replicas-max") => {
|
||||
let val = parser.value()?;
|
||||
let count = term::args::number(&val)?;
|
||||
|
||||
if count == 0 {
|
||||
anyhow::bail!("value for `--replicas-max` must be greater than zero");
|
||||
}
|
||||
max_replicas = Some(count);
|
||||
}
|
||||
Long("seed") => {
|
||||
let val = parser.value()?;
|
||||
let nid = term::args::nid(&val)?;
|
||||
|
||||
seeds.insert(nid);
|
||||
}
|
||||
Long("announce") | Short('a') => {
|
||||
announce = true;
|
||||
}
|
||||
Long("inventory") | Short('i') => {
|
||||
inventory = true;
|
||||
}
|
||||
Long("sort-by") if matches!(op, Some(Operation::Status)) => {
|
||||
let value = parser.value()?;
|
||||
sort_by = value.parse()?;
|
||||
}
|
||||
Long("timeout") | Short('t') => {
|
||||
let value = parser.value()?;
|
||||
let secs = term::args::parse_value("timeout", value)?;
|
||||
|
||||
timeout = time::Duration::from_secs(secs);
|
||||
}
|
||||
Long("help") | Short('h') => {
|
||||
return Err(Error::Help.into());
|
||||
}
|
||||
Value(val) if rid.is_none() => match val.to_string_lossy().as_ref() {
|
||||
"s" | "status" => {
|
||||
op = Some(Operation::Status);
|
||||
}
|
||||
_ => {
|
||||
rid = Some(term::args::rid(&val)?);
|
||||
}
|
||||
},
|
||||
arg => {
|
||||
return Err(anyhow!(arg.unexpected()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sync = if inventory && fetch {
|
||||
anyhow::bail!("`--inventory` cannot be used with `--fetch`");
|
||||
} else if inventory {
|
||||
SyncMode::Inventory
|
||||
} else {
|
||||
let direction = match (fetch, announce) {
|
||||
(true, true) | (false, false) => SyncDirection::Both,
|
||||
(true, false) => SyncDirection::Fetch,
|
||||
(false, true) => SyncDirection::Announce,
|
||||
};
|
||||
let mut settings = SyncSettings::default().timeout(timeout);
|
||||
|
||||
let replicas = match (replicas, max_replicas) {
|
||||
(None, None) => sync::ReplicationFactor::default(),
|
||||
(None, Some(min)) => sync::ReplicationFactor::must_reach(min),
|
||||
(Some(min), None) => sync::ReplicationFactor::must_reach(min),
|
||||
(Some(min), Some(max)) => sync::ReplicationFactor::range(min, max),
|
||||
};
|
||||
settings.replicas = replicas;
|
||||
if !seeds.is_empty() {
|
||||
settings.seeds = seeds;
|
||||
}
|
||||
SyncMode::Repo {
|
||||
settings,
|
||||
direction,
|
||||
}
|
||||
};
|
||||
|
||||
Ok((
|
||||
Options {
|
||||
rid,
|
||||
debug,
|
||||
verbose,
|
||||
sort_by,
|
||||
op: op.unwrap_or(Operation::Synchronize(sync)),
|
||||
},
|
||||
vec![],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||
let profile = ctx.profile()?;
|
||||
let mut node = radicle::Node::new(profile.socket());
|
||||
if !node.is_running() {
|
||||
|
|
@ -290,10 +37,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
"to sync a repository, your node must be running. To start it, run `rad node start`"
|
||||
);
|
||||
}
|
||||
let verbose = args.verbose;
|
||||
let debug = args.verbose;
|
||||
|
||||
match &options.op {
|
||||
Operation::Status => {
|
||||
let rid = match options.rid {
|
||||
match args.command {
|
||||
Some(Command::Status { rid, sort_by }) => {
|
||||
let rid = match rid {
|
||||
Some(rid) => rid,
|
||||
None => {
|
||||
let (_, rid) = radicle::rad::cwd()
|
||||
|
|
@ -301,37 +50,41 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
rid
|
||||
}
|
||||
};
|
||||
sync_status(rid, &mut node, &profile, &options)?;
|
||||
sync_status(rid, &mut node, &profile, &sort_by, verbose)?;
|
||||
}
|
||||
Operation::Synchronize(SyncMode::Repo {
|
||||
settings,
|
||||
direction,
|
||||
}) => {
|
||||
let rid = match options.rid {
|
||||
Some(rid) => rid,
|
||||
None => {
|
||||
let (_, rid) = radicle::rad::cwd()
|
||||
.context("Current directory is not a Radicle repository")?;
|
||||
rid
|
||||
}
|
||||
};
|
||||
let settings = settings.clone().with_profile(&profile);
|
||||
None => match SyncMode::from(args.sync) {
|
||||
SyncMode::Repo {
|
||||
rid,
|
||||
settings,
|
||||
direction,
|
||||
} => {
|
||||
let rid = match rid {
|
||||
Some(rid) => rid,
|
||||
None => {
|
||||
let (_, rid) = radicle::rad::cwd()
|
||||
.context("Current directory is not a Radicle repository")?;
|
||||
rid
|
||||
}
|
||||
};
|
||||
let settings = settings.clone().with_profile(&profile);
|
||||
|
||||
if [SyncDirection::Fetch, SyncDirection::Both].contains(direction) {
|
||||
if !profile.policies()?.is_seeding(&rid)? {
|
||||
anyhow::bail!("repository {rid} is not seeded");
|
||||
if matches!(direction, SyncDirection::Fetch | SyncDirection::Both) {
|
||||
if !profile.policies()?.is_seeding(&rid)? {
|
||||
anyhow::bail!("repository {rid} is not seeded");
|
||||
}
|
||||
let result = fetch(rid, settings.clone(), &mut node, &profile)?;
|
||||
display_fetch_result(&result, verbose)
|
||||
}
|
||||
if matches!(direction, SyncDirection::Announce | SyncDirection::Both) {
|
||||
announce_refs(rid, settings, &mut node, &profile, verbose, debug)?;
|
||||
}
|
||||
let result = fetch(rid, settings.clone(), &mut node, &profile)?;
|
||||
display_fetch_result(&result, options.verbose)
|
||||
}
|
||||
if [SyncDirection::Announce, SyncDirection::Both].contains(direction) {
|
||||
announce_refs(rid, settings, &mut node, &profile, &options)?;
|
||||
SyncMode::Inventory => {
|
||||
announce_inventory(node)?;
|
||||
}
|
||||
}
|
||||
Operation::Synchronize(SyncMode::Inventory) => {
|
||||
announce_inventory(node)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -339,7 +92,8 @@ fn sync_status(
|
|||
rid: RepoId,
|
||||
node: &mut Node,
|
||||
profile: &Profile,
|
||||
options: &Options,
|
||||
sort_by: &SortBy,
|
||||
verbose: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
const SYMBOL_STATE: &str = "?";
|
||||
const SYMBOL_STATE_UNKNOWN: &str = "•";
|
||||
|
|
@ -358,7 +112,7 @@ fn sync_status(
|
|||
]);
|
||||
table.divider();
|
||||
|
||||
sort_seeds_by(local_nid, &mut seeds, &aliases, &options.sort_by);
|
||||
sort_seeds_by(local_nid, &mut seeds, &aliases, sort_by);
|
||||
|
||||
let seeds = seeds.into_iter().flat_map(|seed| {
|
||||
let (status, head, time) = match seed.sync {
|
||||
|
|
@ -386,7 +140,7 @@ fn sync_status(
|
|||
term::format::oid(oid),
|
||||
term::format::timestamp(timestamp),
|
||||
),
|
||||
None if options.verbose => (
|
||||
None if verbose => (
|
||||
term::format::dim(SYMBOL_STATE_UNKNOWN),
|
||||
term::paint(String::new()),
|
||||
term::paint(String::new()),
|
||||
|
|
@ -394,7 +148,7 @@ fn sync_status(
|
|||
None => return None,
|
||||
};
|
||||
|
||||
let (alias, nid) = Author::new(&seed.nid, profile, options.verbose).labels();
|
||||
let (alias, nid) = Author::new(&seed.nid, profile, verbose).labels();
|
||||
|
||||
Some([
|
||||
nid,
|
||||
|
|
@ -448,7 +202,8 @@ fn announce_refs(
|
|||
settings: SyncSettings,
|
||||
node: &mut Node,
|
||||
profile: &Profile,
|
||||
options: &Options,
|
||||
verbose: bool,
|
||||
debug: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let Ok(repo) = profile.storage.repository(rid) else {
|
||||
return Err(anyhow!(
|
||||
|
|
@ -470,14 +225,14 @@ fn announce_refs(
|
|||
&repo,
|
||||
settings,
|
||||
SyncReporting {
|
||||
debug: options.debug,
|
||||
debug,
|
||||
..SyncReporting::default()
|
||||
},
|
||||
node,
|
||||
profile,
|
||||
)?;
|
||||
if let Some(result) = result {
|
||||
print_announcer_result(&result, options.verbose)
|
||||
print_announcer_result(&result, verbose)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,253 @@
|
|||
use std::str::FromStr;
|
||||
use std::time;
|
||||
|
||||
use clap::{Parser, Subcommand, ValueEnum};
|
||||
|
||||
use radicle::{
|
||||
node::{sync, NodeId},
|
||||
prelude::RepoId,
|
||||
};
|
||||
|
||||
use crate::node::SyncSettings;
|
||||
|
||||
pub(crate) const ABOUT: &str = "Sync repositories to the network";
|
||||
|
||||
const LONG_ABOUT: &str = r#"
|
||||
By default, the current repository is synchronized both ways.
|
||||
If an <RID> is specified, that repository is synced instead.
|
||||
|
||||
The process begins by fetching changes from connected seeds,
|
||||
followed by announcing local refs to peers, thereby prompting
|
||||
them to fetch from us.
|
||||
|
||||
When `--fetch` is specified, any number of seeds may be given
|
||||
using the `--seed` option, eg. `--seed <NID>@<ADDR>:<PORT>`.
|
||||
|
||||
When `--replicas` is specified, the given replication factor will try
|
||||
to be matched. For example, `--replicas 5` will sync with 5 seeds.
|
||||
|
||||
The synchronization process can be configured using `--replicas <MIN>` and
|
||||
`--replicas-max <MAX>`. If these options are used independently, then the
|
||||
replication factor is taken as the given `<MIN>`/`<MAX>` value. If the
|
||||
options are used together, then the replication factor has a minimum and
|
||||
maximum bound.
|
||||
|
||||
For fetching, the synchronization process will be considered successful if
|
||||
at least `<MIN>` seeds were fetched from *or* all preferred seeds were
|
||||
fetched from. If `<MAX>` is specified then the process will continue and
|
||||
attempt to sync with `<MAX>` seeds.
|
||||
|
||||
For reference announcing, the synchronization process will be considered
|
||||
successful if at least `<MIN>` seeds were pushed to *and* all preferred
|
||||
seeds were pushed to.
|
||||
|
||||
When `--fetch` or `--announce` are specified on their own, this command
|
||||
will only fetch or announce.
|
||||
|
||||
If `--inventory` is specified, the node's inventory is announced to
|
||||
the network. This mode does not take an `<RID>`.
|
||||
"#;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(about = ABOUT, long_about = LONG_ABOUT, disable_version_flag = true)]
|
||||
pub struct Args {
|
||||
#[clap(subcommand)]
|
||||
pub(super) command: Option<Command>,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub(super) sync: SyncArgs,
|
||||
|
||||
/// Enable debug information when synchronizing
|
||||
#[arg(long)]
|
||||
pub(super) debug: bool,
|
||||
|
||||
/// Enable verbose information when synchronizing
|
||||
#[arg(long, short)]
|
||||
pub(super) verbose: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub(super) struct SyncArgs {
|
||||
/// Enable fetching [default: true]
|
||||
///
|
||||
/// Providing `--announce` without `--fetch` will disable fetching
|
||||
#[arg(long, short, conflicts_with = "inventory")]
|
||||
fetch: bool,
|
||||
|
||||
/// Enable announcing [default: true]
|
||||
///
|
||||
/// Providing `--fetch` without `--announce` will disable announcing
|
||||
#[arg(long, short, conflicts_with = "inventory")]
|
||||
announce: bool,
|
||||
|
||||
/// Synchronize with the given node (may be specified multiple times)
|
||||
#[arg(
|
||||
long = "seed",
|
||||
value_name = "NID",
|
||||
action = clap::ArgAction::Append,
|
||||
conflicts_with = "inventory",
|
||||
)]
|
||||
seeds: Vec<NodeId>,
|
||||
|
||||
/// How many seconds to wait while synchronizing
|
||||
#[arg(
|
||||
long,
|
||||
short,
|
||||
default_value_t = 9,
|
||||
value_name = "SECS",
|
||||
conflicts_with = "inventory"
|
||||
)]
|
||||
timeout: u64,
|
||||
|
||||
/// The repository to perform the synchronizing for [default: cwd]
|
||||
rid: Option<RepoId>,
|
||||
|
||||
/// Synchronize with a specific number of seeds
|
||||
///
|
||||
/// The value must be greater than zero
|
||||
#[arg(
|
||||
long,
|
||||
short,
|
||||
value_name = "COUNT",
|
||||
value_parser = replicas_non_zero,
|
||||
conflicts_with = "inventory",
|
||||
default_value_t = radicle::node::sync::DEFAULT_REPLICATION_FACTOR,
|
||||
)]
|
||||
replicas: usize,
|
||||
|
||||
/// Synchronize with an upper bound number of seeds
|
||||
///
|
||||
/// The value must be greater than zero
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "COUNT",
|
||||
value_parser = replicas_non_zero,
|
||||
conflicts_with = "inventory",
|
||||
)]
|
||||
max_replicas: Option<usize>,
|
||||
|
||||
/// Enable announcing inventory [default: false]
|
||||
///
|
||||
/// `--inventory` is a standalone mode and is not compatible with the other
|
||||
/// options
|
||||
///
|
||||
/// <RID> is ignored with `--inventory`
|
||||
#[arg(long, short)]
|
||||
inventory: bool,
|
||||
}
|
||||
|
||||
impl SyncArgs {
|
||||
fn direction(&self) -> SyncDirection {
|
||||
match (self.fetch, self.announce) {
|
||||
(true, true) | (false, false) => SyncDirection::Both,
|
||||
(true, false) => SyncDirection::Fetch,
|
||||
(false, true) => SyncDirection::Announce,
|
||||
}
|
||||
}
|
||||
|
||||
fn timeout(&self) -> time::Duration {
|
||||
time::Duration::from_secs(self.timeout)
|
||||
}
|
||||
|
||||
fn replication(&self) -> sync::ReplicationFactor {
|
||||
match (self.replicas, self.max_replicas) {
|
||||
(min, None) => sync::ReplicationFactor::must_reach(min),
|
||||
(min, Some(max)) => sync::ReplicationFactor::range(min, max),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub(super) enum Command {
|
||||
/// Display the sync status of a repository
|
||||
#[clap(alias = "s")]
|
||||
Status {
|
||||
/// The repository to display the status for [default: cwd]
|
||||
rid: Option<RepoId>,
|
||||
/// Sort the table by column
|
||||
#[arg(long, value_name = "FIELD", value_enum, default_value_t)]
|
||||
sort_by: SortBy,
|
||||
},
|
||||
}
|
||||
|
||||
/// Sort the status table by the provided field
|
||||
#[derive(ValueEnum, Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub(super) enum SortBy {
|
||||
/// The NID of the entry
|
||||
Nid,
|
||||
/// The alias of the entry
|
||||
Alias,
|
||||
/// The status of the entry
|
||||
#[default]
|
||||
Status,
|
||||
}
|
||||
|
||||
impl FromStr for SortBy {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"nid" => Ok(Self::Nid),
|
||||
"alias" => Ok(Self::Alias),
|
||||
"status" => Ok(Self::Status),
|
||||
_ => Err("invalid `--sort-by` field"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether we are performing a fetch/announce of a repository or only
|
||||
/// announcing the node's inventory
|
||||
pub(super) enum SyncMode {
|
||||
/// Fetch and/or announce a repositories references
|
||||
Repo {
|
||||
/// The repository being synchronized
|
||||
rid: Option<RepoId>,
|
||||
/// The settings for fetch/announce
|
||||
settings: SyncSettings,
|
||||
/// The direction of the synchronization
|
||||
direction: SyncDirection,
|
||||
},
|
||||
/// Announce the node's inventory
|
||||
Inventory,
|
||||
}
|
||||
|
||||
impl From<SyncArgs> for SyncMode {
|
||||
fn from(args: SyncArgs) -> Self {
|
||||
if args.inventory {
|
||||
Self::Inventory
|
||||
} else {
|
||||
assert!(!args.inventory);
|
||||
let direction = args.direction();
|
||||
let mut settings = SyncSettings::default()
|
||||
.timeout(args.timeout())
|
||||
.replicas(args.replication());
|
||||
if !args.seeds.is_empty() {
|
||||
settings.seeds = args.seeds.into_iter().collect();
|
||||
}
|
||||
Self::Repo {
|
||||
rid: args.rid,
|
||||
settings,
|
||||
direction,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The direction of the [`SyncMode`]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(super) enum SyncDirection {
|
||||
/// Only fetching
|
||||
Fetch,
|
||||
/// Only announcing
|
||||
Announce,
|
||||
/// Both fetching and announcing
|
||||
Both,
|
||||
}
|
||||
|
||||
fn replicas_non_zero(s: &str) -> Result<usize, String> {
|
||||
let r = usize::from_str(s).map_err(|_| format!("{s} is not a number"))?;
|
||||
if r == 0 {
|
||||
return Err(format!("{s} must be a value greater than zero"));
|
||||
}
|
||||
Ok(r)
|
||||
}
|
||||
|
|
@ -83,6 +83,7 @@ enum Commands {
|
|||
#[command(name = "self")]
|
||||
RadSelf(rad_self::Args),
|
||||
Stats(stats::Args),
|
||||
Sync(sync::Args),
|
||||
Unblock(unblock::Args),
|
||||
Unfollow(unfollow::Args),
|
||||
Unseed(unseed::Args),
|
||||
|
|
@ -337,7 +338,9 @@ pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyho
|
|||
}
|
||||
}
|
||||
"sync" => {
|
||||
term::run_command_args::<sync::Options, _>(sync::HELP, sync::run, args.to_vec());
|
||||
if let Some(Commands::Sync(args)) = CliArgs::parse().command {
|
||||
term::run_command_fn(sync::run, args);
|
||||
}
|
||||
}
|
||||
"seed" => {
|
||||
if let Some(Commands::Seed(args)) = CliArgs::parse().command {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ use crate::prelude::Doc;
|
|||
|
||||
use super::NodeId;
|
||||
|
||||
pub const DEFAULT_REPLICATION_FACTOR: usize = 3;
|
||||
|
||||
/// A set of nodes that form a private network for fetching from.
|
||||
///
|
||||
/// This could be the set of allowed nodes for a private repository, using
|
||||
|
|
@ -72,7 +74,7 @@ pub struct ReplicationRange {
|
|||
|
||||
impl Default for ReplicationFactor {
|
||||
fn default() -> Self {
|
||||
Self::must_reach(3)
|
||||
Self::must_reach(DEFAULT_REPLICATION_FACTOR)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue