cli: Remove `rad fetch` command

The command is replaced with `rad sync --fetch`. Soon, it will be called
from the git-remote-helper, so users won't have to call it explicitly.
This commit is contained in:
Alexis Sellier 2023-04-24 14:15:09 +02:00
parent a285943166
commit d6cebf613f
No known key found for this signature in database
11 changed files with 122 additions and 175 deletions

View File

@ -18,7 +18,7 @@ Now that the project is tracked we can fetch it and we will have it in
our local storage. our local storage.
``` ```
$ rad fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji $ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi.. ✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s) ✓ Fetched repository from 1 seed(s)
``` ```

View File

@ -6,7 +6,7 @@ Changes have been proposed by another person (or peer) via a radicle patch. To
$ rad track did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --alias bob $ rad track did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --alias bob
✓ Tracking policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob) ✓ Tracking policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
! Warning: fetch after track is not yet supported ! Warning: fetch after track is not yet supported
$ rad fetch $ rad sync --fetch
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk.. ✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk..
✓ Fetched repository from 1 seed(s) ✓ Fetched repository from 1 seed(s)
``` ```

View File

@ -12,8 +12,6 @@ pub mod rad_comment;
pub mod rad_delegate; pub mod rad_delegate;
#[path = "commands/edit.rs"] #[path = "commands/edit.rs"]
pub mod rad_edit; pub mod rad_edit;
#[path = "commands/fetch.rs"]
pub mod rad_fetch;
#[path = "commands/fork.rs"] #[path = "commands/fork.rs"]
pub mod rad_fork; pub mod rad_fork;
#[path = "commands/help.rs"] #[path = "commands/help.rs"]

View File

@ -18,7 +18,7 @@ use radicle::storage;
use radicle::storage::git::Storage; use radicle::storage::git::Storage;
use crate::commands::rad_checkout as checkout; use crate::commands::rad_checkout as checkout;
use crate::commands::rad_fetch as fetch; use crate::commands::rad_sync as sync;
use crate::project; use crate::project;
use crate::terminal as term; use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help}; use crate::terminal::args::{Args, Error, Help};
@ -176,7 +176,7 @@ pub fn clone<G: Signer>(
); );
} }
let results = fetch::fetch(id, node)?; let results = sync::fetch_all(id, node)?;
let Ok(repository) = storage.repository(id) else { let Ok(repository) = storage.repository(id) else {
// If we don't have the project locally, even after attempting to fetch, // If we don't have the project locally, even after attempting to fetch,
// there's nothing we can do. // there's nothing we can do.

View File

@ -1,143 +0,0 @@
#![allow(clippy::or_fun_call)]
use std::ffi::OsString;
use std::path::Path;
use anyhow::{anyhow, Context};
use radicle::identity::doc::Id;
use radicle::node;
use radicle::node::{FetchResult, FetchResults, Handle as _, Node};
use radicle::prelude::*;
use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
pub const HELP: Help = Help {
name: "fetch",
description: "Fetch repository refs from the network",
version: env!("CARGO_PKG_VERSION"),
usage: r#"
Usage
rad fetch <rid> [<option>...]
By default, this command will fetch from all connected seeds.
To instead specify a seed, use the `--seed <nid>` option.
Options
--seed <nid> Fetch seed a specific connected peer
--force, -f Fetch even if the repository isn't tracked
--help Print help
"#,
};
#[derive(Debug)]
pub struct Options {
rid: Option<Id>,
seed: Option<NodeId>,
#[allow(dead_code)]
force: bool,
}
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 rid: Option<Id> = None;
let mut seed: Option<NodeId> = None;
let mut force = false;
while let Some(arg) = parser.next()? {
match arg {
Long("force") | Short('f') => {
force = true;
}
Long("seed") => {
let val = parser.value()?;
let val = term::args::nid(&val)?;
seed = Some(val);
}
Long("help") => {
return Err(Error::Help.into());
}
Value(val) if rid.is_none() => {
let val = term::args::rid(&val)?;
rid = Some(val);
}
_ => return Err(anyhow!(arg.unexpected())),
}
}
Ok((Options { rid, seed, force }, vec![]))
}
}
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;
let mut node = radicle::Node::new(profile.socket());
let rid = match options.rid {
Some(rid) => rid,
None => {
let (_, rid) = radicle::rad::repo(Path::new("."))
.context("Current directory is not a radicle project")?;
rid
}
};
// TODO(cloudhead): Check that we're tracking the repo, and if not, and `--force` is not
// used, abort with error.
let results = if let Some(seed) = options.seed {
let result = fetch_from(rid, &seed, &mut node)?;
FetchResults::from(vec![(seed, result)])
} else {
fetch(rid, &mut node)?
};
let success = results.success().count();
let failed = results.failed().count();
if success == 0 {
term::error(format!("Failed to fetch repository from {failed} seed(s)"));
} else {
term::success!("Fetched repository from {success} seed(s)");
}
Ok(())
}
pub fn fetch(rid: Id, node: &mut Node) -> Result<FetchResults, node::Error> {
// Get seeds. This consults the local routing table only.
let seeds = node.seeds(rid)?;
let mut results = FetchResults::default();
if seeds.has_connections() {
// Fetch from all seeds.
for seed in seeds.connected() {
let result = fetch_from(rid, seed, node)?;
results.push(*seed, result);
}
}
Ok(results)
}
pub fn fetch_from(rid: Id, seed: &NodeId, node: &mut Node) -> Result<FetchResult, node::Error> {
let spinner = term::spinner(format!(
"Fetching {} from {}..",
term::format::tertiary(rid),
term::format::tertiary(term::format::node(seed))
));
let result = node.fetch(rid, *seed)?;
match &result {
FetchResult::Success { .. } => {
spinner.finish();
}
FetchResult::Failed { reason } => {
spinner.error(reason);
}
}
Ok(result)
}

