diff --git a/radicle-cli/examples/rad-patch.md b/radicle-cli/examples/rad-patch.md index 71cfec9d..8696eac7 100644 --- a/radicle-cli/examples/rad-patch.md +++ b/radicle-cli/examples/rad-patch.md @@ -62,4 +62,16 @@ define power requirements b9bb418c6f5 R0 9dad201 (flux-capacitor-power) ahead 1, Nothing to show. +$ rad patch show b9bb418c6f504ee91e54c555bdc8fc37b4d9b28b + +patch b9bb418c6f504ee91e54c555bdc8fc37b4d9b28b + +╭─ define power requirements ─────── + +No description provided. + +╰─────────────────────────────────── + +9dad201 Define power requirements + ``` diff --git a/radicle-cli/src/commands/patch.rs b/radicle-cli/src/commands/patch.rs index 4bb644ef..1e818e7c 100644 --- a/radicle-cli/src/commands/patch.rs +++ b/radicle-cli/src/commands/patch.rs @@ -4,6 +4,8 @@ mod common; mod create; #[path = "patch/list.rs"] mod list; +#[path = "patch/show.rs"] +mod show; use std::ffi::OsString; use std::str::FromStr; @@ -50,9 +52,19 @@ pub enum OptPatch { Patch(PatchId), } +impl From for Option { + fn from(opt: OptPatch) -> Self { + match opt { + OptPatch::Patch(patch_id) => Some(patch_id), + _ => None, + } + } +} + #[derive(Debug, Default, PartialEq, Eq)] pub enum OperationName { Open, + Show, Update, #[default] List, @@ -63,6 +75,9 @@ pub enum Operation { Open { message: Comment, }, + Show { + patch_id: PatchId, + }, Update { patch_id: OptPatch, message: Comment, @@ -79,6 +94,14 @@ pub struct Options { pub verbose: bool, } +fn parse_patch_id(val: OsString) -> Result { + let val = val + .to_str() + .ok_or_else(|| anyhow!("patch id specified is not UTF-8"))?; + let patch_id = PatchId::from_str(val).map_err(|_| anyhow!("invalid patch id '{}'", val))?; + Ok(OptPatch::Patch(patch_id)) +} + impl Args for Options { fn from_args(args: Vec) -> anyhow::Result<(Self, Vec)> { use lexopt::prelude::*; @@ -88,7 +111,7 @@ impl Args for Options { let mut op: Option = None; let mut verbose = false; let mut sync = true; - let mut id = OptPatch::default(); + let mut patch_id = OptPatch::default(); let mut message = Comment::default(); let mut push = true; @@ -137,19 +160,16 @@ impl Args for Options { Value(val) if op.is_none() => match val.to_string_lossy().as_ref() { "l" | "list" => op = Some(OperationName::List), "o" | "open" => op = Some(OperationName::Open), + "s" | "show" => op = Some(OperationName::Show), "u" | "update" => op = Some(OperationName::Update), unknown => anyhow::bail!("unknown operation '{}'", unknown), }, - Value(val) if op == Some(OperationName::Update) && id == OptPatch::Any => { - let val = val - .to_str() - .ok_or_else(|| anyhow!("patch id specified is not UTF-8"))?; - - id = OptPatch::Patch( - PatchId::from_str(val) - .map_err(|_| anyhow!("invalid patch id '{}'", val))?, - ); + Value(val) if op == Some(OperationName::Show) && patch_id == OptPatch::Any => { + patch_id = parse_patch_id(val)?; + } + Value(val) if op == Some(OperationName::Update) && patch_id == OptPatch::Any => { + patch_id = parse_patch_id(val)?; } _ => return Err(anyhow::anyhow!(arg.unexpected())), } @@ -158,10 +178,11 @@ impl Args for Options { let op = match op.unwrap_or_default() { OperationName::Open => Operation::Open { message }, OperationName::List => Operation::List, - OperationName::Update => Operation::Update { - patch_id: id, - message, + OperationName::Show => Operation::Show { + patch_id: Option::from(patch_id) + .ok_or_else(|| anyhow!("a patch id must be provided"))?, }, + OperationName::Update => Operation::Update { patch_id, message }, }; Ok(( @@ -198,6 +219,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { Operation::List => { list::run(&storage, &profile, Some(workdir), options)?; } + Operation::Show { ref patch_id } => { + show::run(&storage, &profile, &workdir, patch_id)?; + } Operation::Update { ref patch_id, ref message, diff --git a/radicle-cli/src/commands/patch/create.rs b/radicle-cli/src/commands/patch/create.rs index 95b96c6f..5efb0313 100644 --- a/radicle-cli/src/commands/patch/create.rs +++ b/radicle-cli/src/commands/patch/create.rs @@ -238,23 +238,8 @@ pub fn run( anyhow::bail!("a title must be given"); } - let title_pretty = &term::format::dim(format!("╭─ {} ───────", title)); - term::blank(); - term::print(title_pretty); - term::blank(); - - if description.is_empty() { - term::print(term::format::italic("No description provided.")); - } else { - term::markdown(&description); - } - - term::blank(); - term::print(term::format::dim(format!( - "╰{}", - "─".repeat(term::text_width(title_pretty) - 1) - ))); + term::patch::print_title_desc(title, &description); term::blank(); if !confirm("Create patch?", &options) { diff --git a/radicle-cli/src/commands/patch/show.rs b/radicle-cli/src/commands/patch/show.rs new file mode 100644 index 00000000..884dfbeb --- /dev/null +++ b/radicle-cli/src/commands/patch/show.rs @@ -0,0 +1,35 @@ +use super::common::*; +use super::*; + +use crate::terminal as term; +use radicle::cob::patch; +use radicle::git; +use radicle::prelude::*; +use radicle::storage::git::Repository; + +pub fn run( + storage: &Repository, + profile: &Profile, + workdir: &git::raw::Repository, + patch_id: &PatchId, +) -> anyhow::Result<()> { + let patches = patch::Patches::open(profile.public_key, storage)?; + let Some(patch) = patches.get(patch_id)? else { + anyhow::bail!("Patch `{}` not found", patch_id); + }; + + term::blank(); + term::print(format!("patch {}", patch_id)); + term::blank(); + + term::patch::print_title_desc(patch.title(), patch.description().unwrap_or("")); + term::blank(); + + let target_head = patch_merge_target_oid(patch.target(), storage)?; + let base_oid = workdir.merge_base(target_head, **patch.head())?; + let commits = patch_commits(workdir, &base_oid, patch.head())?; + term::patch::list_commits(&commits)?; + term::blank(); + + Ok(()) +} diff --git a/radicle-cli/src/terminal/patch.rs b/radicle-cli/src/terminal/patch.rs index e1fe73a0..d74c165f 100644 --- a/radicle-cli/src/terminal/patch.rs +++ b/radicle-cli/src/terminal/patch.rs @@ -84,3 +84,22 @@ pub fn print_commits_ahead_behind( ); Ok(()) } + +/// Print title and description in a text box. +pub fn print_title_desc(title: &str, description: &str) { + let title_pretty = &term::format::dim(format!("╭─ {} ───────", title)); + term::print(title_pretty); + term::blank(); + + if description.is_empty() { + term::print(term::format::italic("No description provided.")); + } else { + term::markdown(description); + } + + term::blank(); + term::print(term::format::dim(format!( + "╰{}", + "─".repeat(term::text_width(title_pretty) - 1) + ))); +}