Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Choose repo visibility on init
cloudhead committed 2 years ago
commit f2e74fdad2f1cfe0d85c642e40ed3a43710f06d6
parent d6d280d450419f4eed7fdfe634ac6e47b9834ca1
12 files changed +141 -101
modified radicle-cli/examples/rad-init-private.md
@@ -1,16 +1,18 @@
Alice initializes a *private* repo.

``` ~alice
-
$ rad init --name heartwood --description "radicle heartwood protocol & stack" --no-confirm --announce --private
+
$ rad init --name heartwood --description "radicle heartwood protocol & stack" --no-confirm --private

Initializing private radicle 👾 project in .

-
✓ Project heartwood created
-
✓ Syncing inventory..
-
✓ Announcing inventory..
+
✓ Project heartwood created.

Your project's Repository ID (RID) is rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu.
-
You can show it any time by running `rad .`
+
You can show it any time by running `rad .` from this directory.
+

+
You have created a private repository.
+
This repository will only be visible to you, and to peers you explicitly allow.
+
To push changes, run `git push`.
```

Bob tries to clone it, and even though he's connected to Alice, it fails.
modified radicle-cli/examples/rad-init-sync.md
@@ -3,14 +3,17 @@ To create your first radicle project, navigate to a git repository, and run
the `init` command:

```
-
$ rad init --name heartwood --description "Radicle Heartwood Protocol & Stack" --no-confirm --announce --scope trusted
+
$ rad init --name heartwood --description "Radicle Heartwood Protocol & Stack" --no-confirm --public --scope trusted

-
Initializing radicle 👾 project in .
+
Initializing public radicle 👾 project in .

-
✓ Project heartwood created
-
✓ Syncing inventory..
-
✓ Announcing inventory..
+
✓ Project heartwood created.

Your project's Repository ID (RID) is rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji.
-
You can show it any time by running `rad .`
+
You can show it any time by running `rad .` from this directory.
+

+
✓ Project successfully announced.
+

+
Your project has been announced to the network and is now discoverable by peers.
+
To push changes, run `git push`.
```
modified radicle-cli/examples/rad-init.md
@@ -3,11 +3,11 @@ To create your first radicle project, navigate to a git repository, and run the
`init` command.  Make sure you have [authenticated](../rad-auth.md) beforehand.

```
-
$ rad init --name heartwood --description "Radicle Heartwood Protocol & Stack" --no-confirm --no-track -v
+
$ rad init --name heartwood --description "Radicle Heartwood Protocol & Stack" --no-confirm --no-track --public -v

-
Initializing radicle 👾 project in .
+
Initializing public radicle 👾 project in .

-
✓ Project heartwood created
+
✓ Project heartwood created.
{
  "name": "heartwood",
  "description": "Radicle Heartwood Protocol & Stack",
@@ -15,9 +15,11 @@ Initializing radicle 👾 project in .
}

Your project's Repository ID (RID) is rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji.
-
You can show it any time by running `rad .`
+
You can show it any time by running `rad .` from this directory.

-
To publish your project to the network, run `git push`
+
Your project will be announced to the network when you start your node.
+
You can start your node with `rad node start`.
+
To push changes, run `git push`.
```

Projects can be listed with the `ls` command:
modified radicle-cli/examples/rad-patch-pull-update.md
@@ -3,16 +3,19 @@ Let's look at how patch updates work.
Alice creates a project and Bob clones it.

``` ~alice
-
$ rad init --name heartwood --description "radicle heartwood protocol & stack" --no-confirm --announce
+
$ rad init --name heartwood --description "radicle heartwood protocol & stack" --no-confirm --public

-
Initializing radicle 👾 project in .
+
Initializing public radicle 👾 project in .

-
✓ Project heartwood created
-
✓ Syncing inventory..
-
✓ Announcing inventory..
+
✓ Project heartwood created.

Your project's Repository ID (RID) is rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK.
-
You can show it any time by running `rad .`
+
You can show it any time by running `rad .` from this directory.
+

+
✓ Project successfully announced.
+

+
Your project has been announced to the network and is now discoverable by peers.
+
To push changes, run `git push`.
```