View File

@ -18,7 +18,6 @@ const COMMANDS: &[Help] = &[
rad_checkout::HELP, rad_checkout::HELP,
rad_clone::HELP, rad_clone::HELP,
rad_edit::HELP, rad_edit::HELP,
rad_fetch::HELP,
rad_fork::HELP, rad_fork::HELP,
rad_help::HELP, rad_help::HELP,
rad_id::HELP, rad_id::HELP,

View File

@ -23,7 +23,7 @@ use radicle::cob::patch;
use radicle::cob::patch::PatchId; use radicle::cob::patch::PatchId;
use radicle::{prelude::*, Node}; use radicle::{prelude::*, Node};
use crate::commands::rad_fetch as fetch; use crate::commands::rad_sync as sync;
use crate::git::Rev; use crate::git::Rev;
use crate::terminal as term; use crate::terminal as term;
use crate::terminal::args::{string, Args, Error, Help}; use crate::terminal::args::{string, Args, Error, Help};
@ -259,7 +259,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let repository = profile.storage.repository(id)?; let repository = profile.storage.repository(id)?;
if options.fetch { if options.fetch {
fetch::fetch(repository.id(), &mut Node::new(profile.socket()))?; sync::fetch_all(repository.id(), &mut Node::new(profile.socket()))?;
} }
match options.op { match options.op {

View File

@ -1,5 +1,4 @@
use radicle::node::tracking::store::Config; use radicle::{git::Url, prelude::Id, Profile};
use radicle::{git::Url, node::TRACKING_DB_FILE, prelude::Id, Profile};
use radicle_crypto::PublicKey; use radicle_crypto::PublicKey;
use crate::git::add_remote; use crate::git::add_remote;
@ -14,7 +13,10 @@ pub fn run(
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let name = match name { let name = match name {
Some(name) => name, Some(name) => name,
_ => get_remote_name(profile, pubkey)? _ => profile
.tracking()?
.node_policy(pubkey)?
.and_then(|node| node.alias)
.ok_or(anyhow::anyhow!("a `name` needs to be specified"))?, .ok_or(anyhow::anyhow!("a `name` needs to be specified"))?,
}; };
if git::is_remote(repository, &name)? { if git::is_remote(repository, &name)? {
@ -24,12 +26,6 @@ pub fn run(
let url = Url::from(id).with_namespace(*pubkey); let url = Url::from(id).with_namespace(*pubkey);
let remote = add_remote(repository, &name, &url)?; let remote = add_remote(repository, &name, &url)?;
term::success!("Remote {} added with {url}", remote.name,); term::success!("Remote {} added with {url}", remote.name,);
Ok(()) Ok(())
} }
/// Get the `git remote` name from the command `Options` and `pubkey`.
fn get_remote_name(profile: &Profile, pubkey: &PublicKey) -> anyhow::Result<Option<String>> {
let path = profile.home.node().join(TRACKING_DB_FILE);
let storage = Config::reader(path)?;
Ok(storage.node_policy(pubkey)?.and_then(|node| node.alias))
}

View File

@ -5,9 +5,9 @@ use std::{io, time};
use anyhow::{anyhow, Context as _}; use anyhow::{anyhow, Context as _};
use radicle::node::Event; use radicle::node;
use radicle::node::Handle as _; use radicle::node::{Event, FetchResult, FetchResults, Handle as _, Node};
use radicle::prelude::{Id, NodeId}; use radicle::prelude::{Id, NodeId, Profile};
use crate::terminal as term; use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help}; use crate::terminal::args::{Args, Error, Help};
@ -20,11 +20,18 @@ pub const HELP: Help = Help {
Usage Usage
rad sync [<rid>] [<option>...] rad sync [<rid>] [<option>...]
rad sync [<rid>] [--fetch] [--seed <nid>] [<option>...]
By default, the current repository is synced. By default, the current repository is synced.
When `--fetch` is specified, this command will fetch from
all connected seeds. To instead specify a seed, use the
`--seed <nid>` option in combination with `--fetch`.
Options Options
--fetch, -f Fetch from seeds instead of having seeds fetch from us
--seed <nid> Seed to fetch from (use with `--fetch`)
--timeout <secs> How many seconds to wait while syncing --timeout <secs> How many seconds to wait while syncing
--verbose, -v Verbose output --verbose, -v Verbose output
--help Print help --help Print help
@ -32,11 +39,20 @@ Options
"#, "#,
}; };
#[derive(Default, Debug)]
pub enum SyncMode {
Fetch,
#[default]
Announce,
}
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Options { pub struct Options {
pub rid: Option<Id>, pub rid: Option<Id>,
pub seed: Option<NodeId>,
pub verbose: bool, pub verbose: bool,
pub timeout: time::Duration, pub timeout: time::Duration,
pub mode: SyncMode,
} }
impl Args for Options { impl Args for Options {
@ -47,12 +63,22 @@ impl Args for Options {
let mut verbose = false; let mut verbose = false;
let mut timeout = time::Duration::from_secs(9); let mut timeout = time::Duration::from_secs(9);
let mut rid = None; let mut rid = None;
let mut seed = None;
let mut mode = SyncMode::default();
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
Long("verbose") | Short('v') => { Long("verbose") | Short('v') => {
verbose = true; verbose = true;
} }
Long("seed") if matches!(mode, SyncMode::Fetch) => {
let val = parser.value()?;
let val = term::args::nid(&val)?;
seed = Some(val);
}
Long("fetch") | Short('f') => {
mode = SyncMode::Fetch;
}
Long("timeout") | Short('t') => { Long("timeout") | Short('t') => {
let value = parser.value()?; let value = parser.value()?;
let secs = term::args::parse_value("timeout", value)?; let secs = term::args::parse_value("timeout", value)?;
@ -76,6 +102,8 @@ impl Args for Options {
rid, rid,
verbose, verbose,
timeout, timeout,
seed,
mode,
}, },
vec![], vec![],
)) ))
@ -95,7 +123,15 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
}; };
let mut node = radicle::Node::new(profile.socket()); let mut node = radicle::Node::new(profile.socket());
let events = node.subscribe(options.timeout)?;
match options.mode {
SyncMode::Announce => announce(rid, node, options.timeout),
SyncMode::Fetch => fetch(rid, profile, &mut node, options.seed),
}
}
fn announce(rid: Id, mut node: Node, timeout: time::Duration) -> anyhow::Result<()> {
let events = node.subscribe(timeout)?;
let seeds = node.seeds(rid)?; let seeds = node.seeds(rid)?;
let mut seeds = seeds.connected().collect::<BTreeSet<_>>(); let mut seeds = seeds.connected().collect::<BTreeSet<_>>();
@ -144,3 +180,64 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
} }
Ok(()) Ok(())
} }
pub fn fetch(
rid: Id,
profile: Profile,
node: &mut Node,
seed: Option<NodeId>,
) -> anyhow::Result<()> {
if !profile.tracking()?.is_repo_tracked(&rid)? {
anyhow::bail!("repository {rid} is not tracked");
}
let results = if let Some(seed) = seed {
let result = fetch_from(rid, &seed, node)?;
FetchResults::from(vec![(seed, result)])
} else {
fetch_all(rid, node)?
};
let success = results.success().count();
let failed = results.failed().count();
if success == 0 {
term::error(format!("Failed to fetch repository from {failed} seed(s)"));
} else {
term::success!("Fetched repository from {success} seed(s)");
}
Ok(())
}
pub fn fetch_all(rid: Id, node: &mut Node) -> Result<FetchResults, node::Error> {
// Get seeds. This consults the local routing table only.
let seeds = node.seeds(rid)?;
let mut results = FetchResults::default();
if seeds.has_connections() {
// Fetch from all seeds.
for seed in seeds.connected() {
let result = fetch_from(rid, seed, node)?;
results.push(*seed, result);
}
}
Ok(results)
}
pub fn fetch_from(rid: Id, seed: &NodeId, node: &mut Node) -> Result<FetchResult, node::Error> {
let spinner = term::spinner(format!(
"Fetching {} from {}..",
term::format::tertiary(rid),
term::format::tertiary(term::format::node(seed))
));
let result = node.fetch(rid, *seed)?;
match &result {
FetchResult::Success { .. } => {
spinner.finish();
}
FetchResult::Failed { reason } => {
spinner.error(reason);
}
}
Ok(result)
}

