Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
clap
✗ CI failure Lorenz Leutgeb committed 7 months ago
commit cd3e0fbac9b0fc0b746ff560f910730e927ff127
parent aa0a00895809b6605b08cad250f670a76f0a3138
1 passed 1 failed 1 pending (3 total) View logs
17 files changed +180 -589
deleted crates/radicle-cli/examples/rad-diff.md
@@ -1,164 +0,0 @@
-
``` (stderr)
-
$ rad diff
-
! Deprecated: The command/option `rad diff` is deprecated and will be removed. Please use `git diff` instead.
-
```
-

-
Exploring `rad diff`.
-

-
``` ./main.c
-
#include <stdio.h>
-

-
int main(void) {
-
    printf("Hello World!\n");
-
    return 0;
-
}
-
```
-

-
```
-
$ ls
-
README
-
main.c
-
```
-

-
```
-
$ git mv README README.md
-
$ git add main.c
-
$ git commit -m "Make changes"
-
[master 5f771e0] Make changes
-
 2 files changed, 6 insertions(+)
-
 rename README => README.md (100%)
-
 create mode 100644 main.c
-
```
-

-
```
-
$ rad diff HEAD^ HEAD
-
╭────────────────────────────────────────────╮
-
│ README -> README.md ❲moved❳                │
-
╰────────────────────────────────────────────╯
-

-
╭────────────────────────────────────────────╮
-
│ main.c +6 ❲created❳                        │
-
├────────────────────────────────────────────┤
-
│ @@ -0,0 +1,6 @@                            │
-
│      1     + #include <stdio.h>            │
-
│      2     +                               │
-
│      3     + int main(void) {              │
-
│      4     +     printf("Hello World!/n"); │
-
│      5     +     return 0;                 │
-
│      6     + }                             │
-
╰────────────────────────────────────────────╯
-

-
```
-

-
```
-
$ sed -i 's/Hello World/Hello Radicle/' main.c
-
$ rad diff
-
╭──────────────────────────────────────────────╮
-
│ main.c -1 +1                                 │
-
├──────────────────────────────────────────────┤
-
│ @@ -1,6 +1,6 @@                              │
-
│ 1    1       #include <stdio.h>              │
-
│ 2    2                                       │
-
│ 3    3       int main(void) {                │
-
│ 4          -     printf("Hello World!/n");   │
-
│      4     +     printf("Hello Radicle!/n"); │
-
│ 5    5           return 0;                   │
-
│ 6    6       }                               │
-
╰──────────────────────────────────────────────╯
-

-
```
-

-
```
-
$ git add main.c
-
$ rad diff
-
$ rad diff --staged
-
╭──────────────────────────────────────────────╮
-
│ main.c -1 +1                                 │
-
├──────────────────────────────────────────────┤
-
│ @@ -1,6 +1,6 @@                              │
-
│ 1    1       #include <stdio.h>              │
-
│ 2    2                                       │
-
│ 3    3       int main(void) {                │
-
│ 4          -     printf("Hello World!/n");   │
-
│      4     +     printf("Hello Radicle!/n"); │
-
│ 5    5           return 0;                   │
-
│ 6    6       }                               │
-
╰──────────────────────────────────────────────╯
-

-
```
-

-
```
-
$ git rm -f -q main.c
-
$ rad diff --staged
-
╭────────────────────────────────────────────╮
-
│ main.c -6 ❲deleted❳                        │
-
├────────────────────────────────────────────┤
-
│ @@ -1,6 +0,0 @@                            │
-
│ 1          - #include <stdio.h>            │
-
│ 2          -                               │
-
│ 3          - int main(void) {              │
-
│ 4          -     printf("Hello World!/n"); │
-
│ 5          -     return 0;                 │
-
│ 6          - }                             │
-
╰────────────────────────────────────────────╯
-

-
```
-

-
For now, copies are not detected.
-

