Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Update commands for private repos
cloudhead committed 2 years ago
commit 9a54c8da7bb9394fc42a8fe99eeef2916b4a0fce
parent 257b950d6f45b8f99db8c0463aec339899441d20
6 files changed +178 -54
modified radicle-cli/src/commands/id.rs
@@ -1,10 +1,11 @@
-
use std::{ffi::OsString, str::FromStr as _};
+
use std::io::IsTerminal;
+
use std::{ffi::OsString, io, str::FromStr as _};

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

use radicle::cob::identity::{self, Proposal, Proposals, Revision, RevisionId};
use radicle::git::Oid;
-
use radicle::identity::Identity;
+
use radicle::identity::{Identity, Visibility};
use radicle::prelude::{Did, Doc};
use radicle::storage::ReadStorage as _;
use radicle_crypto::Verified;
@@ -24,14 +25,16 @@ Usage

    rad id (update|edit) [--title|-t] [--description|-d]
                         [--delegates <did>] [--threshold <num>]
-
                         [--no-confirm] [<option>...]
+
                         [--visibility <private | public>]
+
                         [--allow <did>] [--no-confirm] [<option>...]
    rad id list [<option>...]
    rad id rebase <id> [--rev <revision-id>] [<option>...]
    rad id show <id> [--rev <revision-id>] [--revisions] [<option>...]
-
    rad id (accept|reject|close|commit) [--rev <revision-id>] [--no-confirm] [<option>...]
+
    rad id (accept|reject|close|commit) <id> [--rev <revision-id>] [--no-confirm] [<option>...]

Options

+
    --quiet, -q            Don't print anything
    --help                 Print help
