Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: list tracked and untracked remotes
Fintan Halpenny committed 2 years ago
commit 3cf51043b3c4f253334f175d7d14f8183ca7e05e
parent e3b3f3c4f4fd1f8438f37dc1742a7ad9a4c4652a
4 files changed +224 -29
modified radicle-cli/examples/rad-remote.md
@@ -51,3 +51,44 @@ $ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
✓ Remote bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk added
✓ Remote-tracking branch bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk/master created for z6Mkt67…v4N1tRk
```
+

+
We can also use `rad remote` to list all the remotes that are
+
available in the repository by using the `--untracked` flag:
+

+
```
+
$ rad remote --untracked
+
eve did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
+
```
+

+
If we use `--all`, then we can see all the remotes that we have
+
created in the working copy, followed by all the available remotes:
+

+
```
+
$ rad remote --all
+
bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (fetch)
+
rad                                                  (canonical upstream)                             (fetch)
+
rad                                                  z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
+

+
eve did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
+
```
+

+
As we can see, we have also have another remote namespace `eve`, so
+
let's add them to our set of working copy remotes:
+

+
```
+
$ rad remote add did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --name eve
+
✓ Remote eve added
+
✓ Remote-tracking branch eve/master created for z6Mkux1…nVhib7Z
+
```
+

+
After adding `eve`'s remote, we no longer see any entries that are
+
untracked:
+

+
```
+
$ rad remote --all
+
bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (fetch)
+
eve                                                  z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z (fetch)
+
rad                                                  (canonical upstream)                             (fetch)
+
rad                                                  z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
+
```
+

modified radicle-cli/src/commands/remote.rs
@@ -14,6 +14,7 @@ use radicle::git::RefString;
use radicle::prelude::NodeId;
use radicle::storage::ReadStorage;

+
use crate::terminal as term;
use crate::terminal::args;
use crate::terminal::{Args, Context, Help};

@@ -24,19 +25,28 @@ pub const HELP: Help = Help {
    usage: r#"
Usage

-
    rad remote
-
    rad remote list
-
    rad remote add (<did> | <nid>) [--name <string>]
-
    rad remote rm <name>
+
    rad remote [<option>...]
+
    rad remote list [--tracked | --untracked | --all] [<option>...]
+
    rad remote add (<did> | <nid>) [--name <string>] [<option>...]
+
    rad remote rm <name> [<option>...]
+

+
List options
+

+
    --tracked     Show all remotes that are listed in the working copy
+
    --untracked   Show all remotes that are listed in the Radicle storage
+
    --all         Show all remotes in both the Radicle storage and the working copy
+

+
Add options
+

+
    --name        Override the name of the remote that by default is set to the node alias

Options

-
    --name      Override the name of the remote that by default is set to the node alias
-
    --help      Print help
+
    --help        Print help
"#,
};

-
#[derive(Debug, Default, PartialEq, Eq)]
+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum OperationName {
    Add,
    Rm,
@@ -48,7 +58,15 @@ pub enum OperationName {
pub enum Operation {
    Add { id: NodeId, name: Option<RefString> },
    Rm { name: RefString },
-
    List,
+
    List { option: ListOption },
+
}
+

+
#[derive(Debug, Default)]
+
pub enum ListOption {
+
    All,
+
    #[default]
+
    Tracked,
+
    Untracked,
}

#[derive(Debug)]
@@ -64,6 +82,7 @@ impl Args for Options {
        let mut op: Option<OperationName> = None;
        let mut id: Option<NodeId> = None;
        let mut name: Option<RefString> = None;
+
        let mut list_op: ListOption = ListOption::default();

        while let Some(arg) = parser.next()? {
            match arg {
@@ -82,10 +101,25 @@ impl Args for Options {
                    "r" | "rm" => op = Some(OperationName::Rm),
                    unknown => anyhow::bail!("unknown operation '{}'", unknown),
                },
+

+
                // List options
+
                Long("all") if op.unwrap_or_default() == OperationName::List => {
+
                    list_op = ListOption::All;
+
                }
+
                Long("tracked") if op.unwrap_or_default() == OperationName::List => {
+
                    list_op = ListOption::Tracked;
+
                }
+
                Long("untracked") if op.unwrap_or_default() == OperationName::List => {
+
                    list_op = ListOption::Untracked;
+
                }
+

+
                // Add options
                Value(val) if op == Some(OperationName::Add) && id.is_none() => {
                    let nid = args::pubkey(&val)?;
                    id = Some(nid);
                }
+

+
                // Remove options
                Value(val) if op == Some(OperationName::Rm) && name.is_none() => {
                    let val = args::string(&val);
                    let val = RefString::try_from(val)
@@ -104,7 +138,7 @@ impl Args for Options {
                ))?,
                name,
            },
-
            OperationName::List => Operation::List,
+
            OperationName::List => Operation::List { option: list_op },
            OperationName::Rm => Operation::Rm {
                name: name.ok_or(anyhow!("name required, see `rad remote`"))?,
            },
@@ -127,7 +161,29 @@ pub fn run(options: Options, ctx: impl Context) -> anyhow::Result<()> {
            self::add::run(rid, id, name, Some(branch.clone()), &profile, &working)?
        }
        Operation::Rm { ref name } => self::rm::run(name, &working)?,
-
        Operation::List => self::list::run(&working)?,
+
        Operation::List { option } => match option {
+
            ListOption::All => {
+
                let tracked = list::tracked(&working)?;
+
                let untracked = list::untracked(rid, &profile, tracked.iter())?;
+
                // Only include a blank line if we're printing both tracked and untracked
+
                let include_blank_line = !tracked.is_empty() && !untracked.is_empty();
+

+
                list::print_tracked(tracked.iter());
+
                if include_blank_line {
+
                    term::blank();
+
                }
+
                list::print_untracked(untracked.iter());
+
            }
+
            ListOption::Tracked => {
+
                let tracked = list::tracked(&working)?;
+
                list::print_tracked(tracked.iter());
+
            }
+
            ListOption::Untracked => {
+
                let tracked = list::tracked(&working)?;
+
                let untracked = list::untracked(rid, &profile, tracked.iter())?;
+
                list::print_untracked(untracked.iter());
+
            }
+
        },
    };
    Ok(())
}
modified radicle-cli/src/commands/remote/list.rs
@@ -1,29 +1,119 @@
+
use std::collections::HashSet;
+

+
use radicle::git::Url;
+
use radicle::identity::{Did, Id};
+
use radicle::node::{Alias, AliasStore as _, NodeId};
+
use radicle::storage::ReadStorage as _;
+
use radicle::Profile;
use radicle_term::{Element, Table};

use crate::git;
use crate::terminal as term;

-
pub fn run(repo: &git::Repository) -> anyhow::Result<()> {
-
    let mut table = Table::default();
-
    let remotes = git::rad_remotes(repo)?;
-
    for r in remotes {
-
        for (dir, url) in [("fetch", Some(r.url)), ("push", r.pushurl)] {
-
            let Some(url) = url else {
-
                continue;
-
            };
-

-
            let description = url.namespace.map_or(
-
                term::format::dim("(canonical upstream)".to_string()).italic(),
-
                |namespace| term::format::tertiary(namespace.to_string()),
-
            );
-
            table.push([
-
                term::format::bold(r.name.clone()),
-
                description,
-
                term::format::parens(term::format::secondary(dir.to_owned())),
-
            ]);
+
#[derive(Debug)]
+
pub enum Direction {
+
    Push(Url),
+
    Fetch(Url),
+
}
+

+
#[derive(Debug)]
+
pub struct Tracked {
+
    name: String,
+
    direction: Option<Direction>,
+
}
+

+
impl Tracked {
+
    fn namespace(&self) -> Option<NodeId> {
+
        match self.direction.as_ref()? {
+
            Direction::Push(url) => url.namespace,
+
            Direction::Fetch(url) => url.namespace,
        }
    }
+
}
+

+
#[derive(Debug)]
+
pub struct Untracked {
+
    remote: NodeId,
+
    alias: Option<Alias>,
+
}
+

+
pub fn tracked(working: &git::Repository) -> anyhow::Result<Vec<Tracked>> {
+
    Ok(git::rad_remotes(working)?
+
        .into_iter()
+
        .flat_map(|remote| {
+
            [
+
                Tracked {
+
                    name: remote.name.clone(),
+
                    direction: Some(Direction::Fetch(remote.url)),
+
                },
+
                Tracked {
+
                    name: remote.name,
+
                    direction: remote.pushurl.map(Direction::Push),
+
                },
+
            ]
+
        })
+
        .collect())
+
}
+

+
pub fn untracked<'a>(
+
    rid: Id,
+
    profile: &Profile,
+
    tracked: impl Iterator<Item = &'a Tracked>,
+
) -> anyhow::Result<Vec<Untracked>> {
+
    let repo = profile.storage.repository(rid)?;
+
    let aliases = profile.aliases();
+
    let remotes = repo.remote_ids()?;
+
    let git_remotes = tracked
+
        .filter_map(|tracked| tracked.namespace())
+
        .collect::<HashSet<_>>();
+
    Ok(remotes
+
        .filter_map(|remote| {
+
            remote
+
                .map(|remote| {
+
                    (!git_remotes.contains(&remote)).then_some(Untracked {
+
                        remote,
+
                        alias: aliases.alias(&remote),
+
                    })
+
                })
+
                .transpose()
+
        })
+
        .collect::<Result<Vec<_>, _>>()?)
+
}
+

+
pub fn print_tracked<'a>(tracked: impl Iterator<Item = &'a Tracked>) {
+
    let mut table = Table::default();
+
    for Tracked { direction, name } in tracked {
+
        let Some(direction) = direction else {
+
            continue;
+
        };
+

+
        let (dir, url) = match direction {
+
            Direction::Push(url) => ("push", url),
+
            Direction::Fetch(url) => ("fetch", url),
+
        };
+
        let description = url.namespace.map_or(
+
            term::format::dim("(canonical upstream)".to_string()).italic(),
+
            |namespace| term::format::tertiary(namespace.to_string()),
+
        );
+
        table.push([
+
            term::format::bold(name.clone()),
+
            description,
+
            term::format::parens(term::format::secondary(dir.to_owned())),
+
        ]);
+
    }
    table.print();
+
}

-
    Ok(())
+
pub fn print_untracked<'a>(untracked: impl Iterator<Item = &'a Untracked>) {
+
    let mut t = Table::default();
+
    for Untracked { remote, alias } in untracked {
+
        t.push([
+
            match alias {
+
                None => term::format::secondary("n/a".to_string()),
+
                Some(alias) => term::format::secondary(alias.to_string()),
+
            },
+
            term::format::highlight(Did::from(remote).to_string()),
+
        ])
+
    }
+
    t.print();
}
modified radicle-cli/tests/commands.rs
@@ -1475,6 +1475,7 @@ fn rad_remote() {
    let mut environment = Environment::new();
    let alice = environment.node(Config::test(Alias::new("alice")));
    let bob = environment.node(Config::test(Alias::new("bob")));
+
    let eve = environment.node(Config::test(Alias::new("eve")));
    let working = environment.tmp().join("working");
    let home = alice.home.clone();
    let rid = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
@@ -1491,6 +1492,7 @@ fn rad_remote() {

    let mut alice = alice.spawn();
    let mut bob = bob.spawn();
+
    let mut eve = eve.spawn();
    alice
        .handle
        .follow(bob.id, Some(Alias::new("bob")))
@@ -1502,6 +1504,12 @@ fn rad_remote() {
    bob.announce(rid, bob.home.path()).unwrap();
    alice.has_inventory_of(&rid, &bob.id);

+
    eve.connect(&bob);
+
    eve.routes_to(&[(rid, alice.id)]);
+
    eve.fork(rid, eve.home.path()).unwrap();
+
    eve.announce(rid, eve.home.path()).unwrap();
+
    alice.has_inventory_of(&rid, &eve.id);
+

    test(
        "examples/rad-remote.md",
        working.join("alice"),