-
```
-
$ git reset --hard master -q
-
$ mkdir docs
-
$ cp README.md docs/README.md
-
$ git add docs
-
$ rad diff --staged
-
╭─────────────────────────────╮
-
│ docs/README.md +1 ❲created❳ │
-
├─────────────────────────────┤
-
│ @@ -0,0 +1,1 @@             │
-
│      1     + Hello World!   │
-
╰─────────────────────────────╯
-

-
$ git reset
-
$ git checkout .
-
```
-

-
Empty file.
-

-
```
-
$ touch EMPTY
-
$ git add EMPTY
-
$ rad diff --staged
-
╭─────────────────╮
-
│ EMPTY ❲created❳ │
-
╰─────────────────╯
-

-
$ git reset
-
$ git checkout .
-
```
-

-
File mode change.
-

-
```
-
$ chmod +x README.md
-
$ rad diff
-
╭───────────────────────────────────────────╮
-
│ README.md 100644 -> 100755 ❲mode changed❳ │
-
╰───────────────────────────────────────────╯
-

-
$ git reset -q
-
$ git checkout .
-
```
-

-
Binary file.
-

-
```
-
$ touch file.bin
-
$ truncate -s 8 file.bin
-
$ git add file.bin
-
$ rad diff --staged
-
╭─────────────────────────────╮
-
│ file.bin ❲binary❳ ❲created❳ │
-
╰─────────────────────────────╯
-

-
```
modified crates/radicle-cli/examples/rad-help.md
@@ -11,6 +11,7 @@ Common `rad` commands used in various situations:
	checkout     Checkout a repository into the local directory
	clone        Clone a Radicle repository
	config       Manage your local Radicle configuration
+
	debug        Write out information to help debug your Radicle node remotely
	fork         Create a fork of a repository
	help         CLI help
	id           Manage repository identities
@@ -22,6 +23,7 @@ Common `rad` commands used in various situations:
	node         Control and query the Radicle Node
	patch        Manage patches
	path         Display the Radicle home path
+
	publish      Publish a repository to the network
	clean        Remove all remotes from a repository
	self         Show information about your identity and device
	seed         Manage repository seeding policies
modified crates/radicle-cli/examples/rad-patch-diff.md
@@ -11,13 +11,13 @@ $ git push rad HEAD:refs/patches
```
```
$ rad patch diff 147309e
-
╭───────────────────────────╮
-
│ README.md +1 ❲created❳    │
-
├───────────────────────────┤
-
│ @@ -0,0 +1,1 @@           │
-
│      1     + Hello World! │
-
╰───────────────────────────╯
-

+
diff --git a/README.md b/README.md
+
new file mode 100644
+
index 0000000..980a0d5
+
--- /dev/null
+
+++ b/README.md
+
@@ -0,0 +1 @@
+
+Hello World!
```

If we add another file and update the patch, we can see it in the diff.
@@ -32,20 +32,20 @@ $ git push -f
```
```
$ rad patch diff 147309e
-
╭─────────────────────────────╮
-
│ RADICLE.md +1 ❲created❳     │
-
├─────────────────────────────┤
-
│ @@ -0,0 +1,1 @@             │
-
│      1     + Hello Radicle! │
-
╰─────────────────────────────╯
-

-
╭─────────────────────────────╮
-
│ README.md +1 ❲created❳      │
-
├─────────────────────────────┤
-
│ @@ -0,0 +1,1 @@             │
-
│      1     + Hello World!   │
-
╰─────────────────────────────╯
-

+
diff --git a/RADICLE.md b/RADICLE.md
+
new file mode 100644
+
index 0000000..e517184
+
--- /dev/null
+
+++ b/RADICLE.md
+
@@ -0,0 +1 @@
+
+Hello Radicle!
+
diff --git a/README.md b/README.md
+
new file mode 100644
+
index 0000000..980a0d5
+
--- /dev/null
+
+++ b/README.md
+
@@ -0,0 +1 @@
+
+Hello World!
```