"#,
};
@@ -67,6 +70,7 @@ pub enum Operation {
        title: Option<String>,
        description: Option<String>,
        delegates: Vec<Did>,
+
        visibility: Option<Visibility>,
        threshold: Option<usize>,
    },
    Update {
@@ -114,6 +118,7 @@ pub enum OperationName {
pub struct Options {
    pub op: Operation,
    pub interactive: Interactive,
+
    pub quiet: bool,
}

impl Args for Options {
@@ -127,9 +132,11 @@ impl Args for Options {
        let mut title: Option<String> = None;
        let mut description: Option<String> = None;
        let mut delegates: Vec<Did> = Vec::new();
+
        let mut visibility: Option<Visibility> = None;
        let mut threshold: Option<usize> = None;
-
        let mut interactive = Interactive::Yes;
+
        let mut interactive = Interactive::from(io::stdout().is_terminal());
        let mut show_revisions = false;
+
        let mut quiet = false;

        while let Some(arg) = parser.next()? {
            match arg {
@@ -142,6 +149,9 @@ impl Args for Options {
                Long("description") if op == Some(OperationName::Edit) => {
                    description = Some(parser.value()?.to_string_lossy().into());
                }
+
                Long("quiet") | Short('q') => {
+
                    quiet = true;
+
                }
                Long("no-confirm") => {
                    interactive = Interactive::No;
                }
@@ -169,6 +179,21 @@ impl Args for Options {
                    let did = term::args::did(&parser.value()?)?;
                    delegates.push(did);
                }
+
                Long("allow") => {
+
                    let value = parser.value()?;
+
                    let did = term::args::did(&value)?;
+
                    if let Some(Visibility::Private { allow }) = &mut visibility {
+
                        allow.insert(did);
+
                    } else {
+
                        visibility = Some(Visibility::private([did]));
+
                    }
+
                }
+
                Long("visibility") => {
+
                    let value = parser.value()?;
+
                    let value = term::args::parse_value("visibility", value)?;
+

+
                    visibility = Some(value);
+
                }
                Long("threshold") => {
                    threshold = Some(parser.value()?.to_string_lossy().parse()?);
                }
@@ -198,6 +223,7 @@ impl Args for Options {
                title,
                description,
                delegates,
+
                visibility,
                threshold,
            },
            OperationName::Update => Operation::Update {
@@ -226,7 +252,14 @@ impl Args for Options {
                id: id.ok_or_else(|| anyhow!("a proposal must be provided"))?,
            },
        };
-
        Ok((Options { op, interactive }, vec![]))
+
        Ok((
+
            Options {
+
                op,
+
                interactive,
+
                quiet,
+
            },
+
            vec![],
+
        ))
    }
}

@@ -247,11 +280,15 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
            let (rid, revision) = select(&proposal, rev, &previous, interactive)?;
            warn_out_of_date(revision, &previous);
            let yes = confirm(interactive, "Are you sure you want to accept?");
+

            if yes {
                let (_, signature) = revision.proposed.sign(&signer)?;
                proposal.accept(rid, signature, &signer)?;
-
                term::success!("Accepted proposal ✓");
-
                print(&proposal, &previous, None)?;
+

+
                if !options.quiet {
+
                    term::success!("Accepted proposal ✓");
+
                    print(&proposal, &previous, None)?;
+
                }
            }
        }
        Operation::Reject { id, rev } => {
@@ -260,22 +297,28 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
            let (rid, revision) = select(&proposal, rev, &previous, interactive)?;
            warn_out_of_date(revision, &previous);
            let yes = confirm(interactive, "Are you sure you want to reject?");
+

            if yes {
                proposal.reject(rid, &signer)?;
-
                term::success!("Rejected proposal 👎");
-
                print(&proposal, &previous, None)?;
+

+
                if !options.quiet {
+
                    term::success!("Rejected proposal 👎");
+
                    print(&proposal, &previous, None)?;
+
                }
            }
        }
        Operation::Edit {
            title,
            description,
            delegates,
+
            visibility,
            threshold,
        } => {
            let proposed = {
                let mut proposed = previous.doc.clone();
                proposed.threshold = threshold.unwrap_or(proposed.threshold);
                proposed.delegates.extend(delegates);
+
                proposed.visibility = visibility.unwrap_or(proposed.visibility);
                proposed
            };

@@ -296,11 +339,15 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
                create.proposed,
                &signer,
            )?;
-
            term::success!(
-
                "Identity proposal '{}' created",
-
                term::format::highlight(proposal.id)
-
            );
-
            print(&proposal, &previous, None)?;
+
            if options.quiet {
+
                term::print(proposal.id);
+
            } else {
+
                term::success!(
+
                    "Identity proposal '{}' created",
+
                    term::format::highlight(proposal.id)
+
                );
+
                print(&proposal, &previous, None)?;
+
            }
        }
        Operation::Update {
            id,
@@ -342,15 +389,20 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
            if yes {
                proposal.edit(update.title, update.description, &signer)?;
                let revision = proposal.update(previous.current, update.proposed, &signer)?;
-
                term::success!(
-
                    "Identity proposal '{}' updated",
-
                    term::format::highlight(proposal.id)
-
                );
-
                term::success!(
-
                    "Revision '{}'",
-
                    term::format::highlight(revision.to_string())
-
                );
-
                print(&proposal, &previous, None)?;
+

+
                if options.quiet {
+
                    term::print(revision.to_string());
+
                } else {
+
                    term::success!(
+
                        "Identity proposal '{}' updated",
+
                        term::format::highlight(proposal.id)
+
                    );
+
                    term::success!(
+
                        "Revision '{}'",
+
                        term::format::highlight(revision.to_string())
+
                    );
+
                    print(&proposal, &previous, None)?;
+
                }
            }
        }
        Operation::Rebase { id, rev } => {
@@ -362,15 +414,20 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
            if yes {
                let revision =
                    proposal.update(previous.current, revision.proposed.clone(), &signer)?;
-
                term::success!(
-
                    "Identity proposal '{}' rebased",
-
                    term::format::highlight(proposal.id)
-
                );
-
                term::success!(
-
                    "Revision '{}'",
-
                    term::format::highlight(revision.to_string())
-
                );
-
                print(&proposal, &previous, None)?;
+

+
                if options.quiet {
+
                    term::print(revision.to_string());
+
                } else {
+
                    term::success!(
+
                        "Identity proposal '{}' rebased",
+
                        term::format::highlight(proposal.id)
+
                    );
+
                    term::success!(
+
                        "Revision '{}'",
+
                        term::format::highlight(revision.to_string())
+
                    );
+
                    print(&proposal, &previous, None)?;
+
                }
            }
        }
        Operation::List => {
@@ -413,21 +470,31 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
            let (rid, revision) = commit_select(&proposal, rev, &previous, interactive)?;
            warn_out_of_date(revision, &previous);
            let yes = confirm(interactive, "Are you sure you want to commit?");
+

            if yes {
                let id = Proposal::commit(&proposal, &rid, signer.public_key(), &repo, &signer)?;
                proposal.commit(&signer)?;
-
                term::success!("Committed new identity '{}'", id.current);
-
                print(&proposal, &previous, None)?;
+

+
                if options.quiet {
+
                    term::print(id.current);
+
                } else {
+
                    term::success!("Committed new identity '{}'", id.current);
+
                    print(&proposal, &previous, None)?;
+
                }
            }
        }
        Operation::Close { id } => {
            let id = id.resolve(&repo.backend)?;
            let mut proposal = proposals.get_mut(&id)?;
            let yes = confirm(interactive, "Are you sure you want to close?");
+

            if yes {
                proposal.close(&signer)?;
-
                term::success!("Closed identity proposal '{}'", id);
-
                print(&proposal, &previous, None)?;
+

+
                if !options.quiet {
+
                    term::success!("Closed identity proposal '{}'", id);
+
                    print(&proposal, &previous, None)?;
+
                }
            }
        }
        Operation::Show {
modified radicle-cli/src/commands/init.rs
@@ -182,7 +182,12 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
    let interactive = options.interactive;

    term::headline(format!(
-
        "Initializing radicle 👾 project in {}",
+
        "Initializing{}radicle 👾 project in {}",
+
        if !options.visibility.is_public() {
+
            term::format::yellow(" private ")
+
        } else {
+
            term::format::default(" ")
+
        },
        if path == cwd {
            term::format::highlight(".").to_string()
        } else {
modified radicle-cli/src/commands/inspect.rs
@@ -37,6 +37,7 @@ Options
    --id        Return the repository identifier (RID)
    --payload   Inspect the repository's identity payload
    --refs      Inspect the repository's refs on the local device
+
    --identity  Inspect the identity document
    --delegates Inspect the repository's delegates
    --policy    Inspect the repository's tracking policy
    --history   Show the history of the repository identity document
@@ -49,6 +50,7 @@ pub enum Target {
    Refs,
    Payload,
    Delegates,
+
    Identity,
    Policy,
    History,
    #[default]
@@ -89,6 +91,9 @@ impl Args for Options {
                Long("history") => {
                    target = Target::History;
                }
+
                Long("identity") => {
+
                    target = Target::Identity;
+
                }
                Long("id") => {
                    target = Target::Id;
                }
@@ -147,6 +152,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
                colorizer().colorize_json_str(&serde_json::to_string_pretty(&project.payload)?)?
            );
        }
+
        Target::Identity => {
+
            println!(
+
                "{}",
+
                colorizer().colorize_json_str(&serde_json::to_string_pretty(&project.doc)?)?
+
            );
+
        }
        Target::Policy => {
            let tracking = profile.tracking()?;
            if let Some(repo) = tracking.repo_policy(&id)? {
modified radicle-cli/src/commands/ls.rs
@@ -16,6 +16,8 @@ Usage

Options

+
    --private       Show only private repositories
+
    --public        Show only public repositories
    --verbose, -v   Verbose output
    --help          Print help
"#,
@@ -23,6 +25,8 @@ Options

pub struct Options {
    verbose: bool,
+
    public: bool,
+
    private: bool,
}

impl Args for Options {
@@ -31,18 +35,33 @@ impl Args for Options {

        let mut parser = lexopt::Parser::from_args(args);
        let mut verbose = false;
+
        let mut private = false;
+
        let mut public = false;

-
        if let Some(arg) = parser.next()? {
+
        while let Some(arg) = parser.next()? {
            match arg {
                Long("help") | Short('h') => {
                    return Err(Error::Help.into());
                }
+
                Long("private") => {
+
                    private = true;
+
                }
+
                Long("public") => {
+
                    public = true;
+
                }
                Long("verbose") | Short('v') => verbose = true,
                _ => return Err(anyhow::anyhow!(arg.unexpected())),
            }
        }

-
        Ok((Options { verbose }, vec![]))
+
        Ok((
+
            Options {
+
                verbose,
+
                private,
+
                public,
+
            },
+
            vec![],
+
        ))
    }
}

@@ -52,6 +71,13 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let mut table = term::Table::default();

    for (id, head, doc) in storage.repositories()? {
+
        if doc.visibility.is_public() && options.private && !options.public {
+
            continue;
+
        }
+
        if !doc.visibility.is_public() && !options.private && options.public {
+
            continue;
+
        }
+

        let proj = match doc.verified()?.project() {
            Ok(proj) => proj,
            Err(err) => {
modified radicle/src/identity/doc.rs
@@ -1,11 +1,12 @@
mod id;

-
use std::collections::{BTreeMap, HashMap};
+
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::fmt;
use std::fmt::Write as _;
use std::marker::PhantomData;
use std::ops::{Deref, Not};
use std::path::Path;
+
use std::str::FromStr;

use nonempty::NonEmpty;
use once_cell::sync::Lazy;
@@ -144,11 +145,27 @@ pub enum Visibility {
    Public,
    /// Delegates plus the allowed DIDs.
    Private {
-
        #[serde(default, skip_serializing_if = "Vec::is_empty")]
-
        allow: Vec<Did>,
+
        #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
+
        allow: BTreeSet<Did>,
    },
}

+
#[derive(Error, Debug)]
+
#[error("'{0}' is not a valid visibility type")]
+
pub struct VisibilityParseError(String);
+

+
impl FromStr for Visibility {
+
    type Err = VisibilityParseError;
+

+
    fn from_str(s: &str) -> Result<Self, Self::Err> {
+
        match s {
+
            "public" => Ok(Visibility::Public),
+
            "private" => Ok(Visibility::private([])),
+
            _ => Err(VisibilityParseError(s.to_owned())),
+
        }
+
    }
+
}
+

impl Visibility {
    /// Check whether the visibility is public.
    pub fn is_public(&self) -> bool {
@@ -156,9 +173,9 @@ impl Visibility {
    }

    /// Private visibility with list of allowed DIDs beyond the repository delegates.
-
    pub fn private(allow: impl Into<Vec<Did>>) -> Self {
+
    pub fn private(allow: impl IntoIterator<Item = Did>) -> Self {
        Self::Private {
-
            allow: allow.into(),
+
            allow: BTreeSet::from_iter(allow),
        }
    }
}
@@ -545,16 +562,14 @@ mod test {
            serde_json::json!({ "type": "public" })
        );
        assert_eq!(
-
            serde_json::to_value(Visibility::Private { allow: vec![] }).unwrap(),
+
            serde_json::to_value(Visibility::private([])).unwrap(),
            serde_json::json!({ "type": "private" })
        );
        assert_eq!(
-
            serde_json::to_value(Visibility::Private {
-
                allow: vec![Did::from_str(
-
                    "did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT"
-
                )
-
                .unwrap()]
-
            })
+
            serde_json::to_value(Visibility::private([Did::from_str(
+
                "did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT"
+
            )
+
            .unwrap()]))
            .unwrap(),
            serde_json::json!({ "type": "private", "allow": ["did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT"] })
        );
modified radicle/src/test/arbitrary.rs
@@ -1,4 +1,4 @@
-
use std::collections::{BTreeMap, HashSet};
+
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::hash::Hash;
use std::ops::RangeBounds;
use std::str::FromStr;
@@ -122,7 +122,7 @@ impl Arbitrary for Visibility {
            Visibility::Public
        } else {
            Visibility::Private {
-
                allow: Vec::arbitrary(g),
+
                allow: BTreeSet::arbitrary(g),
            }
        }
    }