cli: Enable `rad tag` and `rad untag` for patches as well

Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
Sebastian Martinez 2023-03-23 11:31:38 +01:00
parent 564886b165
commit 8e3b629178
No known key found for this signature in database
2 changed files with 86 additions and 32 deletions

View File

@ -8,17 +8,18 @@ use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help}; use crate::terminal::args::{Args, Error, Help};
use radicle::cob; use radicle::cob;
use radicle::cob::common::Tag; use radicle::cob::common::Tag;
use radicle::cob::issue; use radicle::cob::{issue, patch, store};
use radicle::storage::WriteStorage; use radicle::crypto::Signer;
use radicle::storage::{self, WriteStorage};
pub const HELP: Help = Help { pub const HELP: Help = Help {
name: "tag", name: "tag",
description: "Tag an issue", description: "Tag an issue or patch",
version: env!("CARGO_PKG_VERSION"), version: env!("CARGO_PKG_VERSION"),
usage: r#" usage: r#"
Usage Usage
rad tag <issue> <tag>.. rad tag <id> <tag>..
Options Options
@ -28,7 +29,7 @@ Options
#[derive(Debug)] #[derive(Debug)]
pub struct Options { pub struct Options {
pub id: issue::IssueId, pub id: cob::ObjectId,
pub tags: NonEmpty<Tag>, pub tags: NonEmpty<Tag>,
} }
@ -37,7 +38,7 @@ impl Args for Options {
use lexopt::prelude::*; use lexopt::prelude::*;
let mut parser = lexopt::Parser::from_args(args); let mut parser = lexopt::Parser::from_args(args);
let mut id: Option<issue::IssueId> = None; let mut id: Option<cob::ObjectId> = None;
let mut tags: Vec<Tag> = Vec::new(); let mut tags: Vec<Tag> = Vec::new();
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
@ -47,8 +48,8 @@ impl Args for Options {
} }
Value(ref val) if id.is_none() => { Value(ref val) if id.is_none() => {
let val = val.to_string_lossy(); let val = val.to_string_lossy();
let Ok(val) = issue::IssueId::from_str(&val) else { let Ok(val) = cob::ObjectId::from_str(&val) else {
return Err(anyhow!("invalid Issue ID '{}'", val)); return Err(anyhow!("invalid issue or patch ID '{}'", val));
}; };
id = Some(val); id = Some(val);
} }
@ -66,26 +67,52 @@ impl Args for Options {
Ok(( Ok((
Options { Options {
id: id.ok_or_else(|| anyhow!("an issue must be specified"))?, id: id.ok_or_else(|| anyhow!("an issue or patch must be specified"))?,
tags: NonEmpty::from_vec(tags).ok_or_else(|| anyhow!("a tag must be specified"))?, tags: NonEmpty::from_vec(tags)
.ok_or_else(|| anyhow!("at least one tag must be specified"))?,
}, },
vec![], vec![],
)) ))
} }
} }
fn tag(
options: Options,
repo: &storage::git::Repository,
signer: impl Signer,
) -> anyhow::Result<()> {
let mut issues = issue::Issues::open(repo)?;
match issues.get_mut(&options.id) {
Ok(mut issue) => {
issue.tag(options.tags.into_iter(), [], &signer)?;
return Ok(());
}
Err(store::Error::NotFound(_, _)) => {}
Err(e) => return Err(e.into()),
}
let mut patches = patch::Patches::open(repo)?;
match patches.get_mut(&options.id) {
Ok(mut patch) => {
patch.tag(options.tags.into_iter(), [], &signer)?;
return Ok(());
}
Err(store::Error::NotFound(_, _)) => {}
Err(e) => return Err(e.into()),
}
anyhow::bail!("Couldn't find issue or patch {}", options.id)
}
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?; let profile = ctx.profile()?;
let (_, id) = radicle::rad::cwd()?; let (_, id) = radicle::rad::cwd()?;
let repo = profile.storage.repository_mut(id)?; 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 signer = term::signer(&profile)?;
issue.tag(options.tags.into_iter(), [], &signer)?; tag(options, &repo, signer)?;
Ok(()) Ok(())
} }

