cli: move rad assign/unassign under rad issue and rad patch
It is more natural for the assign and unassign commands to exist as subcommands of the object they are assigning to, i.e. issues and patches. Remove the rad assign and rad unassign commands, moving their functionality to the rad issue and rad patch commands. The rad-issue.md and rad-patch.md tests are modified to include a assign test. Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com> X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
parent
dce5316cbb
commit
d32a10b4b9
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 │
|
||||
╰─────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue