Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
cli: add `rad unblock` command
Merged fintohaps opened 1 year ago

Add the rad unblock command for reversing the effect of blocking an RID.

Note that the unblock command also takes a scope, which defaults to all. It was noticed that when adding the command without a scope, that the schema defaults to followed. This seemed strange that the output of rad seed would show the scope as all alongside the block policy, and then would change to followed.

To allow setting the policy and the scope in one SQL statement, a new method set_seedinge was also added.

7 files changed +148 -1 468d5a46 52554af4
modified radicle-cli/examples/rad-block.md
@@ -41,3 +41,15 @@ $ rad seed
│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji          block    all   │
╰───────────────────────────────────────────────────────────╯
```
+

+
If we want to reverse the blocking of the RID we can use `rad unblock`:
+

+
```
+
$ rad unblock rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+
✓ The 'block' policy for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji is removed
+
```
+

+
```
+
$ rad seed
+
No seeding policies to show.
+
```
modified radicle-cli/src/commands.rs
@@ -52,6 +52,8 @@ pub mod rad_self;
pub mod rad_stats;
#[path = "commands/sync.rs"]
pub mod rad_sync;
+
#[path = "commands/unblock.rs"]
+
pub mod rad_unblock;
#[path = "commands/unfollow.rs"]
pub mod rad_unfollow;
#[path = "commands/unseed.rs"]
modified radicle-cli/src/commands/help.rs
@@ -33,6 +33,7 @@ const COMMANDS: &[Help] = &[
    rad_self::HELP,
    rad_seed::HELP,
    rad_follow::HELP,
+
    rad_unblock::HELP,
    rad_unfollow::HELP,
    rad_unseed::HELP,
    rad_remote::HELP,
added radicle-cli/src/commands/unblock.rs
@@ -0,0 +1,97 @@
+
use std::ffi::OsString;
+

+
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: "Unblock repositories or nodes to allow them to be seeded or followed",
+
    version: env!("RADICLE_VERSION"),
+
    usage: r#"
+
Usage
+

+
    rad unblock <rid> [<option>...]
+
    rad unblock <nid> [<option>...]
+

+
    Unblock a repository to allow them to be seeded or a node to be 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 {
+
                        return Err(anyhow::anyhow!(
+
                            "invalid repository or node specified, see `rad unblock --help`"
+
                        ));
+
                    }
+
                }
+
                _ => return Err(anyhow::anyhow!(arg.unexpected())),
+
            }
+
        }
+

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

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

+
    let updated = match options.target {
+
        Target::Node(nid) => policies.unblock_nid(&nid)?,
+
        Target::Repo(rid) => policies.unblock_rid(&rid)?,
+
    };
+

+
    if updated {
+
        term::success!("The 'block' policy for {} is removed", options.target);
+
    } else {
+
        term::info!("No 'block' policy exists for {}", options.target)
+
    }
+
    Ok(())
+
}
modified radicle-cli/src/main.rs
@@ -291,6 +291,13 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
                args.to_vec(),
            );
        }
+
        "unblock" => {
+
            term::run_command_args::<rad_unblock::Options, _>(
+
                rad_unblock::HELP,
+
                rad_unblock::run,
+
                args.to_vec(),
+
            );
+
        }
        "unfollow" => {
            term::run_command_args::<rad_unfollow::Options, _>(
                rad_unfollow::HELP,
modified radicle/src/node/policy.rs
@@ -139,11 +139,15 @@ impl TryFrom<&sqlite::Value> for Policy {
    type Error = sqlite::Error;

    fn try_from(value: &sqlite::Value) -> Result<Self, Self::Error> {
-
        let message = Some("sql: invalid policy".to_owned());
+
        let message = Some("sql: invalid policy value".to_owned());

        match value {
            sqlite::Value::String(s) if s == "allow" => Ok(Policy::Allow),
            sqlite::Value::String(s) if s == "block" => Ok(Policy::Block),
+
            sqlite::Value::String(s) => Err(sqlite::Error {
+
                code: None,
+
                message: Some(format!("sql: invalid policy '{s}'")),
+
            }),
            _ => Err(sqlite::Error {
                code: None,
                message,
modified radicle/src/node/policy/store.rs
@@ -198,6 +198,30 @@ impl Store<Write> {

        Ok(self.db.change_count() > 0)
    }
+

+
    /// Unblock a repository.
+
    pub fn unblock_rid(&mut self, id: &RepoId) -> Result<bool, Error> {
+
        let mut stmt = self
+
            .db
+
            .prepare("DELETE FROM `seeding` WHERE id = ? AND policy = 'block'")?;
+

+
        stmt.bind((1, id))?;
+
        stmt.next()?;
+

+
        Ok(self.db.change_count() > 0)
+
    }
+

+
    /// Unblock a node.
+
    pub fn unblock_nid(&mut self, id: &NodeId) -> Result<bool, Error> {
+
        let mut stmt = self
+
            .db
+
            .prepare("DELETE FROM `following` WHERE id = ? AND policy = 'block'")?;
+

+
        stmt.bind((1, id))?;
+
        stmt.next()?;
+

+
        Ok(self.db.change_count() > 0)
+
    }
}

/// `Read` methods for `Config`. This implies that a