View File

@ -8,17 +8,18 @@ use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help}; use crate::terminal::args::{Args, Error, Help};
use radicle::cob; use radicle::cob;
use radicle::cob::common::Tag; use radicle::cob::common::Tag;
use radicle::cob::issue; use radicle::cob::{issue, patch, store};
use radicle::storage::WriteStorage; use radicle::crypto::Signer;
use radicle::storage::{self, WriteStorage};
pub const HELP: Help = Help { pub const HELP: Help = Help {
name: "untag", name: "untag",
description: "Untag an issue", description: "Untag an issue or patch",
version: env!("CARGO_PKG_VERSION"), version: env!("CARGO_PKG_VERSION"),
usage: r#" usage: r#"
Usage Usage
rad untag <issue> <tag>.. rad untag <id> <tag>..
Options Options
@ -28,7 +29,7 @@ Options
#[derive(Debug)] #[derive(Debug)]
pub struct Options { pub struct Options {
pub id: issue::IssueId, pub id: cob::ObjectId,
pub tags: NonEmpty<Tag>, pub tags: NonEmpty<Tag>,
} }
@ -37,7 +38,7 @@ impl Args for Options {
use lexopt::prelude::*; use lexopt::prelude::*;
let mut parser = lexopt::Parser::from_args(args); let mut parser = lexopt::Parser::from_args(args);
let mut id: Option<issue::IssueId> = None; let mut id: Option<cob::ObjectId> = None;
let mut tags: Vec<Tag> = Vec::new(); let mut tags: Vec<Tag> = Vec::new();
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
@ -47,8 +48,8 @@ impl Args for Options {
} }
Value(ref val) if id.is_none() => { Value(ref val) if id.is_none() => {
let val = val.to_string_lossy(); let val = val.to_string_lossy();
let Ok(val) = issue::IssueId::from_str(&val) else { let Ok(val) = cob::ObjectId::from_str(&val) else {
return Err(anyhow!("invalid Issue ID '{}'", val)); return Err(anyhow!("invalid issue or patch ID '{}'", val));
}; };
id = Some(val); id = Some(val);
} }
@ -66,26 +67,52 @@ impl Args for Options {
Ok(( Ok((
Options { Options {
id: id.ok_or_else(|| anyhow!("an issue must be specified"))?, id: id.ok_or_else(|| anyhow!("an issue or patch must be specified"))?,
tags: NonEmpty::from_vec(tags).ok_or_else(|| anyhow!("a tag must be specified"))?, tags: NonEmpty::from_vec(tags)
.ok_or_else(|| anyhow!("at least one tag must be specified"))?,
}, },
vec![], vec![],
)) ))
} }
} }
fn untag(
options: Options,
repo: &storage::git::Repository,
signer: impl Signer,
) -> anyhow::Result<()> {
let mut issues = issue::Issues::open(repo)?;
match issues.get_mut(&options.id) {
Ok(mut issue) => {
issue.tag([], options.tags.into_iter(), &signer)?;
return Ok(());
}
Err(store::Error::NotFound(_, _)) => {}
Err(e) => return Err(e.into()),
}
let mut patches = patch::Patches::open(repo)?;
match patches.get_mut(&options.id) {
Ok(mut patch) => {
patch.tag([], options.tags.into_iter(), &signer)?;
return Ok(());
}
Err(store::Error::NotFound(_, _)) => {}
Err(e) => return Err(e.into()),
}
anyhow::bail!("Couldn't find issue or patch {}", options.id)
}
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?; let profile = ctx.profile()?;
let (_, id) = radicle::rad::cwd()?; let (_, id) = radicle::rad::cwd()?;
let repo = profile.storage.repository_mut(id)?; 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 signer = term::signer(&profile)?;
issue.tag([], options.tags.into_iter(), &signer)?; untag(options, &repo, signer)?;
Ok(()) Ok(())
} }