Buf if we only want to see the changes from the first revision, we can do that
@@ -53,11 +53,11 @@ too.

```
$ rad patch diff 147309e --revision 147309e
-
╭───────────────────────────╮
-
│ README.md +1 ❲created❳    │
-
├───────────────────────────┤
-
│ @@ -0,0 +1,1 @@           │
-
│      1     + Hello World! │
-
╰───────────────────────────╯
-

+
diff --git a/README.md b/README.md
+
new file mode 100644
+
index 0000000..980a0d5
+
--- /dev/null
+
+++ b/README.md
+
@@ -0,0 +1 @@
+
+Hello World!
```
modified crates/radicle-cli/src/commands.rs
@@ -6,7 +6,6 @@ pub mod clone;
pub mod cob;
pub mod config;
pub mod debug;
-
pub mod diff;
pub mod follow;
pub mod fork;
pub mod help;
modified crates/radicle-cli/src/commands/block.rs
@@ -1,96 +1,24 @@
-
use std::ffi::OsString;
+
mod args;

use radicle::node::policy::Policy;
-
use radicle::prelude::{NodeId, RepoId};

use crate::terminal as term;
-
use crate::terminal::args;
-
use crate::terminal::args::{Args, Error, Help};

-
pub const HELP: Help = Help {
-
    name: "block",
-
    description: "Block repositories or nodes from being seeded or followed",
-
    version: env!("RADICLE_VERSION"),
-
    usage: r#"
-
Usage
+
use args::Target;

-
    rad block <rid> [<option>...]
-
    rad block <nid> [<option>...]
+
pub use args::Args;
+
pub(crate) use args::ABOUT;

-
    Blocks a repository from being seeded or a node from being followed.
-

-
Options
-

-
    --help          Print help
-
"#,
-
};
-

-
enum Target {
-
    Node(NodeId),
-
    Repo(RepoId),
-
}
-

-
impl std::fmt::Display for Target {
-
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-
        match self {
-
            Self::Node(nid) => nid.fmt(f),
-
            Self::Repo(rid) => rid.fmt(f),
-
        }
-
    }
-
}
-

-
pub struct Options {
-
    target: Target,
-
}
-

-
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 target = None;
-

-
        while let Some(arg) = parser.next()? {
-
            match arg {
-
                Long("help") | Short('h') => {
-
                    return Err(Error::Help.into());
-
                }
-
                Value(val) if target.is_none() => {
-
                    if let Ok(rid) = args::rid(&val) {
-
                        target = Some(Target::Repo(rid));
-
                    } else if let Ok(nid) = args::nid(&val) {
-
                        target = Some(Target::Node(nid));
-
                    } else {
-
                        anyhow::bail!(
-
                            "invalid repository or node specified, see `rad block --help`"
-
                        )
-
                    }
-
                }
-
                _ => anyhow::bail!(arg.unexpected()),
-
            }
-
        }
-

-
        Ok((
-
            Options {
-
                target: target.ok_or(anyhow::anyhow!(
-
                    "a repository or node to block must be specified, see `rad block --help`"
-
                ))?,
-
            },
-
            vec![],
-
        ))
-
    }
-
}
-

-
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
+
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
    let profile = ctx.profile()?;
    let mut policies = profile.policies_mut()?;

-
    let updated = match options.target {
+
    let updated = match args.target {
        Target::Node(nid) => policies.set_follow_policy(&nid, Policy::Block)?,
        Target::Repo(rid) => policies.set_seed_policy(&rid, Policy::Block)?,
    };
    if updated {
-
        term::success!("Policy for {} set to 'block'", options.target);
+
        term::success!("Policy for {} set to 'block'", args.target);
    }
    Ok(())
}
added crates/radicle-cli/src/commands/block/args.rs
@@ -0,0 +1,48 @@
+
use clap::Parser;
+
use thiserror::Error;
+

+
use radicle::prelude::{NodeId, RepoId};
+

