Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: move rad assign/unassign under rad issue and rad patch
Fintan Halpenny committed 2 years ago
commit d32a10b4b932e979d9eb1c4a8d1515ecc9db3c15
parent dce5316cbbd1ffb17266e32dcb9b56aed5a8285a
10 files changed +157 -235
modified radicle-cli/examples/rad-issue.md
@@ -51,7 +51,7 @@ Let's assign ourselves to this one, this is to ensure work is not
duplicated. While we're at it, let's add a label.

```
-
$ rad assign d185ee16a00bac874c0bcbc2a8ad80fdce5e1e6 --to did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
$ rad issue assign d185ee1 --add did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
$ rad issue label d185ee1 -l good-first-issue
```

@@ -69,7 +69,7 @@ $ rad issue list --assigned
Note: this can always be undone with the `unassign` subcommand.

```
-
$ rad unassign d185ee16a00bac874c0bcbc2a8ad80fdce5e1e61 --from did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
$ rad issue assign d185ee1 --remove did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
```

Great, now we have communicated to the world about our car's defect.
modified radicle-cli/examples/rad-patch.md
@@ -89,10 +89,13 @@ $ git branch -vv
  master               f2de534 [rad/master] Second commit
```

-
We also want to label the patch after we've created it:
+
We can also label patches as well as assign DIDs to the patch to help
+
organise your workflow:
+

```
-
$ rad patch label 6ff4f09c1b5a81347981f59b02ef43a31a07cdae -l fun
-
$ rad patch show 6ff4f09c1b5a81347981f59b02ef43a31a07cdae
+
$ rad patch label 6ff4f09 -l fun
+
$ rad patch assign 6ff4f09 --add did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
$ rad patch show 6ff4f09
╭────────────────────────────────────────────────────╮
│ Title     Define power requirements                │
│ Patch     6ff4f09c1b5a81347981f59b02ef43a31a07cdae │
@@ -123,7 +126,7 @@ $ git commit --message "Add README, just for the fun"
```
``` (stderr)
$ git push rad -o patch.message="Add README, just for the fun"
-
✓ Patch 6ff4f09 updated to 873e637a66be511c45f4ef7b04fddc9def8f072c
+
✓ Patch 6ff4f09 updated to e0fd9f00b51e10e1ca88868e68e46e859ed371d7
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
   3e674d1..27857ec  flux-capacitor-power -> patches/6ff4f09c1b5a81347981f59b02ef43a31a07cdae