``` ~bob
modified radicle-cli/src/commands/auth.rs
@@ -84,7 +84,7 @@ pub fn init(options: Options) -> anyhow::Result<()> {

    if let Ok(version) = radicle::git::version() {
        if version < radicle::git::VERSION_REQUIRED {
-
            term::warning(&format!(
+
            term::warning(format!(
                "Your git version is unsupported, please upgrade to {} or later",
                radicle::git::VERSION_REQUIRED,
            ));
modified radicle-cli/src/commands/id.rs
@@ -522,7 +522,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
fn warn_out_of_date(revision: &Revision, previous: &Identity<Oid>) {
    if revision.current != previous.current {
        term::warning("Revision is out of date");
-
        term::warning(&format!("{} =/= {}", revision.current, previous.current));
+
        term::warning(format!("{} =/= {}", revision.current, previous.current));
        term::tip!("Consider using 'rad id rebase' to update the proposal to the latest identity");
    }
}
modified radicle-cli/src/commands/init.rs
@@ -1,18 +1,21 @@
#![allow(clippy::or_fun_call)]
+
#![allow(clippy::collapsible_else_if)]
use std::convert::TryFrom;
use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
+
use std::str::FromStr;

use anyhow::{anyhow, bail, Context as _};
use serde_json as json;

-
use radicle::crypto::ssh;
+
use radicle::crypto::{ssh, Verified};
use radicle::git::RefString;
use radicle::identity::Visibility;
use radicle::node::tracking::Scope;
use radicle::node::{Handle, NodeId};
-
use radicle::profile;
+
use radicle::prelude::Doc;
+
use radicle::{profile, Node};

use crate::git;
use crate::terminal as term;
@@ -34,10 +37,10 @@ Options
        --description <string>     Description of the project
        --default-branch <name>    The default branch of the project
        --scope <scope>            Tracking scope (default: all)
-
        --private                  Set repository visibility to *private* (default: public)
+
        --private                  Set repository visibility to *private*
+
        --public                   Set repository visibility to *public*
    -u, --set-upstream             Setup the upstream of the default branch
        --setup-signing            Setup the radicle key as a signing key for this repository
-
        --announce                 Announce the new project to the network
        --no-confirm               Don't ask for confirmation during setup
    -v, --verbose                  Verbose mode
        --help                     Print help
@@ -51,11 +54,10 @@ pub struct Options {
    pub description: Option<String>,
    pub branch: Option<String>,
    pub interactive: Interactive,
-
    pub visibility: Visibility,
+
    pub visibility: Option<Visibility>,
    pub setup_signing: bool,
    pub scope: Scope,
    pub set_upstream: bool,
-
    pub announce: bool,
    pub verbose: bool,
    pub track: bool,
}
@@ -73,11 +75,10 @@ impl Args for Options {
        let mut interactive = Interactive::Yes;
        let mut set_upstream = false;
        let mut setup_signing = false;
-
        let mut announce = false;
        let mut scope = Scope::All;
        let mut track = true;
        let mut verbose = false;
-
        let mut visibility = Visibility::default();
+
        let mut visibility = None;

        while let Some(arg) = parser.next()? {
            match arg {
@@ -124,9 +125,6 @@ impl Args for Options {
                Long("setup-signing") => {
                    setup_signing = true;
                }
-
                Long("announce") => {
-
                    announce = true;
-
                }
                Long("no-confirm") => {
                    interactive = Interactive::No;
                }
@@ -134,7 +132,10 @@ impl Args for Options {
                    track = false;
                }
                Long("private") => {
-
                    visibility = Visibility::private([]);
+
                    visibility = Some(Visibility::private([]));
+
                }
+
                Long("public") => {
+
                    visibility = Some(Visibility::Public);
                }
                Long("verbose") | Short('v') => {
                    verbose = true;
@@ -159,7 +160,6 @@ impl Args for Options {
                interactive,
                set_upstream,
                setup_signing,
-
                announce,
                track,
                visibility,
                verbose,
@@ -190,24 +190,30 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>

    term::headline(format!(
        "Initializing{}radicle 👾 project in {}",
-
        if !options.visibility.is_public() {
-
            term::format::yellow(" private ")
+
        if let Some(visibility) = &options.visibility {
+
            if !visibility.is_public() {
+
                term::format::yellow(" private ")
+
            } else {
+
                term::format::positive(" public ")
+
            }
        } else {
            term::format::default(" ")
        },
        if path == cwd {
-
            term::format::highlight(".").to_string()
+
            term::format::tertiary(".").to_string()
        } else {
-
            term::format::highlight(path.display()).to_string()
+
            term::format::tertiary(path.display()).to_string()
        }
    ));

+
    // TODO: Move up.
    if let Ok((remote, _)) = git::rad_remote(&repo) {
        if let Some(remote) = remote.url() {
            bail!("repository is already initialized with remote {remote}");
        }
    }

+
    // TODO: Move up.
    let signer = term::signer(profile)?;
    let head: String = repo
        .head()
@@ -241,6 +247,16 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
    });
    let branch = RefString::try_from(branch.clone())
        .map_err(|e| anyhow!("invalid branch name {:?}: {}", branch, e))?;
+
    let visibility = if let Some(v) = options.visibility {
+
        v
+
    } else {
+
        let selected = term::select(
+
            "Visibility",
+
            &["public", "private"],
+
            "Public repositories are accessible by anyone on the network after initialization",
+
        )?;
+
        Visibility::from_str(selected)?
+
    };

    let mut node = radicle::Node::new(profile.socket());
    let mut spinner = term::spinner("Initializing...");
@@ -251,7 +267,7 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
        &name,
        &description,
        branch,
-
        options.visibility,
+
        visibility,
        &signer,
        &profile.storage,
    ) {
@@ -265,7 +281,7 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
            }

            spinner.message(format!(
-
                "Project {} created",
+
                "Project {} created.",
                term::format::highlight(proj.name())
            ));
            spinner.finish();
@@ -291,24 +307,6 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
                self::setup_signing(profile.id(), &repo, interactive)?;
            }

-
            if node.is_running() {
-
                let spinner = term::spinner("Syncing inventory..");
-
                if let Err(e) = node.sync_inventory() {
-
                    spinner.error(e);
-
                } else {
-
                    spinner.finish();
-
                }
-
            }
-

-
            if options.announce {
-
                let spinner = term::spinner("Announcing inventory..");
-
                if let Err(e) = node.announce_inventory() {
-
                    spinner.error(e);
-
                } else {
-
                    spinner.finish();
-
                }
-
            }
-

            term::blank();
            term::info!(
                "Your project's Repository ID {} is {}.",
@@ -316,17 +314,21 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
                term::format::highlight(id.urn())
            );
            term::info!(
-
                "You can show it any time by running {}",
+
                "You can show it any time by running {} from this directory.",
                term::format::command("rad .")
            );
+
            term::blank();

-
            if !options.announce {
+
            // Announce inventory to network.
+
            if let Err(e) = announce(doc, &mut node) {
+
                term::blank();
+
                term::warning(format!(
+
                    "There was an error announcing your project to the network: {e}"
+
                ));
+
                term::warning("Try again with `rad sync --announce`, or check your logs with `rad node logs`.");
                term::blank();
-
                term::info!(
-
                    "To publish your project to the network, run {}",
-
                    term::format::command(push_cmd)
-
                );
            }
+
            term::info!("To push changes, run {}.", term::format::command(push_cmd));
        }
        Err(err) => {
            spinner.failed();
@@ -340,6 +342,44 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
    Ok(())
}

+
pub fn announce(doc: Doc<Verified>, node: &mut Node) -> anyhow::Result<()> {
+
    if doc.visibility.is_public() {
+
        if node.is_running() {
+
            let mut spinner = term::spinner("Updating inventory..");
+

+
            node.sync_inventory()?;
+
            spinner.message("Announcing..");
+
            node.announce_inventory()?;
+
            spinner.message("Project successfully announced.");
+
            spinner.finish();
+

+
            term::blank();
+
            term::info!(
+
                "Your project has been announced to the network and is \
+
                now discoverable by peers.",
+
            );
+
        } else {
+
            term::info!("Your project will be announced to the network when you start your node.");
+
            term::info!(
+
                "You can start your node with {}.",
+
                term::format::command("rad node start")
+
            );
+
        }
+
    } else {
+
        // TODO: Tell users how to make the project public.
+
        term::info!(
+
            "You have created a {} repository.",
+
            term::format::yellow("private")
+
        );
+
        term::info!(
+
            "This repository will only be visible to you, \
+
            and to peers you explicitly allow.",
+
        );
+
    }
+

+
    Ok(())
+
}
+

/// Setup radicle key as commit signing key in repository.
pub fn setup_signing(
    node_id: &NodeId,
modified radicle-cli/src/commands/node.rs
@@ -132,7 +132,7 @@ impl Args for Options {
        let mut rid: Option<Id> = None;
        let mut json: bool = false;
        let mut addr: Option<PeerAddr<NodeId, Address>> = None;
-
        let mut lines: usize = 10;
+
        let mut lines: usize = 60;
        let mut count: usize = usize::MAX;
        let mut timeout = time::Duration::MAX;
        let mut verbose = false;
modified radicle-cli/src/commands/rm.rs
@@ -101,7 +101,7 @@ fn untrack(rid: &Id, profile: &Profile) -> anyhow::Result<()> {
    };

    if let Err(e) = result {
-
        term::warning(&format!("Failed to untrack repository: {e}"));
+
        term::warning(format!("Failed to untrack repository: {e}"));
        term::warning("Make sure to untrack this repository when your node is running");
    } else {
        term::success!("Untracked {rid}")
@@ -116,7 +116,7 @@ fn remove_remote(rid: &Id) -> anyhow::Result<()> {
        .map_err(|err| err.into())
        .and_then(|repo| git::remove_remote(&repo, rid))
    {
-
        term::warning(&format!(
+
        term::warning(format!(
            "Attempted to remove 'rad' remote from working copy: {e}"
        ));
        term::warning("In case a working copy exists, make sure to `git remote remove rad`");
modified radicle-cli/tests/commands.rs
@@ -1004,7 +1004,7 @@ fn test_replication_via_seed() {
                "Radicle Heartwood Protocol & Stack",
                "--default-branch",
                "master",
-
                "--announce",
+
                "--public",
            ],
            working.join("alice"),
        )
modified radicle-term/src/io.rs
@@ -28,7 +28,9 @@ pub static CONFIG: Lazy<RenderConfig> = Lazy::new(|| RenderConfig {
    prompt_prefix: Styled::new("?").with_fg(Color::LightBlue),
    answered_prompt_prefix: Styled::new("✓").with_fg(Color::LightGreen),
    answer: StyleSheet::new(),
-
    highlighted_option_prefix: Styled::new("*").with_fg(Color::LightYellow),
+
    highlighted_option_prefix: Styled::new("✓").with_fg(Color::LightYellow),
+
    selected_option: Some(StyleSheet::new().with_fg(Color::LightYellow)),
+
    option: StyleSheet::new(),
    help_message: StyleSheet::new().with_fg(Color::DarkGrey),
    default_value: StyleSheet::new().with_fg(Color::LightBlue),
    error_message: ErrorMessageRenderConfig::default_colored()
@@ -152,7 +154,7 @@ pub fn subcommand(msg: impl fmt::Display) {
    println!("{}", style(format!("Running `{msg}`...")).dim());
}

-
pub fn warning(warning: &str) {
+
pub fn warning(warning: impl fmt::Display) {
    println!(
        "{} {} {warning}",
        WARNING_PREFIX,
@@ -237,23 +239,16 @@ pub fn passphrase_stdin() -> Result<Passphrase, anyhow::Error> {
    Ok(Passphrase::from(input.trim_end().to_owned()))
}

-
pub fn select<'a, T>(
-
    prompt: &str,
-
    options: &'a [T],
-
    active: &'a T,
-
) -> Result<Option<&'a T>, InquireError>
+
pub fn select<'a, T>(prompt: &str, options: &'a [T], help: &str) -> Result<&'a T, InquireError>
where
    T: fmt::Display + Eq + PartialEq,
{
-
    let active = options.iter().position(|o| o == active);
-
    let selection =
-
        Select::new(prompt, options.iter().collect::<Vec<_>>()).with_render_config(*CONFIG);
+
    let selection = Select::new(prompt, options.iter().collect::<Vec<_>>())
+
        .with_vim_mode(true)
+
        .with_help_message(help)
+
        .with_render_config(*CONFIG);

-
    if let Some(active) = active {
-
        selection.with_starting_cursor(active).prompt_skippable()
-
    } else {
-
        selection.prompt_skippable()
-
    }
+
    selection.with_starting_cursor(0).prompt()
}

pub fn markdown(content: &str) {
modified radicle-tools/src/rad-cli-demo.rs
@@ -13,16 +13,16 @@ fn main() -> anyhow::Result<()> {
            "editor",
            "prompt",
        ],
-
        &"spinner",
+
        "Choose wisely!",
    )?;

-
    match demo {
-
        Some(&"confirm") => {
+
    match *demo {
+
        "confirm" => {
            if terminal::confirm("Would you like to proceed?") {
                terminal::success!("You said 'yes'");
            }
        }
-
        Some(&"editor") => {
+
        "editor" => {
            let output = terminal::editor::Editor::new()
                .extension("rs")
                .edit("// Enter code here.");
@@ -40,7 +40,7 @@ fn main() -> anyhow::Result<()> {
                }
            }
        }
-
        Some(&"spinner") => {
+
        "spinner" => {
            let mut spinner = terminal::spinner("Spinning turbines..");
            thread::sleep(time::Duration::from_secs(1));
            spinner.message("Still spinning..");
@@ -51,27 +51,22 @@ fn main() -> anyhow::Result<()> {

            spinner.finish();
        }
-
        Some(&"spinner-drop") => {
+
        "spinner-drop" => {
            let _spinner = terminal::spinner("Spinning turbines..");
            thread::sleep(time::Duration::from_secs(3));
        }
-
        Some(&"spinner-error") => {
+
        "spinner-error" => {
            let spinner = terminal::spinner("Spinning turbines..");
            thread::sleep(time::Duration::from_secs(3));
            spinner.error("broken turbine");
        }
-
        Some(&"prompt") => {
+
        "prompt" => {
            let fruit = terminal::io::select(
                "Enter your favorite fruit:",
                &["apple", "pear", "banana", "strawberry"],
-
                &"apple",
+
                "Choose wisely!",
            )?;
-

-
            if let Some(fruit) = fruit {
-
                terminal::success!("You have chosen '{fruit}'");
-
            } else {
-
                terminal::info!("Ok, bye.");
-
            }
+
            terminal::success!("You have chosen '{fruit}'");
        }
        _ => {}
    }