+
pub(crate) const ABOUT: &str = "Block repositories or nodes from being seeded or followed";
+

+
#[derive(Clone, Debug)]
+
pub(super) enum Target {
+
    Node(NodeId),
+
    Repo(RepoId),
+
}
+

+
#[derive(Debug, Error)]
+
#[error("invalid repository or node specified (RID parsing failed with: '{repo}', NID parsing failed with: '{node}'))")]
+
pub(super) struct ParseTargetError {
+
    repo: radicle::identity::IdError,
+
    node: radicle::crypto::PublicKeyError,
+
}
+

+
impl std::str::FromStr for Target {
+
    type Err = ParseTargetError;
+

+
    fn from_str(val: &str) -> Result<Self, Self::Err> {
+
        val.parse::<RepoId>().map(Target::Repo).or_else(|repo| {
+
            val.parse::<NodeId>()
+
                .map(Target::Node)
+
                .map_err(|node| ParseTargetError { repo, node })
+
        })
+
    }
+
}
+

+
impl std::fmt::Display for Target {
+
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+
        match self {
+
            Self::Node(nid) => nid.fmt(f),
+
            Self::Repo(rid) => rid.fmt(f),
+
        }
+
    }
+
}
+

+
#[derive(Parser, Debug)]
+
#[command(about = ABOUT, disable_version_flag = true)]
+
pub struct Args {
+
    /// A Repository ID or Node ID to block from seeding or following (respectively)
+
    #[arg(value_name = "RID|NID")]
+
    pub(super) target: Target,
+
}
modified crates/radicle-cli/src/commands/debug.rs
@@ -1,7 +1,7 @@
-
#![allow(clippy::or_fun_call)]
+
mod args;
+

use std::collections::BTreeMap;
use std::env;
-
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;

@@ -11,40 +11,16 @@ use serde::Serialize;
use radicle::Profile;

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

+
pub use args::Args;
+
pub(crate) use args::ABOUT;

pub const NAME: &str = "rad";
pub const VERSION: &str = env!("RADICLE_VERSION");
pub const DESCRIPTION: &str = "Radicle command line interface";
pub const GIT_HEAD: &str = env!("GIT_HEAD");

-
pub const HELP: Help = Help {
-
    name: "debug",
-
    description: "Write out information to help debug your Radicle node remotely",
-
    version: env!("RADICLE_VERSION"),
-
    usage: r#"
-
Usage
-

-
    rad debug
-

-
    Run this if you are reporting a problem in Radicle. The output is
-
    helpful for Radicle developers to debug your problem remotely. The
-
    output is meant to not include any sensitive information, but
-
    please check it, and then forward to the Radicle developers.
-

-
"#,
-
};
-

-
#[derive(Debug)]
-
pub struct Options {}
-

-
impl Args for Options {
-
    fn from_args(_args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
-
        Ok((Options {}, vec![]))
-
    }
-
}
-

