Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Remove `rad fetch` command
Alexis Sellier committed 3 years ago
commit d6cebf613f7be0d5d06fa55e10301e46a2bf1705
parent a28594316612733b536d8d0146e9fd3d47be6276
11 files changed +122 -175
modified radicle-cli/examples/rad-fetch.md
@@ -18,7 +18,7 @@ Now that the project is tracked we can fetch it and we will have it in
our local storage.

```
-
$ rad fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+
$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
```
modified radicle-cli/examples/workflow/5-patching-maintainer.md
@@ -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
✓ Tracking policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
! Warning: fetch after track is not yet supported
-
$ rad fetch
+
$ rad sync --fetch
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk..
✓ Fetched repository from 1 seed(s)
```
modified radicle-cli/src/commands.rs
@@ -12,8 +12,6 @@ pub mod rad_comment;
pub mod rad_delegate;
#[path = "commands/edit.rs"]
pub mod rad_edit;
-
#[path = "commands/fetch.rs"]
-
pub mod rad_fetch;
#[path = "commands/fork.rs"]
pub mod rad_fork;
#[path = "commands/help.rs"]
modified radicle-cli/src/commands/clone.rs
@@ -18,7 +18,7 @@ use radicle::storage;
use radicle::storage::git::Storage;

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::terminal as term;
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 {
        // If we don't have the project locally, even after attempting to fetch,
        // there's nothing we can do.
deleted radicle-cli/src/commands/fetch.rs
@@ -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)
-
}
modified radicle-cli/src/commands/help.rs
@@ -18,7 +18,6 @@ const COMMANDS: &[Help] = &[
    rad_checkout::HELP,
    rad_clone::HELP,
    rad_edit::HELP,
-
    rad_fetch::HELP,
    rad_fork::HELP,
    rad_help::HELP,
    rad_id::HELP,
modified radicle-cli/src/commands/patch.rs
@@ -23,7 +23,7 @@ use radicle::cob::patch;
use radicle::cob::patch::PatchId;
use radicle::{prelude::*, Node};

-
use crate::commands::rad_fetch as fetch;
+
use crate::commands::rad_sync as sync;
use crate::git::Rev;
use crate::terminal as term;
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)?;

    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 {
modified radicle-cli/src/commands/remote/add.rs
@@ -1,5 +1,4 @@
-
use radicle::node::tracking::store::Config;
-
use radicle::{git::Url, node::TRACKING_DB_FILE, prelude::Id, Profile};
+
use radicle::{git::Url, prelude::Id, Profile};
use radicle_crypto::PublicKey;

use crate::git::add_remote;
@@ -14,7 +13,10 @@ pub fn run(
) -> anyhow::Result<()> {
    let name = match 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"))?,
    };
    if git::is_remote(repository, &name)? {
@@ -24,12 +26,6 @@ pub fn run(
    let url = Url::from(id).with_namespace(*pubkey);
    let remote = add_remote(repository, &name, &url)?;
    term::success!("Remote {} added with {url}", remote.name,);
-
    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))
+
    Ok(())
}
modified radicle-cli/src/commands/sync.rs
@@ -5,9 +5,9 @@ use std::{io, time};

use anyhow::{anyhow, Context as _};

-
use radicle::node::Event;
-
use radicle::node::Handle as _;
-
use radicle::prelude::{Id, NodeId};
+
use radicle::node;
+
use radicle::node::{Event, FetchResult, FetchResults, Handle as _, Node};
+
use radicle::prelude::{Id, NodeId, Profile};

use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
@@ -20,11 +20,18 @@ pub const HELP: Help = Help {
Usage

    rad sync [<rid>] [<option>...]
+
    rad sync [<rid>] [--fetch] [--seed <nid>] [<option>...]

    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

+
    --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
    --verbose, -v       Verbose output
    --help              Print help
@@ -33,10 +40,19 @@ Options
};

#[derive(Default, Debug)]
+
pub enum SyncMode {
+
    Fetch,
+
    #[default]
+
    Announce,
+
}
+

+
#[derive(Default, Debug)]
pub struct Options {
    pub rid: Option<Id>,
+
    pub seed: Option<NodeId>,
    pub verbose: bool,
    pub timeout: time::Duration,
+
    pub mode: SyncMode,
}

impl Args for Options {
@@ -47,12 +63,22 @@ impl Args for Options {
        let mut verbose = false;
        let mut timeout = time::Duration::from_secs(9);
        let mut rid = None;
+
        let mut seed = None;
+
        let mut mode = SyncMode::default();

        while let Some(arg) = parser.next()? {
            match arg {
                Long("verbose") | Short('v') => {
                    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') => {
                    let value = parser.value()?;
                    let secs = term::args::parse_value("timeout", value)?;
@@ -76,6 +102,8 @@ impl Args for Options {
                rid,
                verbose,
                timeout,
+
                seed,
+
                mode,
            },
            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 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 mut seeds = seeds.connected().collect::<BTreeSet<_>>();

@@ -144,3 +180,64 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    }
    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)
+
}
modified radicle-cli/src/main.rs
@@ -163,14 +163,6 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
                args.to_vec(),
            );
        }
-
        "fetch" => {
-
            term::run_command_args::<rad_fetch::Options, _>(
-
                rad_fetch::HELP,
-
                "Fetch",
-
                rad_fetch::run,
-
                args.to_vec(),
-
            );
-
        }
        "fork" => {
            term::run_command_args::<rad_fork::Options, _>(
                rad_fork::HELP,
modified radicle/src/profile.rs
@@ -18,7 +18,7 @@ use thiserror::Error;
use crate::crypto::ssh::agent::Agent;
use crate::crypto::ssh::{keystore, Keystore, Passphrase};
use crate::crypto::{PublicKey, Signer};
-
use crate::node;
+
use crate::node::{self, tracking};
use crate::prelude::Did;
use crate::storage::git::transport;
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.
    pub fn keys(&self) -> PathBuf {
        self.home.keys()