View File

@ -163,14 +163,6 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
args.to_vec(), args.to_vec(),
); );
} }
"fetch" => {
term::run_command_args::<rad_fetch::Options, _>(
rad_fetch::HELP,
"Fetch",
rad_fetch::run,
args.to_vec(),
);
}
"fork" => { "fork" => {
term::run_command_args::<rad_fork::Options, _>( term::run_command_args::<rad_fork::Options, _>(
rad_fork::HELP, rad_fork::HELP,

View File

@ -18,7 +18,7 @@ use thiserror::Error;
use crate::crypto::ssh::agent::Agent; use crate::crypto::ssh::agent::Agent;
use crate::crypto::ssh::{keystore, Keystore, Passphrase}; use crate::crypto::ssh::{keystore, Keystore, Passphrase};
use crate::crypto::{PublicKey, Signer}; use crate::crypto::{PublicKey, Signer};
use crate::node; use crate::node::{self, tracking};
use crate::prelude::Did; use crate::prelude::Did;
use crate::storage::git::transport; use crate::storage::git::transport;
use crate::storage::git::Storage; use crate::storage::git::Storage;
@ -127,6 +127,14 @@ impl Profile {
} }
} }
/// Return a read-only handle to the tracking configuration of the node.
pub fn tracking(&self) -> Result<tracking::store::Config, tracking::store::Error> {
let path = self.home.node().join(node::TRACKING_DB_FILE);
let config = tracking::store::Config::reader(path)?;
Ok(config)
}
/// Return the path to the keys folder. /// Return the path to the keys folder.
pub fn keys(&self) -> PathBuf { pub fn keys(&self) -> PathBuf {
self.home.keys() self.home.keys()