-
pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
+
pub fn run(_args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
    match ctx.profile() {
        Ok(profile) => debug(Some(&profile)),
        Err(e) => {
added crates/radicle-cli/src/commands/debug/args.rs
@@ -0,0 +1,13 @@
+
use clap::Parser;
+

+
pub const ABOUT: &str = "Write out information to help debug your Radicle node remotely";
+

+
const LONG_ABOUT: &str = r#"
+
Run this if you are reporting a problem in Radicle. The output is
+
helpful for Radicle developers to debug your problem remotely. The
+
output is meant to not include any sensitive information, but
+
please check it, and then forward to the Radicle developers."#;
+

+
#[derive(Parser, Debug)]
+
#[command(about = ABOUT, long_about = LONG_ABOUT, disable_version_flag = true)]
+
pub struct Args {}
deleted crates/radicle-cli/src/commands/diff.rs
@@ -1,153 +0,0 @@
-
use std::ffi::OsString;
-

-
use anyhow::anyhow;
-

-
use radicle::git;
-
use radicle::rad;
-
use radicle_surf as surf;
-

-
use crate::git::pretty_diff::ToPretty as _;
-
use crate::git::Rev;
-
use crate::terminal as term;
-
use crate::terminal::args::{Args, Error, Help};
-
use crate::terminal::highlight::Highlighter;
-

-
pub const HELP: Help = Help {
-
    name: "diff",
-
    description: "Show changes between commits",
-
    version: env!("RADICLE_VERSION"),
-
    usage: r#"
-
Usage
-

-
    rad diff [<commit>] [--staged] [<option>...]
-
    rad diff <commit> [<commit>] [<option>...]
-

-
    This command is meant to operate as closely as possible to `git diff`,
-
    except its output is optimized for human-readability.
-

-
Options
-

-
    --unified, -U   Context lines to show (default: 5)
-
    --staged        View staged changes
-
    --color         Force color output
-
    --help          Print help
-
"#,
-
};
-

-
pub struct Options {
-
    pub commits: Vec<Rev>,
-
    pub staged: bool,
-
    pub unified: usize,
-
    pub color: 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 commits = Vec::new();
-
        let mut staged = false;
-
        let mut unified = 5;
-
        let mut color = false;
-

-
        while let Some(arg) = parser.next()? {
-
            match arg {
-
                Long("unified") | Short('U') => {
-
                    let val = parser.value()?;
-
                    unified = term::args::number(&val)?;
-
                }
-
                Long("staged") | Long("cached") => staged = true,
-
                Long("color") => color = true,
-
                Long("help") | Short('h') => return Err(Error::Help.into()),
-
                Value(val) => {
-
                    let rev = term::args::rev(&val)?;
-

-
                    commits.push(rev);
-
                }
-
                _ => anyhow::bail!(arg.unexpected()),
-
            }
-
        }
-

-
        Ok((
-
            Options {
-
                commits,
-
                staged,
-
                unified,
-
                color,
-
            },
-
            vec![],
-
        ))
-
    }
-
}
-

-
pub fn run(options: Options, _ctx: impl term::Context) -> anyhow::Result<()> {
-
    crate::warning::deprecated("rad diff", "git diff");
-

-
    let repo = rad::repo()?;
-
    let oids = options
-
        .commits
-
        .into_iter()
-
        .map(|rev| {
-
            repo.revparse_single(rev.as_str())
-
                .map_err(|e| anyhow!("unknown object {rev}: {e}"))
-
                .and_then(|o| {
-
                    o.into_commit()
-
                        .map_err(|_| anyhow!("object {rev} is not a commit"))
-
                })
-
        })
-
        .collect::<Result<Vec<_>, _>>()?;
-

-
    let mut opts = git::raw::DiffOptions::new();
-
    opts.patience(true)
-
        .minimal(true)
-
        .context_lines(options.unified as u32);
-

-
    let mut find_opts = git::raw::DiffFindOptions::new();
-
    find_opts.exact_match_only(true);
-
    find_opts.all(true);
-

-
    let mut diff = match oids.as_slice() {
-
        [] => {
-
            if options.staged {
-
                let head = repo.head()?.peel_to_tree()?;
-
                // HEAD vs. index.
-
                repo.diff_tree_to_index(Some(&head), None, Some(&mut opts))
-
            } else {
-
                // Working tree vs. index.
-
                repo.diff_index_to_workdir(None, None)
-
            }
-
        }
-
        [commit] => {
-
            let commit = commit.tree()?;
-
            if options.staged {
-
                // Commit vs. index.
-
                repo.diff_tree_to_index(Some(&commit), None, Some(&mut opts))
-
            } else {
-
                // Commit vs. working tree.
-
                repo.diff_tree_to_workdir(Some(&commit), Some(&mut opts))
-
            }
-
        }
-
        [left, right] => {
-
            // Commit vs. commit.
-
            let left = left.tree()?;
-
            let right = right.tree()?;
-

-
            repo.diff_tree_to_tree(Some(&left), Some(&right), Some(&mut opts))
-
        }
-
        _ => {
-
            anyhow::bail!("Too many commits given. See `rad diff --help` for usage.");
-
        }
-
    }?;
-
    diff.find_similar(Some(&mut find_opts))?;
-

-
    term::Paint::force(options.color);
-

-
    let diff = surf::diff::Diff::try_from(diff)?;
-
    let mut hi = Highlighter::default();
-
    let pretty = diff.pretty(&mut hi, &(), &repo);
-

-
    crate::pager::run(pretty)?;
-

-
    Ok(())
-
}
modified crates/radicle-cli/src/commands/fork.rs
@@ -1,66 +1,23 @@
-
use std::ffi::OsString;
+
mod args;

use anyhow::Context as _;

-
use radicle::prelude::RepoId;
use radicle::rad;

use crate::terminal as term;
-
use crate::terminal::args;
-
use crate::terminal::args::{Args, Error, Help};

-
pub const HELP: Help = Help {
-
    name: "fork",
-
    description: "Create a fork of a repository",
-
    version: env!("RADICLE_VERSION"),
-
    usage: r#"
-
Usage
+
pub use args::Args;
+
pub(crate) use args::ABOUT;

-
    rad fork [<rid>] [<option>...]
-

-
Options
-

-
    --help          Print help
-
"#,
-
};
-

-
pub struct Options {
-
    rid: Option<RepoId>,
-
}
-

-
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 = None;
-

-
        if let Some(arg) = parser.next()? {
-
            match arg {
-
                Long("help") | Short('h') => {
-
                    return Err(Error::Help.into());
-
                }
-
                Value(val) if rid.is_none() => {
-
                    rid = Some(args::rid(&val)?);
-
                }
-
                _ => anyhow::bail!(arg.unexpected()),
-
            }
-
        }
-

-
        Ok((Options { rid }, vec![]))
-
    }
-
}
-

