From 654a21b2b38efb85742423538dee110fbc356f01 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Wed, 22 Mar 2023 14:35:17 +0100 Subject: [PATCH] cli: Allow git revision parameters as COB IDs This commit allows any git revision to be used instead of the full SHA-1, when specifying COBs. Signed-off-by: Vincenzo Palazzo --- radicle-cli/examples/rad-id.md | 2 +- radicle-cli/examples/rad-issue.md | 13 ++++++++ radicle-cli/examples/rad-patch.md | 2 +- radicle-cli/src/commands/assign.rs | 19 +++++------- radicle-cli/src/commands/comment.rs | 26 +++++++--------- radicle-cli/src/commands/id.rs | 38 +++++++++++++----------- radicle-cli/src/commands/issue.rs | 29 +++++++++--------- radicle-cli/src/commands/merge.rs | 21 +++++-------- radicle-cli/src/commands/patch.rs | 46 +++++++++++++++-------------- radicle-cli/src/commands/review.rs | 20 +++++-------- radicle-cli/src/git.rs | 31 +++++++++++++++++++ radicle-cli/src/terminal/args.rs | 4 +++ 12 files changed, 142 insertions(+), 109 deletions(-) diff --git a/radicle-cli/examples/rad-id.md b/radicle-cli/examples/rad-id.md index 6bf8e848..32b01dc5 100644 --- a/radicle-cli/examples/rad-id.md +++ b/radicle-cli/examples/rad-id.md @@ -145,7 +145,7 @@ increased to `1`. Instead, let's accept the proposal: ``` -$ rad id accept 0d396a83a5e1dda2b8929f7dc401d19dd1a79fb8 --no-confirm +$ rad id accept 0d396a --no-confirm ✓ Accepted proposal ✓ title: Add Bob description: Add Bob as a delegate diff --git a/radicle-cli/examples/rad-issue.md b/radicle-cli/examples/rad-issue.md index 6ac27cea..2adf1db1 100644 --- a/radicle-cli/examples/rad-issue.md +++ b/radicle-cli/examples/rad-issue.md @@ -20,6 +20,19 @@ $ rad issue list 2e8c1bf3fe0532a314778357c886608a966a34bd "flux capacitor underpowered" ❲unassigned❳ ``` +Show the issue information issue. + +``` +$ rad issue show 2e8c1bf +title: flux capacitor underpowered +state: open +tags: [] +assignees: [] + +Flux capacitor power requirements exceed current supply +``` + + Great! Now we've documented the issue for ourselves and others. Just like with other project management systems, the issue can be assigned to diff --git a/radicle-cli/examples/rad-patch.md b/radicle-cli/examples/rad-patch.md index 00e06fbd..2d726177 100644 --- a/radicle-cli/examples/rad-patch.md +++ b/radicle-cli/examples/rad-patch.md @@ -111,7 +111,7 @@ $ rad comment 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 --message 'I cannot wait Now, let's checkout the patch that we just created: ``` -$ rad patch checkout 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 +$ rad patch checkout 191a14e52 ✓ Switched to branch patch/191a14e520f ``` diff --git a/radicle-cli/src/commands/assign.rs b/radicle-cli/src/commands/assign.rs index 5e1f09f6..d45a174c 100644 --- a/radicle-cli/src/commands/assign.rs +++ b/radicle-cli/src/commands/assign.rs @@ -1,12 +1,12 @@ use std::ffi::OsString; -use std::str::FromStr; use anyhow::anyhow; use nonempty::NonEmpty; use radicle::prelude::Did; +use crate::git::Rev; use crate::terminal as term; -use crate::terminal::args::{Args, Error, Help}; +use crate::terminal::args::{string, Args, Error, Help}; use radicle::cob; use radicle::cob::issue; use radicle::storage::WriteStorage; @@ -32,7 +32,7 @@ Options #[derive(Debug)] pub struct Options { - pub id: issue::IssueId, + pub id: Rev, pub to: NonEmpty, } @@ -41,7 +41,7 @@ impl Args for Options { use lexopt::prelude::*; let mut parser = lexopt::Parser::from_args(args); - let mut id: Option = None; + let mut id: Option = None; let mut to: Vec = Vec::new(); while let Some(arg) = parser.next()? { @@ -56,11 +56,8 @@ impl Args for Options { to.push(did); } Value(ref val) if id.is_none() => { - let val = val.to_string_lossy(); - let Ok(val) = issue::IssueId::from_str(&val) else { - return Err(anyhow!("invalid Issue ID '{}'", val)); - }; - id = Some(val); + let val = string(val); + id = Some(Rev::from(val)); } _ => { return Err(anyhow!(arg.unexpected())); @@ -83,14 +80,14 @@ 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(&options.id).map_err(|e| match e { + 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(()) } diff --git a/radicle-cli/src/commands/comment.rs b/radicle-cli/src/commands/comment.rs index 3d8b79a5..9ac1a908 100644 --- a/radicle-cli/src/commands/comment.rs +++ b/radicle-cli/src/commands/comment.rs @@ -1,9 +1,7 @@ use std::ffi::OsString; -use std::str::FromStr; use anyhow::anyhow; -use radicle::cob; use radicle::cob::issue::Issues; use radicle::cob::patch::Patches; use radicle::cob::store; @@ -11,7 +9,9 @@ use radicle::cob::thread; use radicle::prelude::*; use radicle::storage; +use crate::git::Rev; use crate::terminal as term; +use crate::terminal::args::string; use crate::terminal::args::{Args, Error, Help}; use crate::terminal::patch::Message; @@ -34,25 +34,17 @@ Options #[derive(Debug)] pub struct Options { - pub id: cob::ObjectId, + pub id: Rev, pub message: Message, pub reply_to: Option, } -#[inline] -fn parse_cob_id(val: OsString) -> anyhow::Result { - let val = val - .to_str() - .ok_or_else(|| anyhow!("object id specified is not UTF-8"))?; - cob::ObjectId::from_str(val).map_err(|_| anyhow!("invalid object id '{}'", val)) -} - impl Args for Options { fn from_args(args: Vec) -> anyhow::Result<(Self, Vec)> { use lexopt::prelude::*; let mut parser = lexopt::Parser::from_args(args); - let mut id: Option = None; + let mut id: Option = None; let mut message = Message::default(); let mut reply_to = None; @@ -76,7 +68,10 @@ impl Args for Options { // Common. Long("help") => return Err(Error::Help.into()), - Value(val) if id.is_none() => id = Some(parse_cob_id(val)?), + Value(val) if id.is_none() => { + let val = string(&val); + id = Some(Rev::from(val)); + } _ => return Err(anyhow::anyhow!(arg.unexpected())), } } @@ -103,7 +98,8 @@ fn comment( } let mut issues = Issues::open(repo)?; - match issues.get_mut(&options.id) { + let id = options.id.resolve(&repo.backend)?; + match issues.get_mut(&id) { Ok(mut issue) => { let comment_id = options.reply_to.unwrap_or_else(|| { let (comment_id, _) = issue.comments().next().expect("root comment always exists"); @@ -119,7 +115,7 @@ fn comment( } let mut patches = Patches::open(repo)?; - match patches.get_mut(&options.id) { + match patches.get_mut(&id) { Ok(mut patch) => { let (revision_id, _) = patch.revisions().last().expect("patch has a revision"); let comment_id = patch.comment(*revision_id, message, options.reply_to, &signer)?; diff --git a/radicle-cli/src/commands/id.rs b/radicle-cli/src/commands/id.rs index d1914d45..8acd022e 100644 --- a/radicle-cli/src/commands/id.rs +++ b/radicle-cli/src/commands/id.rs @@ -1,15 +1,16 @@ use std::{ffi::OsString, str::FromStr as _}; use anyhow::{anyhow, Context as _}; -use radicle::cob::identity::{self, Proposal, ProposalId, Proposals, Revision, RevisionId}; +use radicle::cob::identity::{self, Proposal, Proposals, Revision, RevisionId}; use radicle::git::Oid; use radicle::identity::Identity; use radicle::prelude::{Did, Doc}; use radicle::storage::ReadStorage as _; use radicle_crypto::Verified; +use crate::git::Rev; use crate::terminal as term; -use crate::terminal::args::{Args, Error, Help}; +use crate::terminal::args::{string, Args, Error, Help}; use crate::terminal::Element; use crate::terminal::Interactive; @@ -53,11 +54,11 @@ impl Metadata { #[derive(Clone, Debug, Default)] pub enum Operation { Accept { - id: ProposalId, + id: Rev, rev: Option, }, Reject { - id: ProposalId, + id: Rev, rev: Option, }, Edit { @@ -67,7 +68,7 @@ pub enum Operation { threshold: Option, }, Update { - id: ProposalId, + id: Rev, rev: Option, title: Option, description: Option, @@ -75,22 +76,22 @@ pub enum Operation { threshold: Option, }, Rebase { - id: ProposalId, + id: Rev, rev: Option, }, Show { - id: ProposalId, + id: Rev, rev: Option, show_revisions: bool, }, #[default] List, Commit { - id: ProposalId, + id: Rev, rev: Option, }, Close { - id: ProposalId, + id: Rev, }, } @@ -119,7 +120,7 @@ impl Args for Options { let mut parser = lexopt::Parser::from_args(args); let mut op: Option = None; - let mut id: Option = None; + let mut id: Option = None; let mut rev: Option = None; let mut title: Option = None; let mut description: Option = None; @@ -173,14 +174,8 @@ impl Args for Options { show_revisions = true; } Value(val) if op.is_some() => { - let val = val - .to_str() - .ok_or_else(|| anyhow!("proposal id specified is not UTF-8"))?; - - id = Some( - ProposalId::from_str(val) - .map_err(|_| anyhow!("invalid proposal id '{}'", val))?, - ); + let val = string(&val); + id = Some(Rev::from(val)); } _ => { return Err(anyhow!(arg.unexpected())); @@ -245,6 +240,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { let interactive = &options.interactive; match options.op { Operation::Accept { id, rev } => { + let id = id.resolve(&repo.backend)?; let mut proposal = proposals.get_mut(&id)?; let (rid, revision) = select(&proposal, rev, &previous, interactive)?; warn_out_of_date(revision, &previous); @@ -257,6 +253,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { } } Operation::Reject { id, rev } => { + let id = id.resolve(&repo.backend)?; let mut proposal = proposals.get_mut(&id)?; let (rid, revision) = select(&proposal, rev, &previous, interactive)?; warn_out_of_date(revision, &previous); @@ -311,6 +308,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { delegates, threshold, } => { + let id = id.resolve(&repo.backend)?; let mut proposal = proposals.get_mut(&id)?; let (_, revision) = select(&proposal, rev, &previous, interactive)?; @@ -354,6 +352,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { } } Operation::Rebase { id, rev } => { + let id = id.resolve(&repo.backend)?; // TODO: it would be nice if rebasing also handled fast-forwards nicely. let mut proposal = proposals.get_mut(&id)?; let (_, revision) = select(&proposal, rev, &previous, interactive)?; @@ -407,6 +406,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { t.print(); } Operation::Commit { id, rev } => { + let id = id.resolve(&repo.backend)?; let mut proposal = proposals.get_mut(&id)?; let (rid, revision) = commit_select(&proposal, rev, &previous, interactive)?; warn_out_of_date(revision, &previous); @@ -419,6 +419,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { } } Operation::Close { id } => { + let id = id.resolve(&repo.backend)?; let mut proposal = proposals.get_mut(&id)?; let yes = confirm(interactive, "Are you sure you want to close?"); if yes { @@ -432,6 +433,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { rev, show_revisions, } => { + let id = id.resolve(&repo.backend)?; let proposal = proposals .get(&id)? .context("No proposal with the given ID exists")?; diff --git a/radicle-cli/src/commands/issue.rs b/radicle-cli/src/commands/issue.rs index d80760e9..ab7bf2be 100644 --- a/radicle-cli/src/commands/issue.rs +++ b/radicle-cli/src/commands/issue.rs @@ -6,13 +6,14 @@ use anyhow::{anyhow, Context as _}; use radicle::node::Handle; use radicle::prelude::Did; +use crate::git::Rev; use crate::terminal as term; -use crate::terminal::args::{Args, Error, Help}; +use crate::terminal::args::{string, Args, Error, Help}; use crate::terminal::Element; use radicle::cob::common::{Reaction, Tag}; use radicle::cob::issue; -use radicle::cob::issue::{CloseReason, IssueId, Issues, State}; +use radicle::cob::issue::{CloseReason, Issues, State}; use radicle::storage::WriteStorage; use radicle::{cob, Node}; @@ -71,17 +72,17 @@ pub enum Operation { description: Option, }, Show { - id: IssueId, + id: Rev, }, State { - id: IssueId, + id: Rev, state: State, }, Delete { - id: IssueId, + id: Rev, }, React { - id: IssueId, + id: Rev, reaction: Reaction, }, List { @@ -101,7 +102,7 @@ impl Args for Options { let mut parser = lexopt::Parser::from_args(args); let mut op: Option = None; - let mut id: Option = None; + let mut id: Option = None; let mut assigned: Option = None; let mut title: Option = None; let mut reaction: Option = None; @@ -161,14 +162,8 @@ impl Args for Options { unknown => anyhow::bail!("unknown operation '{}'", unknown), }, Value(val) if op.is_some() => { - let val = val - .to_str() - .ok_or_else(|| anyhow!("issue id specified is not UTF-8"))?; - - id = Some( - IssueId::from_str(val) - .map_err(|_| anyhow!("invalid issue id '{}'", val))?, - ); + let val = string(&val); + id = Some(Rev::from(val)); } _ => { return Err(anyhow!(arg.unexpected())); @@ -225,16 +220,19 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { show_issue(&issue)?; } Operation::Show { id } => { + let id = id.resolve(&repo.backend)?; let issue = issues .get(&id)? .context("No issue with the given ID exists")?; show_issue(&issue)?; } Operation::State { id, state } => { + let id = id.resolve(&repo.backend)?; let mut issue = issues.get_mut(&id)?; issue.lifecycle(state, &signer)?; } Operation::React { id, reaction } => { + let id = id.resolve(&repo.backend)?; if let Ok(mut issue) = issues.get_mut(&id) { let (comment_id, _) = term::io::comment_select(&issue).unwrap(); issue.react(*comment_id, reaction, &signer)?; @@ -325,6 +323,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { t.print(); } Operation::Delete { id } => { + let id = id.resolve(&repo.backend)?; issues.remove(&id, &signer)?; } } diff --git a/radicle-cli/src/commands/merge.rs b/radicle-cli/src/commands/merge.rs index 9691ebad..81d0241f 100644 --- a/radicle-cli/src/commands/merge.rs +++ b/radicle-cli/src/commands/merge.rs @@ -5,8 +5,9 @@ use std::str::FromStr; use anyhow::{anyhow, Context}; +use crate::git::Rev; use crate::terminal as term; -use crate::terminal::args::{Args, Error, Help}; +use crate::terminal::args::{string, Args, Error, Help}; use radicle::cob::patch::RevisionIx; use radicle::cob::patch::{Patch, PatchId, Patches}; use radicle::git; @@ -72,7 +73,7 @@ pub enum State { } #[derive(Debug)] pub struct Options { - pub id: PatchId, + pub id: Rev, pub interactive: bool, pub revision: Option, } @@ -82,7 +83,7 @@ impl Args for Options { use lexopt::prelude::*; let mut parser = lexopt::Parser::from_args(args); - let mut id: Option = None; + let mut id: Option = None; let mut revision: Option = None; let mut interactive = false; @@ -103,14 +104,8 @@ impl Args for Options { revision = Some(id); } Value(val) => { - let val = val - .to_str() - .ok_or_else(|| anyhow!("patch id specified is not UTF-8"))?; - - id = Some( - PatchId::from_str(val) - .map_err(|_| anyhow!("invalid patch id '{}'", val))?, - ); + let val = string(&val); + id = Some(Rev::from(val)); } _ => return Err(anyhow::anyhow!(arg.unexpected())), } @@ -149,10 +144,10 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { // // Get patch information // - let patch_id = options.id; + let patch_id = options.id.resolve(&repository.backend)?; let mut patch = patches .get_mut(&patch_id) - .map_err(|e| anyhow!("couldn't find patch {} locally: {e}", &options.id))?; + .map_err(|e| anyhow!("couldn't find patch {} locally: {e}", &options.id.clone()))?; let head = repo.head()?; let branch = head diff --git a/radicle-cli/src/commands/patch.rs b/radicle-cli/src/commands/patch.rs index 2676f3c1..a15f7f95 100644 --- a/radicle-cli/src/commands/patch.rs +++ b/radicle-cli/src/commands/patch.rs @@ -21,8 +21,9 @@ use radicle::cob::patch::PatchId; use radicle::{prelude::*, Node}; use crate::commands::rad_fetch as fetch; +use crate::git::Rev; use crate::terminal as term; -use crate::terminal::args::{Args, Error, Help}; +use crate::terminal::args::{string, Args, Error, Help}; use crate::terminal::patch::Message; pub const HELP: Help = Help { @@ -70,17 +71,17 @@ pub enum Operation { message: Message, }, Show { - patch_id: PatchId, + patch_id: Rev, }, Update { - patch_id: Option, + patch_id: Option, message: Message, }, Delete { - patch_id: PatchId, + patch_id: Rev, }, Checkout { - patch_id: PatchId, + patch_id: Rev, }, List, } @@ -113,7 +114,7 @@ impl Args for Options { Long("message") | Short('m') => { if message != Message::Blank { // We skip this code when `no-message` is specified. - let txt: String = parser.value()?.to_string_lossy().into(); + let txt: String = term::args::string(&parser.value()?); message.append(&txt); } } @@ -156,17 +157,11 @@ impl Args for Options { "c" | "checkout" => op = Some(OperationName::Checkout), unknown => anyhow::bail!("unknown operation '{}'", unknown), }, - Value(val) if op == Some(OperationName::Show) && patch_id.is_none() => { - patch_id = Some(term::cob::parse_patch_id(val)?); - } - Value(val) if op == Some(OperationName::Update) && patch_id.is_none() => { - patch_id = Some(term::cob::parse_patch_id(val)?); - } - Value(val) if op == Some(OperationName::Checkout) && patch_id.is_none() => { - patch_id = Some(term::cob::parse_patch_id(val)?); - } - Value(val) if op == Some(OperationName::Delete) && patch_id.is_none() => { - patch_id = Some(term::cob::parse_patch_id(val)?); + Value(val) + if op.is_some() && op != Some(OperationName::List) && patch_id.is_none() => + { + let val = string(&val); + patch_id = Some(Rev::from(val)); } _ => return Err(anyhow::anyhow!(arg.unexpected())), } @@ -218,13 +213,18 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { Operation::List => { list::run(&repository, &profile, Some(workdir))?; } - Operation::Show { ref patch_id } => { - show::run(&repository, &workdir, patch_id)?; + Operation::Show { patch_id } => { + let patch_id = patch_id.resolve(&repository.backend)?; + show::run(&repository, &workdir, &patch_id)?; } Operation::Update { - patch_id, + ref patch_id, ref message, } => { + let patch_id = patch_id + .as_ref() + .map(|id| id.resolve(&repository.backend)) + .transpose()?; update::run( &repository, &profile, @@ -235,10 +235,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { )?; } Operation::Delete { patch_id } => { + let patch_id = patch_id.resolve(&repository.backend)?; delete::run(&repository, &profile, &patch_id)?; } - Operation::Checkout { ref patch_id } => { - checkout::run(&repository, &workdir, patch_id)?; + Operation::Checkout { patch_id } => { + let patch_id = patch_id.resolve(&repository.backend)?; + checkout::run(&repository, &workdir, &patch_id)?; } } Ok(()) diff --git a/radicle-cli/src/commands/review.rs b/radicle-cli/src/commands/review.rs index 97499ede..fb7feb1e 100644 --- a/radicle-cli/src/commands/review.rs +++ b/radicle-cli/src/commands/review.rs @@ -3,12 +3,13 @@ use std::str::FromStr; use anyhow::{anyhow, Context}; -use radicle::cob::patch::{PatchId, Patches, RevisionIx, Verdict}; +use radicle::cob::patch::{Patches, RevisionIx, Verdict}; use radicle::prelude::*; use radicle::rad; +use crate::git::Rev; use crate::terminal as term; -use crate::terminal::args::{Args, Error, Help}; +use crate::terminal::args::{string, Args, Error, Help}; use crate::terminal::patch::Message; pub const HELP: Help = Help { @@ -46,7 +47,7 @@ Markdown supported. #[derive(Debug)] pub struct Options { - pub id: PatchId, + pub id: Rev, pub revision: Option, pub message: Message, pub sync: bool, @@ -60,7 +61,7 @@ impl Args for Options { use lexopt::prelude::*; let mut parser = lexopt::Parser::from_args(args); - let mut id: Option = None; + let mut id: Option = None; let mut revision: Option = None; let mut message = Message::default(); let mut sync = true; @@ -109,14 +110,7 @@ impl Args for Options { verdict = Some(Verdict::Reject); } Value(val) => { - let val = val - .to_str() - .ok_or_else(|| anyhow!("patch id specified is not UTF-8"))?; - - id = Some( - PatchId::from_str(val) - .map_err(|_| anyhow!("invalid patch id '{}'", val))?, - ); + id = Some(Rev::from(string(&val))); } _ => return Err(anyhow::anyhow!(arg.unexpected())), } @@ -148,7 +142,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { .context(format!("couldn't load project {id} from local state"))?; let mut patches = Patches::open(&repository)?; - let patch_id = options.id; + let patch_id = options.id.resolve(&repository.backend)?; let mut patch = patches .get_mut(&patch_id) .context(format!("couldn't find patch {patch_id} locally"))?; diff --git a/radicle-cli/src/git.rs b/radicle-cli/src/git.rs index 1bfe1850..3ad7f2cf 100644 --- a/radicle-cli/src/git.rs +++ b/radicle-cli/src/git.rs @@ -1,5 +1,6 @@ //! Git-related functions and types. use std::collections::HashSet; +use std::fmt::Display; use std::fs::{File, OpenOptions}; use std::io; use std::io::Write; @@ -10,6 +11,7 @@ use std::str::FromStr; use anyhow::anyhow; use anyhow::Context as _; +use radicle::cob::ObjectId; use radicle::crypto::ssh; use radicle::git; use radicle::git::raw as git2; @@ -27,6 +29,35 @@ pub const CONFIG_GPG_FORMAT: &str = "gpg.format"; pub const CONFIG_GPG_SSH_PROGRAM: &str = "gpg.ssh.program"; pub const CONFIG_GPG_SSH_ALLOWED_SIGNERS: &str = "gpg.ssh.allowedSignersFile"; +/// Git revision parameter. Supports extended SHA-1 syntax. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Rev(String); + +impl Rev { + /// Return the revision as a string. + pub fn as_str(&self) -> &str { + &self.0 + } + + /// Resolve the revision to an [`ObjectId`]. + pub fn resolve(&self, repo: &git2::Repository) -> Result { + let object = repo.revparse_single(self.as_str())?; + Ok(ObjectId::from(object.id())) + } +} + +impl Display for Rev { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for Rev { + fn from(value: String) -> Self { + Rev(value) + } +} + /// Get the git repository in the current directory. pub fn repository() -> Result { match Repository::open(".") { diff --git a/radicle-cli/src/terminal/args.rs b/radicle-cli/src/terminal/args.rs index 1d353229..adc70068 100644 --- a/radicle-cli/src/terminal/args.rs +++ b/radicle-cli/src/terminal/args.rs @@ -101,3 +101,7 @@ pub fn addr(val: &OsString) -> anyhow::Result
{ let val = val.to_string_lossy(); Address::from_str(&val).map_err(|_| anyhow!("invalid address '{}'", val)) } + +pub fn string(val: &OsString) -> String { + val.to_string_lossy().to_string() +}