```
@@ -131,13 +134,13 @@ To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkE
And let's leave a quick comment for our team:

```
-
$ rad patch comment 6ff4f09c1b5a81347981f59b02ef43a31a07cdae --message 'I cannot wait to get back to the 90s!'
+
$ rad patch comment 6ff4f09 --message 'I cannot wait to get back to the 90s!'
╭───────────────────────────────────────╮
-
│ z6MknSL…StBU8Vi (you) now efaf6fb     │
+
│ z6MknSL…StBU8Vi (you) now f5b4613     │
│ I cannot wait to get back to the 90s! │
╰───────────────────────────────────────╯
-
$ rad patch comment 6ff4f09c1b5a81347981f59b02ef43a31a07cdae --message 'My favorite decade!' --reply-to efaf6fb -q
-
2cb22a1c87af86c25368c7be9fc385720fd6086f
+
$ rad patch comment 6ff4f09 --message 'My favorite decade!' --reply-to f5b4613 -q
+
611df66ccb3803b604a59f2efa9a42d72256dd49
```

Now, let's checkout the patch that we just created:
@@ -151,7 +154,7 @@ $ rad patch checkout 6ff4f09
We can also add a review verdict as such:

```
-
$ rad review 6ff4f09c1b5a81347981f59b02ef43a31a07cdae --accept --no-message --no-sync
+
$ rad review 6ff4f09 --accept --no-message --no-sync
✓ Patch 6ff4f09 accepted
```

@@ -175,7 +178,7 @@ $ rad patch show 6ff4f09
│ 3e674d1 Define power requirements                                   │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by z6MknSL…StBU8Vi (you) now                               │
-
│ ↑ updated to 873e637a66be511c45f4ef7b04fddc9def8f072c (27857ec) now │
+
│ ↑ updated to e0fd9f00b51e10e1ca88868e68e46e859ed371d7 (27857ec) now │
│ ✓ accepted by z6MknSL…StBU8Vi (you) now                             │
╰─────────────────────────────────────────────────────────────────────╯
```
@@ -201,7 +204,7 @@ $ rad patch show 6ff4f09
│ 3e674d1 Define power requirements                                   │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by z6MknSL…StBU8Vi (you) now                               │
-
│ ↑ updated to 873e637a66be511c45f4ef7b04fddc9def8f072c (27857ec) now │
+
│ ↑ updated to e0fd9f00b51e10e1ca88868e68e46e859ed371d7 (27857ec) now │
│ ✓ accepted by z6MknSL…StBU8Vi (you) now                             │
╰─────────────────────────────────────────────────────────────────────╯
```
modified radicle-cli/src/commands.rs
@@ -1,5 +1,3 @@
-
#[path = "commands/assign.rs"]
-
pub mod rad_assign;
#[path = "commands/auth.rs"]
pub mod rad_auth;
#[path = "commands/checkout.rs"]
@@ -46,7 +44,5 @@ pub mod rad_self;
pub mod rad_sync;
#[path = "commands/track.rs"]
pub mod rad_track;
-
#[path = "commands/unassign.rs"]
-
pub mod rad_unassign;
#[path = "commands/untrack.rs"]
pub mod rad_untrack;
deleted radicle-cli/src/commands/assign.rs
@@ -1,95 +0,0 @@
-
use std::ffi::OsString;
-

-
use anyhow::anyhow;
-
use nonempty::NonEmpty;
-

-
use radicle::cob;
-
use radicle::cob::issue;
-
use radicle::prelude::Did;
-
use radicle::storage::WriteStorage;
-

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

-
pub const HELP: Help = Help {
-
    name: "assign",
-
    description: "Assign an issue",
-
    version: env!("CARGO_PKG_VERSION"),
-
    usage: r#"
-
Usage
-

-
    rad assign <issue-id> --to <did> [<option>...]
-

-
    To assign multiple users to an issue, you may repeat
-
    the `--to` option.
-

-
    --to <did>    Assignee to add to the issue
-

-
Options
-

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

-
#[derive(Debug)]
-
pub struct Options {
-
    pub id: Rev,
-
    pub to: NonEmpty<Did>,
-
}
-

-
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 id: Option<Rev> = None;
-
        let mut to: Vec<Did> = Vec::new();
-

-
        while let Some(arg) = parser.next()? {
-
            match arg {
-
                Long("help") | Short('h') => {
-
                    return Err(Error::Help.into());
-
                }
-
                Long("to") => {
-
                    let val = parser.value()?;
-
                    let did = term::args::did(&val)?;
-

-
                    to.push(did);
-
                }
-
                Value(ref val) if id.is_none() => {
-
                    let val = string(val);
-
                    id = Some(Rev::from(val));
-
                }
-
                _ => {
-
                    return Err(anyhow!(arg.unexpected()));
-
                }
-
            }
-
        }
-

-
        Ok((
-
            Options {
-
                id: id.ok_or_else(|| anyhow!("an issue must be specified"))?,
-
                to: NonEmpty::from_vec(to)
-
                    .ok_or_else(|| anyhow!("an assignee must be specified"))?,
-
            },
-
            vec![],
-
        ))
-
    }
-
}
-

-
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
-
    let profile = ctx.profile()?;
-
    let (_, id) = radicle::rad::cwd()?;
-
    let repo = profile.storage.repository_mut(id)?;
-
    let oid = options.id.resolve(&repo.backend)?;
-
    let mut issues = issue::Issues::open(&repo)?;
-
    let mut issue = issues.get_mut(&oid).map_err(|e| match e {
-
        cob::store::Error::NotFound(_, _) => anyhow!("issue {} not found", options.id),
-
        _ => e.into(),
-
    })?;
-
    let signer = term::signer(&profile)?;
-

-
    issue.assign(options.to.into_iter().map(Did::into), &signer)?;
-
    Ok(())
-
}
modified radicle-cli/src/commands/help.rs
@@ -13,7 +13,6 @@ pub const HELP: Help = Help {
};

const COMMANDS: &[Help] = &[
-
    rad_assign::HELP,
    rad_auth::HELP,
    rad_checkout::HELP,
    rad_clone::HELP,
@@ -31,7 +30,6 @@ const COMMANDS: &[Help] = &[
    rad_clean::HELP,
    rad_self::HELP,
    rad_track::HELP,
-
    rad_unassign::HELP,
    rad_untrack::HELP,
    rad_remote::HELP,
    rad_sync::HELP,
modified radicle-cli/src/commands/issue.rs
@@ -1,4 +1,4 @@
-
#![allow(clippy::or_fun_call)]
+
use std::collections::BTreeSet;
use std::ffi::OsString;
use std::str::FromStr;

@@ -39,31 +39,41 @@ Usage
    rad issue list [--assigned <did>] [--all | --closed | --open | --solved] [<option>...]
    rad issue open [--title <title>] [--description <text>] [--label <label>] [<option>...]
    rad issue react <issue-id> [--emoji <char>] [--to <comment>] [<option>...]
+
    rad issue assign <issue-id> [--add <did>] [--remove <did>] [<option>...]
    rad issue label <issue-id> --label <label> [<option>...]
    rad issue unlabel <issue-id> --label <label> [<option>...]
    rad issue comment <issue-id> [--message <message>] [--reply-to <comment-id>] [<option>...]
    rad issue show <issue-id> [<option>...]
    rad issue state <issue-id> [--closed | --open | --solved] [<option>...]

+
Assign options
+

+
    -a, --add    <did>   Add an assignee to the issue (may be specified multiple times).
+
                         Note: --add will take precedence over --remove
+

+
    -r, --remove <did>   Remove an assignee from the issue (may be specified multiple times).
+
                         Note: --add will take precedence over --remove
+

Label options

-
    -l, --label       Label the issue with the provided label (may be specified multiple times)
+
    -l, --label          Label the issue with the provided label (may be specified multiple times)

Unlabel options

-
    -l, --label       Remove the provided label from the issue (may be specified multiple times)
+
    -l, --label          Remove the provided label from the issue (may be specified multiple times)

Options

-
    --no-announce     Don't announce issue to peers
-
    --header          Show only the issue header, hiding the comments
-
    --quiet, -q       Don't print anything
-
    --help            Print help
+
    --no-announce        Don't announce issue to peers
+
    --header             Show only the issue header, hiding the comments
+
    --quiet, -q          Don't print anything
+
    --help               Print help
"#,
};

#[derive(Default, Debug, PartialEq, Eq)]
pub enum OperationName {
+
    Assign,
    Edit,
    Open,
    Comment,
@@ -119,6 +129,10 @@ pub enum Operation {
        reaction: Reaction,
        comment_id: Option<thread::CommentId>,
    },
+
    Assign {
+
        id: Rev,
+
        opts: AssignOptions,
+
    },
    Label {
        id: Rev,
        labels: NonEmpty<Label>,
@@ -133,6 +147,12 @@ pub enum Operation {
    },
}

+
#[derive(Debug, Default, PartialEq, Eq)]
+
pub struct AssignOptions {
+
    pub add: BTreeSet<Did>,
+
    pub remove: BTreeSet<Did>,
+
}
+

#[derive(Debug)]
pub struct Options {
    pub op: Operation,
@@ -160,6 +180,7 @@ impl Args for Options {
        let mut reply_to = None;
        let mut announce = true;
        let mut quiet = false;
+
        let mut assign_opts = AssignOptions::default();

        while let Some(arg) = parser.next()? {
            match arg {
@@ -262,6 +283,17 @@ impl Args for Options {
                Long("no-announce") => {
                    announce = false;
                }
+

+
                // Assign options
+
                Short('a') | Long("add") if op == Some(OperationName::Assign) => {
+
                    assign_opts.add.insert(term::args::did(&parser.value()?)?);
+
                }
+
                Short('r') | Long("remove") if op == Some(OperationName::Assign) => {
+
                    assign_opts
+
                        .remove
+
                        .insert(term::args::did(&parser.value()?)?);
+
                }
+

                Long("quiet") | Short('q') => {
                    quiet = true;
                }
@@ -274,6 +306,7 @@ impl Args for Options {
                    "o" | "open" => op = Some(OperationName::Open),
                    "r" | "react" => op = Some(OperationName::React),
                    "s" | "state" => op = Some(OperationName::State),
+
                    "assign" => op = Some(OperationName::Assign),
                    "label" => op = Some(OperationName::Label),
                    "unlabel" => op = Some(OperationName::Unlabel),

@@ -322,6 +355,10 @@ impl Args for Options {
            OperationName::Delete => Operation::Delete {
                id: id.ok_or_else(|| anyhow!("an issue to remove must be provided"))?,
            },
+
            OperationName::Assign => Operation::Assign {
+
                id: id.ok_or_else(|| anyhow!("an issue to label must be provided"))?,
+
                opts: assign_opts,
+
            },
            OperationName::Label => Operation::Label {
                id: id.ok_or_else(|| anyhow!("an issue to label must be provided"))?,
                labels: NonEmpty::from_vec(labels)
@@ -358,6 +395,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
                | Operation::React { .. }
                | Operation::State { .. }
                | Operation::Delete { .. }
+
                | Operation::Assign { .. }
+
                | Operation::Label { .. }
+
                | Operation::Unlabel { .. }
        );

    let mut node = Node::new(profile.socket());
@@ -445,6 +485,22 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
                &profile,
            )?;
        }
+
        Operation::Assign {
+
            id,
+
            opts: AssignOptions { add, remove },
+
        } => {
+
            let id = id.resolve(&repo.backend)?;
+
            let Ok(mut issue) = issues.get_mut(&id) else {
+
                anyhow::bail!("Issue `{id}` not found");
+
            };
+
            let assignees = issue
+
                .assignees()
+
                .filter(|did| !remove.contains(did))
+
                .chain(add.iter())
+
                .cloned()
+
                .collect::<Vec<_>>();
+
            issue.assign(assignees, &signer)?;
+
        }
        Operation::Label { id, labels } => {
            let id = id.resolve(&repo.backend)?;
            let Ok(mut issue) = issues.get_mut(&id) else {
modified radicle-cli/src/commands/patch.rs
@@ -1,5 +1,7 @@
#[path = "patch/archive.rs"]
mod archive;
+
#[path = "patch/assign.rs"]
+
mod assign;
#[path = "patch/checkout.rs"]
mod checkout;
#[path = "patch/comment.rs"]
@@ -54,8 +56,9 @@ Usage
    rad patch checkout <patch-id> [<option>...]
    rad patch delete <patch-id> [<option>...]
    rad patch redact <revision-id> [<option>...]
-
    rad patch label <patch-id> --label <label> [<option>...]
-
    rad patch unlabel <patch-id> --label <label> [<option>...]
+
    rad patch assign <revision-id> [--add <did>] [--remove <did>] [<option>...]
+
    rad patch label <revision-id> --label <label> [<option>...]
+
    rad patch unlabel <revision-id> --label <label> [<option>...]
    rad patch ready <patch-id> [--undo] [<option>...]
    rad patch edit <patch-id> [<option>...]
    rad patch set <patch-id> [<option>...]
@@ -74,6 +77,14 @@ Edit options

    -m, --message [<string>]   Provide a comment message to the patch or revision (default: prompt)

+
Assign options
+

+
    -a, --add    <did>   Add an assignee to the issue (may be specified multiple times).
+
                         Note: --add will take precedence over --remove
+

+
    -r, --remove <did>   Remove an assignee from the issue (may be specified multiple times).
+
                         Note: --add will take precedence over --remove
+

Label options

    -l, --label                Label the patch with the provided label (may be specified multiple times)
@@ -117,6 +128,7 @@ Other options

#[derive(Debug, Default, PartialEq, Eq)]
pub enum OperationName {
+
    Assign,
    Show,
    Update,
    Archive,
@@ -133,6 +145,12 @@ pub enum OperationName {
    Set,
}

+
#[derive(Debug, Default, PartialEq, Eq)]
+
pub struct AssignOptions {
+
    pub add: BTreeSet<Did>,
+
    pub remove: BTreeSet<Did>,
+
}
+

pub struct Filter(fn(&patch::State) -> bool);

impl Filter {
@@ -184,6 +202,10 @@ pub enum Operation {
        message: Message,
        reply_to: Option<Rev>,
    },
+
    Assign {
+
        patch_id: Rev,
+
        opts: AssignOptions,
+
    },
    Label {
        patch_id: Rev,
        labels: NonEmpty<Label>,
@@ -239,6 +261,7 @@ impl Args for Options {
        let mut reply_to: Option<Rev> = None;
        let mut checkout_opts = checkout::Options::default();
        let mut labels = Vec::new();
+
        let mut assign_opts = AssignOptions::default();

        while let Some(arg) = parser.next()? {
            match arg {
@@ -309,6 +332,17 @@ impl Args for Options {
                    checkout_opts.name = Some(term::args::refstring("name", val)?);
                }

+
                // Assign options.
+
                Short('a') | Long("add") if matches!(op, Some(OperationName::Assign)) => {
+
                    assign_opts.add.insert(term::args::did(&parser.value()?)?);
+
                }
+

+
                Short('r') | Long("remove") if matches!(op, Some(OperationName::Assign)) => {
+
                    assign_opts
+
                        .remove
+
                        .insert(term::args::did(&parser.value()?)?);
+
                }
+

                // Un/Label options.
                Short('l') | Long("label")
                    if matches!(op, Some(OperationName::Label | OperationName::Unlabel)) =>
@@ -367,6 +401,7 @@ impl Args for Options {
                    "y" | "ready" => op = Some(OperationName::Ready),
                    "e" | "edit" => op = Some(OperationName::Edit),
                    "r" | "redact" => op = Some(OperationName::Redact),
+
                    "assign" => op = Some(OperationName::Assign),
                    "label" => op = Some(OperationName::Label),
                    "unlabel" => op = Some(OperationName::Unlabel),
                    "comment" => op = Some(OperationName::Comment),
@@ -389,6 +424,7 @@ impl Args for Options {
                            Some(OperationName::Comment),
                            Some(OperationName::Edit),
                            Some(OperationName::Set),
+
                            Some(OperationName::Assign),
                            Some(OperationName::Label),
                            Some(OperationName::Unlabel),
                        ]
@@ -439,6 +475,10 @@ impl Args for Options {
            OperationName::Redact => Operation::Redact {
                revision_id: revision_id.ok_or_else(|| anyhow!("a revision must be provided"))?,
            },
+
            OperationName::Assign => Operation::Assign {
+
                patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
+
                opts: assign_opts,
+
            },
            OperationName::Label => Operation::Label {
                patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
                labels: NonEmpty::from_vec(labels)
@@ -555,6 +595,13 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
        Operation::Redact { revision_id } => {
            redact::run(&revision_id, &profile, &repository)?;
        }
+
        Operation::Assign {
+
            patch_id,
+
            opts: AssignOptions { add, remove },
+
        } => {
+
            let patch_id = patch_id.resolve(&repository.backend)?;
+
            assign::run(&patch_id, add, remove, &profile, &repository)?;
+
        }
        Operation::Label { patch_id, labels } => {
            let patch_id = patch_id.resolve(&repository.backend)?;
            label::add(&patch_id, labels, &profile, &repository)?;
added radicle-cli/src/commands/patch/assign.rs
@@ -0,0 +1,29 @@
+
use std::collections::BTreeSet;
+

+
use super::*;
+

+
use radicle::prelude::Did;
+
use radicle::storage::git::Repository;
+

+
use crate::terminal as term;
+

+
pub fn run(
+
    patch_id: &PatchId,
+
    add: BTreeSet<Did>,
+
    remove: BTreeSet<Did>,
+
    profile: &Profile,
+
    repository: &Repository,
+
) -> anyhow::Result<()> {
+
    let signer = term::signer(profile)?;
+
    let mut patches = radicle::cob::patch::Patches::open(repository)?;
+
    let Ok(mut patch) = patches.get_mut(patch_id) else {
+
        anyhow::bail!("Patch `{patch_id}` not found");
+
    };
+
    let assignees = patch
+
        .assignees()
+
        .filter(|did| !remove.contains(did))
+
        .chain(add)
+
        .collect::<BTreeSet<_>>();
+
    patch.assign(assignees, &signer)?;
+
    Ok(())
+
}
deleted radicle-cli/src/commands/unassign.rs
@@ -1,98 +0,0 @@
-
use std::ffi::OsString;
-

-
use anyhow::anyhow;
-
use nonempty::NonEmpty;
-

-
use radicle::cob;
-
use radicle::cob::issue;
-
use radicle::prelude::Did;
-
use radicle::storage::WriteStorage;
-

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

-
pub const HELP: Help = Help {
-
    name: "unassign",
-
    description: "Unassign an issue",
-
    version: env!("CARGO_PKG_VERSION"),
-
    usage: r#"
-
Usage
-

-
    rad unassign <issue-id> --from <did> [<option>...]
-

-
    To unassign multiple users from an issue, you may repeat
-
    the `--from` option.
-

-
    --from <did>     Assignee to remove from the issue
-

-
Options
-

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

-
#[derive(Debug)]
-
pub struct Options {
-
    pub id: issue::IssueId,
-
    pub from: NonEmpty<Did>,
-
}
-

-
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 id: Option<issue::IssueId> = None;
-
        let mut from: Vec<Did> = Vec::new();
-

-
        while let Some(arg) = parser.next()? {
-
            match arg {
-
                Long("help") | Short('h') => {
-
                    return Err(Error::Help.into());
-
                }
-
                Long("from") => {
-
                    let val = parser.value()?;
-
                    let did = term::args::did(&val)?;
-

-
                    from.push(did);
-
                }
-
                Value(ref val) if id.is_none() => {
-
                    id = Some(term::args::issue(val)?);
-
                }
-
                _ => {
-
                    return Err(anyhow!(arg.unexpected()));
-
                }
-
            }
-
        }
-

-
        Ok((
-
            Options {
-
                id: id.ok_or_else(|| anyhow!("an issue must be specified"))?,
-
                from: NonEmpty::from_vec(from)
-
                    .ok_or_else(|| anyhow!("an assignee must be specified"))?,
-
            },
-
            vec![],
-
        ))
-
    }
-
}
-

-
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
-
    let profile = ctx.profile()?;
-
    let (_, id) = radicle::rad::cwd()?;
-
    let repo = profile.storage.repository_mut(id)?;
-
    let mut issues = issue::Issues::open(&repo)?;
-
    let mut issue = issues.get_mut(&options.id).map_err(|e| match e {
-
        cob::store::Error::NotFound(_, _) => anyhow!("issue {} not found", options.id),
-
        _ => e.into(),
-
    })?;
-
    let signer = term::signer(&profile)?;
-
    let assigned = issue
-
        .assignees()
-
        .cloned()
-
        .filter(|did| !options.from.contains(did))
-
        .collect::<Vec<_>>();
-

-
    issue.assign(assigned, &signer)?;
-

-
    Ok(())
-
}
modified radicle-cli/src/main.rs
@@ -97,13 +97,6 @@ fn run(command: Command) -> Result<(), Option<anyhow::Error>> {

fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>> {
    match exe {
-
        "assign" => {
-
            term::run_command_args::<rad_assign::Options, _>(
-
                rad_assign::HELP,
-
                rad_assign::run,
-
                args.to_vec(),
-
            );
-
        }
        "auth" => {
            term::run_command_args::<rad_auth::Options, _>(
                rad_auth::HELP,
@@ -250,13 +243,6 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
                args.to_vec(),
            );
        }
-
        "unassign" => {
-
            term::run_command_args::<rad_unassign::Options, _>(
-
                rad_unassign::HELP,
-
                rad_unassign::run,
-
                args.to_vec(),
-
            );
-
        }
        "untrack" => {
            term::run_command_args::<rad_untrack::Options, _>(
                rad_untrack::HELP,