-
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
+
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
    let profile = ctx.profile()?;
    let signer = profile.signer()?;
    let storage = &profile.storage;

-
    let rid = match options.rid {
+
    let rid = match args.rid {
        Some(rid) => rid,
        None => {
-
            let (_, rid) =
-
                radicle::rad::cwd().context("Current directory is not a Radicle repository")?;
+
            let (_, rid) = rad::cwd().context("Current directory is not a Radicle repository")?;

            rid
        }
added crates/radicle-cli/src/commands/fork/args.rs
@@ -0,0 +1,9 @@
+
pub(crate) const ABOUT: &str = "Create a fork of a repository";
+

+
#[derive(Debug, clap::Parser)]
+
#[command(about = ABOUT, disable_version_flag = true)]
+
pub struct Args {
+
    /// The Repository ID of the repository to fork
+
    #[arg(value_name = "RID")]
+
    pub(super) rid: Option<radicle::identity::RepoId>,
+
}
modified crates/radicle-cli/src/commands/help.rs
@@ -38,14 +38,24 @@ impl CommandItem {

const COMMANDS: &[CommandItem] = &[
    CommandItem::Lexopt(crate::commands::auth::HELP),
-
    CommandItem::Lexopt(crate::commands::block::HELP),
+
    CommandItem::Clap {
+
        name: "block",
+
        about: crate::commands::block::ABOUT,
+
    },
    CommandItem::Lexopt(crate::commands::checkout::HELP),
    CommandItem::Clap {
        name: "clone",
        about: crate::commands::clone::ABOUT,
    },
    CommandItem::Lexopt(crate::commands::config::HELP),
-
    CommandItem::Lexopt(crate::commands::fork::HELP),
+
    CommandItem::Clap {
+
        name: "debug",
+
        about: crate::commands::debug::ABOUT,
+
    },
+
    CommandItem::Clap {
+
        name: "fork",
+
        about: crate::commands::fork::ABOUT,
+
    },
    CommandItem::Lexopt(crate::commands::help::HELP),
    CommandItem::Lexopt(crate::commands::id::HELP),
    CommandItem::Lexopt(crate::commands::init::HELP),
@@ -63,6 +73,10 @@ const COMMANDS: &[CommandItem] = &[
        about: crate::commands::path::ABOUT,
    },
    CommandItem::Clap {
+
        name: "publish",
+
        about: crate::commands::publish::ABOUT,
+
    },
+
    CommandItem::Clap {
        name: "clean",
        about: crate::commands::clean::ABOUT,
    },
modified crates/radicle-cli/src/commands/patch/diff.rs
@@ -25,7 +25,7 @@ pub fn run(
    };
    let (from, to) = revision.range();

-
    process::Command::new("rad")
+
    process::Command::new("git")
        .current_dir(stored.path())
        .args(["diff", from.to_string().as_str(), to.to_string().as_str()])
        .stdout(process::Stdio::inherit())
modified crates/radicle-cli/src/commands/publish.rs
@@ -1,75 +1,20 @@
-
use std::ffi::OsString;
+
mod args;

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

use radicle::cob;
use radicle::identity::{Identity, Visibility};
use radicle::node::Handle as _;
-
use radicle::prelude::RepoId;
use radicle::storage::{SignRepository, ValidateRepository, WriteRepository, WriteStorage};

use crate::terminal as term;
-
use crate::terminal::args::{Args, Error, Help};

-
pub const HELP: Help = Help {
-
    name: "publish",
-
    description: "Publish a repository to the network",
-
    version: env!("RADICLE_VERSION"),
-
    usage: r#"
-
Usage
+
pub use args::Args;
+
pub(crate) use args::ABOUT;

-
    rad publish [<rid>] [<option>...]
-

-
    Publishing a private repository makes it public and discoverable
-
    on the network.
-

-
    By default, this command will publish the current repository.
-
    If an `<rid>` is specified, that repository will be published instead.
-

-
    Note that this command can only be run for repositories with a
-
    single delegate. The delegate must be the currently authenticated
-
    user. For repositories with more than one delegate, the `rad id`
-
    command must be used.
-

-
Options
-

-
    --help                    Print help
-
"#,
-
};
-

-
#[derive(Default, Debug)]
-
pub struct Options {
-
    pub rid: Option<RepoId>,
-
}
-

-
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 = None;
-

-
        while let Some(arg) = parser.next()? {
-
            match arg {
-
                Long("help") | Short('h') => {
-
                    return Err(Error::Help.into());
-
                }
-
                Value(val) if rid.is_none() => {
-
                    rid = Some(term::args::rid(&val)?);
-
                }
-
                arg => {
-
                    return Err(anyhow!(arg.unexpected()));
-
                }
-
            }
-
        }
-

-
        Ok((Options { rid }, vec![]))
-
    }
-
}
-

-
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
+
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
    let profile = ctx.profile()?;
-
    let rid = match options.rid {
+
    let rid = match args.rid {
        Some(rid) => rid,
        None => radicle::rad::cwd()
            .map(|(_, rid)| rid)
@@ -81,7 +26,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let doc = identity.doc();

    if doc.is_public() {
-
        return Err(Error::WithHint {
+
        return Err(term::Error::WithHint {
            err: anyhow!("repository is already public"),
            hint: "to announce the repository to the network, run `rad sync --inventory`",
        }
@@ -91,7 +36,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
        return Err(anyhow!("only the repository delegate can publish it"));
    }
    if doc.delegates().len() > 1 {
-
        return Err(Error::WithHint {
+
        return Err(term::Error::WithHint {
            err: anyhow!(
                "only repositories with a single delegate can be published with this command"
            ),
added crates/radicle-cli/src/commands/publish/args.rs
@@ -0,0 +1,21 @@
+
pub(crate) const ABOUT: &str = "Publish a repository to the network";
+

+
const LONG_ABOUT: &str = r#"
+
Publishing a private repository makes it public and discoverable
+
on the network.
+

+
By default, this command will publish the current repository.
+
If an `<rid>` is specified, that repository will be published instead.
+

+
Note that this command can only be run for repositories with a
+
single delegate. The delegate must be the currently authenticated
+
user. For repositories with more than one delegate, the `rad id`
+
command must be used."#;
+

+
#[derive(Debug, clap::Parser)]
+
#[command(about = ABOUT, long_about = LONG_ABOUT, disable_version_flag = true)]
+
pub struct Args {
+
    /// The Repository ID of the repository to publish
+
    #[arg(value_name = "RID")]
+
    pub(super) rid: Option<radicle::identity::RepoId>,
+
}
modified crates/radicle-cli/src/main.rs
@@ -45,10 +45,14 @@ struct CliArgs {

#[derive(Subcommand, Debug)]
enum Commands {
+
    Block(block::Args),
    Clean(clean::Args),
    Clone(clone::Args),
+
    Debug(debug::Args),
+
    Fork(fork::Args),
    Issue(issue::Args),
    Path(path::Args),
+
    Publish(publish::Args),
    Stats(stats::Args),
    Unfollow(unfollow::Args),
    Unseed(unseed::Args),
@@ -179,7 +183,9 @@ pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyho
            term::run_command_args::<auth::Options, _>(auth::HELP, auth::run, args.to_vec());
        }
        "block" => {
-
            term::run_command_args::<block::Options, _>(block::HELP, block::run, args.to_vec());
+
            if let Some(Commands::Block(args)) = CliArgs::parse().command {
+
                term::run_command_fn(block::run, args);
+
            }
        }
        "checkout" => {
            term::run_command_args::<checkout::Options, _>(
@@ -199,17 +205,18 @@ pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyho
        "config" => {
            term::run_command_args::<config::Options, _>(config::HELP, config::run, args.to_vec());
        }
-
        "diff" => {
-
            term::run_command_args::<diff::Options, _>(diff::HELP, diff::run, args.to_vec());
-
        }
        "debug" => {
-
            term::run_command_args::<debug::Options, _>(debug::HELP, debug::run, args.to_vec());
+
            if let Some(Commands::Debug(args)) = CliArgs::parse().command {
+
                term::run_command_fn(debug::run, args);
+
            }
        }
        "follow" => {
            term::run_command_args::<follow::Options, _>(follow::HELP, follow::run, args.to_vec());
        }
        "fork" => {
-
            term::run_command_args::<fork::Options, _>(fork::HELP, fork::run, args.to_vec());
+
            if let Some(Commands::Fork(args)) = CliArgs::parse().command {
+
                term::run_command_fn(fork::run, args);
+
            }
        }
        "help" => {
            term::run_command_args::<help::Options, _>(help::HELP, help::run, args.to_vec());
@@ -250,11 +257,9 @@ pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyho
            }
        }
        "publish" => {
-
            term::run_command_args::<publish::Options, _>(
-
                publish::HELP,
-
                publish::run,
-
                args.to_vec(),
-
            );
+
            if let Some(Commands::Publish(args)) = CliArgs::parse().command {
+
                term::run_command_fn(publish::run, args);
+
            }
        }
        "clean" => {
            if let Some(Commands::Clean(args)) = CliArgs::parse().command {
modified crates/radicle-cli/tests/commands.rs
@@ -1688,15 +1688,6 @@ fn rad_fork() {
}

#[test]
-
fn rad_diff() {
-
    let tmp = tempfile::tempdir().unwrap();
-

-
    fixtures::repository(&tmp);
-

-
    test("examples/rad-diff.md", tmp, None, []).unwrap();
-
}
-

-
#[test]
// User tries to clone; no seeds are available, but user has the repo locally.
fn test_clone_without_seeds() {
    let mut environment = Environment::new();