cli: Add `--repo` option to `issue` and `patch`

Allows users to operate on a repo without a checkout.
This commit is contained in:
cloudhead 2024-02-23 18:16:52 +01:00
parent 449790e252
commit 0726754b0e
No known key found for this signature in database
5 changed files with 58 additions and 15 deletions

View File

@ -13,7 +13,7 @@ use radicle::cob::issue::{CloseReason, State};
use radicle::cob::thread; use radicle::cob::thread;
use radicle::crypto::Signer; use radicle::crypto::Signer;
use radicle::issue::cache::Issues as _; use radicle::issue::cache::Issues as _;
use radicle::prelude::Did; use radicle::prelude::{Did, RepoId};
use radicle::profile; use radicle::profile;
use radicle::storage; use radicle::storage;
use radicle::storage::{ReadRepository, WriteRepository, WriteStorage}; use radicle::storage::{ReadRepository, WriteRepository, WriteStorage};
@ -69,6 +69,7 @@ Show options
Options Options
--repo <rid> Operate on the given repository (default: cwd)
--no-announce Don't announce issue to peers --no-announce Don't announce issue to peers
--header Show only the issue header, hiding the comments --header Show only the issue header, hiding the comments
-q, --quiet Don't print anything -q, --quiet Don't print anything
@ -167,6 +168,7 @@ pub struct LabelOptions {
#[derive(Debug)] #[derive(Debug)]
pub struct Options { pub struct Options {
pub op: Operation, pub op: Operation,
pub repo: Option<RepoId>,
pub announce: bool, pub announce: bool,
pub quiet: bool, pub quiet: bool,
} }
@ -194,6 +196,7 @@ impl Args for Options {
let mut debug = false; let mut debug = false;
let mut assign_opts = AssignOptions::default(); let mut assign_opts = AssignOptions::default();
let mut label_opts = LabelOptions::default(); let mut label_opts = LabelOptions::default();
let mut repo = None;
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
@ -337,6 +340,12 @@ impl Args for Options {
Long("quiet") | Short('q') => { Long("quiet") | Short('q') => {
quiet = true; quiet = true;
} }
Long("repo") => {
let val = parser.value()?;
let rid = term::args::rid(&val)?;
repo = Some(rid);
}
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() { Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
"c" | "comment" => op = Some(OperationName::Comment), "c" | "comment" => op = Some(OperationName::Comment),
@ -412,6 +421,7 @@ impl Args for Options {
Ok(( Ok((
Options { Options {
op, op,
repo,
announce, announce,
quiet, quiet,
}, },
@ -423,7 +433,11 @@ impl Args for Options {
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 signer = term::signer(&profile)?; let signer = term::signer(&profile)?;
let (_, rid) = radicle::rad::cwd()?; let rid = if let Some(rid) = options.repo {
rid
} else {
radicle::rad::cwd().map(|(_, rid)| rid)?
};
let repo = profile.storage.repository_mut(rid)?; let repo = profile.storage.repository_mut(rid)?;
let announce = options.announce let announce = options.announce
&& matches!( && matches!(

View File

@ -16,10 +16,13 @@ pub fn run(id: Option<IssueId>, repository: &Repository, profile: &Profile) -> a
} }
None => issues.write_all(|result, progress| { None => issues.write_all(|result, progress| {
match result { match result {
Ok((id, _)) => term::success!("Successfully cached issue `{id}`"), Ok((id, _)) => term::success!(
"Successfully cached issue {id} ({}/{})",
progress.seen(),
progress.total()
),
Err(e) => term::warning(format!("Failed to retrieve issue: {e}")), Err(e) => term::warning(format!("Failed to retrieve issue: {e}")),
}; };
term::info!("Cached {} of {}", progress.seen(), progress.total());
ControlFlow::Continue(()) ControlFlow::Continue(())
})?, })?,
} }

View File

@ -145,6 +145,7 @@ Checkout options
Other options Other options
--repo <rid> Operate on the given repository (default: cwd)
--[no-]announce Announce changes made to the network --[no-]announce Announce changes made to the network
-q, --quiet Quiet output -q, --quiet Quiet output
--help Print help --help Print help
@ -278,6 +279,7 @@ impl Operation {
#[derive(Debug)] #[derive(Debug)]
pub struct Options { pub struct Options {
pub op: Operation, pub op: Operation,
pub repo: Option<RepoId>,
pub announce: bool, pub announce: bool,
pub verbose: bool, pub verbose: bool,
pub quiet: bool, pub quiet: bool,
@ -309,6 +311,7 @@ impl Args for Options {
let mut label_opts = LabelOptions::default(); let mut label_opts = LabelOptions::default();
let mut review_op = review::Operation::default(); let mut review_op = review::Operation::default();
let mut base_id = None; let mut base_id = None;
let mut repo = None;
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
@ -503,6 +506,12 @@ impl Args for Options {
Long("quiet") | Short('q') => { Long("quiet") | Short('q') => {
quiet = true; quiet = true;
} }
Long("repo") => {
let val = parser.value()?;
let rid = term::args::rid(&val)?;
repo = Some(rid);
}
Long("help") => { Long("help") => {
return Err(Error::HelpManual { name: "rad-patch" }.into()); return Err(Error::HelpManual { name: "rad-patch" }.into());
} }
@ -631,6 +640,7 @@ impl Args for Options {
Ok(( Ok((
Options { Options {
op, op,
repo,
verbose, verbose,
quiet, quiet,
announce, announce,
@ -643,11 +653,16 @@ impl Args for Options {
} }
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let (workdir, id) = radicle::rad::cwd() let (workdir, rid) = if let Some(rid) = options.repo {
.map_err(|_| anyhow!("this command must be run in the context of a repository"))?; (None, rid)
} else {
radicle::rad::cwd()
.map(|(workdir, rid)| (Some(workdir), rid))
.map_err(|_| anyhow!("this command must be run in the context of a repository"))?
};
let profile = ctx.profile()?; let profile = ctx.profile()?;
let repository = profile.storage.repository(id)?; let repository = profile.storage.repository(rid)?;
let announce = options.announce && options.op.is_announce(); let announce = options.announce && options.op.is_announce();
transport::local::register(profile.storage.clone()); transport::local::register(profile.storage.clone());
@ -673,7 +688,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
options.verbose, options.verbose,
&profile, &profile,
&repository, &repository,
&workdir, workdir.as_ref(),
)?; )?;
} }
Operation::Diff { Operation::Diff {
@ -697,6 +712,10 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
.as_ref() .as_ref()
.map(|base| base.resolve(&repository.backend)) .map(|base| base.resolve(&repository.backend))
.transpose()?; .transpose()?;
let workdir = workdir.ok_or(anyhow!(
"this command must be run from a repository checkout"
))?;
update::run( update::run(
patch_id, patch_id,
base_id, base_id,
@ -728,6 +747,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
.map(|rev| rev.resolve::<radicle::git::Oid>(&repository.backend)) .map(|rev| rev.resolve::<radicle::git::Oid>(&repository.backend))
.transpose()? .transpose()?
.map(patch::RevisionId::from); .map(patch::RevisionId::from);
let workdir = workdir.ok_or(anyhow!(
"this command must be run from a repository checkout"
))?;
checkout::run( checkout::run(
&patch::PatchId::from(patch_id), &patch::PatchId::from(patch_id),
revision_id, revision_id,
@ -798,7 +820,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let patch = patches let patch = patches
.get(&patch_id)? .get(&patch_id)?
.ok_or_else(|| anyhow!("patch {patch_id} not found"))?; .ok_or_else(|| anyhow!("patch {patch_id} not found"))?;
let workdir = workdir.ok_or(anyhow!(
"this command must be run from a repository checkout"
))?;
radicle::rad::setup_patch_upstream(&patch_id, *patch.head(), &workdir, true)?; radicle::rad::setup_patch_upstream(&patch_id, *patch.head(), &workdir, true)?;
} }
Operation::Cache { patch_id } => { Operation::Cache { patch_id } => {
@ -811,7 +835,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
if announce { if announce {
let mut node = Node::new(profile.socket()); let mut node = Node::new(profile.socket());
node::announce(id, &mut node)?; node::announce(rid, &mut node)?;
} }
Ok(()) Ok(())
} }

View File

@ -16,10 +16,13 @@ pub fn run(id: Option<PatchId>, repository: &Repository, profile: &Profile) -> a
} }
None => patches.write_all(|result, progress| { None => patches.write_all(|result, progress| {
match result { match result {
Ok((id, _)) => term::success!("Successfully cached patch `{id}`"), Ok((id, _)) => term::success!(
"Successfully cached patch {id} ({}/{})",
progress.seen(),
progress.total()
),
Err(e) => term::warning(format!("Failed to retrieve patch: {e}")), Err(e) => term::warning(format!("Failed to retrieve patch: {e}")),
}; };
term::info!("Cached {} of {}", progress.seen(), progress.total());
ControlFlow::Continue(()) ControlFlow::Continue(())
})?, })?,
} }

View File

@ -30,8 +30,7 @@ pub fn run(
verbose: bool, verbose: bool,
profile: &Profile, profile: &Profile,
stored: &Repository, stored: &Repository,
// TODO: Should be optional. workdir: Option<&git::raw::Repository>,
workdir: &git::raw::Repository,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let patches = profile.patches(stored)?; let patches = profile.patches(stored)?;
let Some(patch) = patches.get(patch_id)? else { let Some(patch) = patches.get(patch_id)? else {
@ -42,7 +41,7 @@ pub fn run(
println!("{:#?}", patch); println!("{:#?}", patch);
return Ok(()); return Ok(());
} }
term::patch::show(&patch, patch_id, verbose, stored, Some(workdir), profile)?; term::patch::show(&patch, patch_id, verbose, stored, workdir, profile)?;
if diff { if diff {
term::blank(); term::blank();