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 <vincenzopalazzodev@gmail.com>
This commit is contained in:
parent
f2d31aa9c0
commit
654a21b2b3
|
|
@ -145,7 +145,7 @@ increased to `1`.
|
||||||
Instead, let's accept the proposal:
|
Instead, let's accept the proposal:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad id accept 0d396a83a5e1dda2b8929f7dc401d19dd1a79fb8 --no-confirm
|
$ rad id accept 0d396a --no-confirm
|
||||||
✓ Accepted proposal ✓
|
✓ Accepted proposal ✓
|
||||||
title: Add Bob
|
title: Add Bob
|
||||||
description: Add Bob as a delegate
|
description: Add Bob as a delegate
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,19 @@ $ rad issue list
|
||||||
2e8c1bf3fe0532a314778357c886608a966a34bd "flux capacitor underpowered" ❲unassigned❳
|
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.
|
Great! Now we've documented the issue for ourselves and others.
|
||||||
|
|
||||||
Just like with other project management systems, the issue can be assigned to
|
Just like with other project management systems, the issue can be assigned to
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ $ rad comment 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 --message 'I cannot wait
|
||||||
Now, let's checkout the patch that we just created:
|
Now, let's checkout the patch that we just created:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad patch checkout 191a14e520f2eeff7c0e3ee0a5523c5217eecb89
|
$ rad patch checkout 191a14e52
|
||||||
✓ Switched to branch patch/191a14e520f
|
✓ Switched to branch patch/191a14e520f
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
use radicle::prelude::Did;
|
use radicle::prelude::Did;
|
||||||
|
|
||||||
|
use crate::git::Rev;
|
||||||
use crate::terminal as term;
|
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;
|
||||||
use radicle::cob::issue;
|
use radicle::cob::issue;
|
||||||
use radicle::storage::WriteStorage;
|
use radicle::storage::WriteStorage;
|
||||||
|
|
@ -32,7 +32,7 @@ Options
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub id: issue::IssueId,
|
pub id: Rev,
|
||||||
pub to: NonEmpty<Did>,
|
pub to: NonEmpty<Did>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,7 +41,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<Rev> = None;
|
||||||
let mut to: Vec<Did> = Vec::new();
|
let mut to: Vec<Did> = Vec::new();
|
||||||
|
|
||||||
while let Some(arg) = parser.next()? {
|
while let Some(arg) = parser.next()? {
|
||||||
|
|
@ -56,11 +56,8 @@ impl Args for Options {
|
||||||
to.push(did);
|
to.push(did);
|
||||||
}
|
}
|
||||||
Value(ref val) if id.is_none() => {
|
Value(ref val) if id.is_none() => {
|
||||||
let val = val.to_string_lossy();
|
let val = string(val);
|
||||||
let Ok(val) = issue::IssueId::from_str(&val) else {
|
id = Some(Rev::from(val));
|
||||||
return Err(anyhow!("invalid Issue ID '{}'", val));
|
|
||||||
};
|
|
||||||
id = Some(val);
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(anyhow!(arg.unexpected()));
|
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 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 oid = options.id.resolve(&repo.backend)?;
|
||||||
let mut issues = issue::Issues::open(&repo)?;
|
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),
|
cob::store::Error::NotFound(_, _) => anyhow!("issue {} not found", options.id),
|
||||||
_ => e.into(),
|
_ => e.into(),
|
||||||
})?;
|
})?;
|
||||||
let signer = term::signer(&profile)?;
|
let signer = term::signer(&profile)?;
|
||||||
|
|
||||||
issue.assign(options.to.into_iter().map(Did::into), &signer)?;
|
issue.assign(options.to.into_iter().map(Did::into), &signer)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
use radicle::cob;
|
|
||||||
use radicle::cob::issue::Issues;
|
use radicle::cob::issue::Issues;
|
||||||
use radicle::cob::patch::Patches;
|
use radicle::cob::patch::Patches;
|
||||||
use radicle::cob::store;
|
use radicle::cob::store;
|
||||||
|
|
@ -11,7 +9,9 @@ use radicle::cob::thread;
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::storage;
|
use radicle::storage;
|
||||||
|
|
||||||
|
use crate::git::Rev;
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
|
use crate::terminal::args::string;
|
||||||
use crate::terminal::args::{Args, Error, Help};
|
use crate::terminal::args::{Args, Error, Help};
|
||||||
use crate::terminal::patch::Message;
|
use crate::terminal::patch::Message;
|
||||||
|
|
||||||
|
|
@ -34,25 +34,17 @@ Options
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub id: cob::ObjectId,
|
pub id: Rev,
|
||||||
pub message: Message,
|
pub message: Message,
|
||||||
pub reply_to: Option<thread::CommentId>,
|
pub reply_to: Option<thread::CommentId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_cob_id(val: OsString) -> anyhow::Result<cob::ObjectId> {
|
|
||||||
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 {
|
impl Args for Options {
|
||||||
fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
|
fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
|
||||||
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<cob::ObjectId> = None;
|
let mut id: Option<Rev> = None;
|
||||||
let mut message = Message::default();
|
let mut message = Message::default();
|
||||||
let mut reply_to = None;
|
let mut reply_to = None;
|
||||||
|
|
||||||
|
|
@ -76,7 +68,10 @@ impl Args for Options {
|
||||||
// Common.
|
// Common.
|
||||||
Long("help") => return Err(Error::Help.into()),
|
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())),
|
_ => return Err(anyhow::anyhow!(arg.unexpected())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +98,8 @@ fn comment(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut issues = Issues::open(repo)?;
|
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) => {
|
Ok(mut issue) => {
|
||||||
let comment_id = options.reply_to.unwrap_or_else(|| {
|
let comment_id = options.reply_to.unwrap_or_else(|| {
|
||||||
let (comment_id, _) = issue.comments().next().expect("root comment always exists");
|
let (comment_id, _) = issue.comments().next().expect("root comment always exists");
|
||||||
|
|
@ -119,7 +115,7 @@ fn comment(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut patches = Patches::open(repo)?;
|
let mut patches = Patches::open(repo)?;
|
||||||
match patches.get_mut(&options.id) {
|
match patches.get_mut(&id) {
|
||||||
Ok(mut patch) => {
|
Ok(mut patch) => {
|
||||||
let (revision_id, _) = patch.revisions().last().expect("patch has a revision");
|
let (revision_id, _) = patch.revisions().last().expect("patch has a revision");
|
||||||
let comment_id = patch.comment(*revision_id, message, options.reply_to, &signer)?;
|
let comment_id = patch.comment(*revision_id, message, options.reply_to, &signer)?;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
use std::{ffi::OsString, str::FromStr as _};
|
use std::{ffi::OsString, str::FromStr as _};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context 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::git::Oid;
|
||||||
use radicle::identity::Identity;
|
use radicle::identity::Identity;
|
||||||
use radicle::prelude::{Did, Doc};
|
use radicle::prelude::{Did, Doc};
|
||||||
use radicle::storage::ReadStorage as _;
|
use radicle::storage::ReadStorage as _;
|
||||||
use radicle_crypto::Verified;
|
use radicle_crypto::Verified;
|
||||||
|
|
||||||
|
use crate::git::Rev;
|
||||||
use crate::terminal as term;
|
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::Element;
|
||||||
use crate::terminal::Interactive;
|
use crate::terminal::Interactive;
|
||||||
|
|
||||||
|
|
@ -53,11 +54,11 @@ impl Metadata {
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub enum Operation {
|
pub enum Operation {
|
||||||
Accept {
|
Accept {
|
||||||
id: ProposalId,
|
id: Rev,
|
||||||
rev: Option<RevisionId>,
|
rev: Option<RevisionId>,
|
||||||
},
|
},
|
||||||
Reject {
|
Reject {
|
||||||
id: ProposalId,
|
id: Rev,
|
||||||
rev: Option<RevisionId>,
|
rev: Option<RevisionId>,
|
||||||
},
|
},
|
||||||
Edit {
|
Edit {
|
||||||
|
|
@ -67,7 +68,7 @@ pub enum Operation {
|
||||||
threshold: Option<usize>,
|
threshold: Option<usize>,
|
||||||
},
|
},
|
||||||
Update {
|
Update {
|
||||||
id: ProposalId,
|
id: Rev,
|
||||||
rev: Option<RevisionId>,
|
rev: Option<RevisionId>,
|
||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
|
|
@ -75,22 +76,22 @@ pub enum Operation {
|
||||||
threshold: Option<usize>,
|
threshold: Option<usize>,
|
||||||
},
|
},
|
||||||
Rebase {
|
Rebase {
|
||||||
id: ProposalId,
|
id: Rev,
|
||||||
rev: Option<RevisionId>,
|
rev: Option<RevisionId>,
|
||||||
},
|
},
|
||||||
Show {
|
Show {
|
||||||
id: ProposalId,
|
id: Rev,
|
||||||
rev: Option<RevisionId>,
|
rev: Option<RevisionId>,
|
||||||
show_revisions: bool,
|
show_revisions: bool,
|
||||||
},
|
},
|
||||||
#[default]
|
#[default]
|
||||||
List,
|
List,
|
||||||
Commit {
|
Commit {
|
||||||
id: ProposalId,
|
id: Rev,
|
||||||
rev: Option<RevisionId>,
|
rev: Option<RevisionId>,
|
||||||
},
|
},
|
||||||
Close {
|
Close {
|
||||||
id: ProposalId,
|
id: Rev,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,7 +120,7 @@ impl Args for Options {
|
||||||
|
|
||||||
let mut parser = lexopt::Parser::from_args(args);
|
let mut parser = lexopt::Parser::from_args(args);
|
||||||
let mut op: Option<OperationName> = None;
|
let mut op: Option<OperationName> = None;
|
||||||
let mut id: Option<ProposalId> = None;
|
let mut id: Option<Rev> = None;
|
||||||
let mut rev: Option<RevisionId> = None;
|
let mut rev: Option<RevisionId> = None;
|
||||||
let mut title: Option<String> = None;
|
let mut title: Option<String> = None;
|
||||||
let mut description: Option<String> = None;
|
let mut description: Option<String> = None;
|
||||||
|
|
@ -173,14 +174,8 @@ impl Args for Options {
|
||||||
show_revisions = true;
|
show_revisions = true;
|
||||||
}
|
}
|
||||||
Value(val) if op.is_some() => {
|
Value(val) if op.is_some() => {
|
||||||
let val = val
|
let val = string(&val);
|
||||||
.to_str()
|
id = Some(Rev::from(val));
|
||||||
.ok_or_else(|| anyhow!("proposal id specified is not UTF-8"))?;
|
|
||||||
|
|
||||||
id = Some(
|
|
||||||
ProposalId::from_str(val)
|
|
||||||
.map_err(|_| anyhow!("invalid proposal id '{}'", val))?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(anyhow!(arg.unexpected()));
|
return Err(anyhow!(arg.unexpected()));
|
||||||
|
|
@ -245,6 +240,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
let interactive = &options.interactive;
|
let interactive = &options.interactive;
|
||||||
match options.op {
|
match options.op {
|
||||||
Operation::Accept { id, rev } => {
|
Operation::Accept { id, rev } => {
|
||||||
|
let id = id.resolve(&repo.backend)?;
|
||||||
let mut proposal = proposals.get_mut(&id)?;
|
let mut proposal = proposals.get_mut(&id)?;
|
||||||
let (rid, revision) = select(&proposal, rev, &previous, interactive)?;
|
let (rid, revision) = select(&proposal, rev, &previous, interactive)?;
|
||||||
warn_out_of_date(revision, &previous);
|
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 } => {
|
Operation::Reject { id, rev } => {
|
||||||
|
let id = id.resolve(&repo.backend)?;
|
||||||
let mut proposal = proposals.get_mut(&id)?;
|
let mut proposal = proposals.get_mut(&id)?;
|
||||||
let (rid, revision) = select(&proposal, rev, &previous, interactive)?;
|
let (rid, revision) = select(&proposal, rev, &previous, interactive)?;
|
||||||
warn_out_of_date(revision, &previous);
|
warn_out_of_date(revision, &previous);
|
||||||
|
|
@ -311,6 +308,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
delegates,
|
delegates,
|
||||||
threshold,
|
threshold,
|
||||||
} => {
|
} => {
|
||||||
|
let id = id.resolve(&repo.backend)?;
|
||||||
let mut proposal = proposals.get_mut(&id)?;
|
let mut proposal = proposals.get_mut(&id)?;
|
||||||
let (_, revision) = select(&proposal, rev, &previous, interactive)?;
|
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 } => {
|
Operation::Rebase { id, rev } => {
|
||||||
|
let id = id.resolve(&repo.backend)?;
|
||||||
// TODO: it would be nice if rebasing also handled fast-forwards nicely.
|
// TODO: it would be nice if rebasing also handled fast-forwards nicely.
|
||||||
let mut proposal = proposals.get_mut(&id)?;
|
let mut proposal = proposals.get_mut(&id)?;
|
||||||
let (_, revision) = select(&proposal, rev, &previous, interactive)?;
|
let (_, revision) = select(&proposal, rev, &previous, interactive)?;
|
||||||
|
|
@ -407,6 +406,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
t.print();
|
t.print();
|
||||||
}
|
}
|
||||||
Operation::Commit { id, rev } => {
|
Operation::Commit { id, rev } => {
|
||||||
|
let id = id.resolve(&repo.backend)?;
|
||||||
let mut proposal = proposals.get_mut(&id)?;
|
let mut proposal = proposals.get_mut(&id)?;
|
||||||
let (rid, revision) = commit_select(&proposal, rev, &previous, interactive)?;
|
let (rid, revision) = commit_select(&proposal, rev, &previous, interactive)?;
|
||||||
warn_out_of_date(revision, &previous);
|
warn_out_of_date(revision, &previous);
|
||||||
|
|
@ -419,6 +419,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Operation::Close { id } => {
|
Operation::Close { id } => {
|
||||||
|
let id = id.resolve(&repo.backend)?;
|
||||||
let mut proposal = proposals.get_mut(&id)?;
|
let mut proposal = proposals.get_mut(&id)?;
|
||||||
let yes = confirm(interactive, "Are you sure you want to close?");
|
let yes = confirm(interactive, "Are you sure you want to close?");
|
||||||
if yes {
|
if yes {
|
||||||
|
|
@ -432,6 +433,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
rev,
|
rev,
|
||||||
show_revisions,
|
show_revisions,
|
||||||
} => {
|
} => {
|
||||||
|
let id = id.resolve(&repo.backend)?;
|
||||||
let proposal = proposals
|
let proposal = proposals
|
||||||
.get(&id)?
|
.get(&id)?
|
||||||
.context("No proposal with the given ID exists")?;
|
.context("No proposal with the given ID exists")?;
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,14 @@ use anyhow::{anyhow, Context as _};
|
||||||
use radicle::node::Handle;
|
use radicle::node::Handle;
|
||||||
use radicle::prelude::Did;
|
use radicle::prelude::Did;
|
||||||
|
|
||||||
|
use crate::git::Rev;
|
||||||
use crate::terminal as term;
|
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::Element;
|
||||||
|
|
||||||
use radicle::cob::common::{Reaction, Tag};
|
use radicle::cob::common::{Reaction, Tag};
|
||||||
use radicle::cob::issue;
|
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::storage::WriteStorage;
|
||||||
use radicle::{cob, Node};
|
use radicle::{cob, Node};
|
||||||
|
|
||||||
|
|
@ -71,17 +72,17 @@ pub enum Operation {
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
},
|
},
|
||||||
Show {
|
Show {
|
||||||
id: IssueId,
|
id: Rev,
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
id: IssueId,
|
id: Rev,
|
||||||
state: State,
|
state: State,
|
||||||
},
|
},
|
||||||
Delete {
|
Delete {
|
||||||
id: IssueId,
|
id: Rev,
|
||||||
},
|
},
|
||||||
React {
|
React {
|
||||||
id: IssueId,
|
id: Rev,
|
||||||
reaction: Reaction,
|
reaction: Reaction,
|
||||||
},
|
},
|
||||||
List {
|
List {
|
||||||
|
|
@ -101,7 +102,7 @@ impl Args for Options {
|
||||||
|
|
||||||
let mut parser = lexopt::Parser::from_args(args);
|
let mut parser = lexopt::Parser::from_args(args);
|
||||||
let mut op: Option<OperationName> = None;
|
let mut op: Option<OperationName> = None;
|
||||||
let mut id: Option<IssueId> = None;
|
let mut id: Option<Rev> = None;
|
||||||
let mut assigned: Option<Assigned> = None;
|
let mut assigned: Option<Assigned> = None;
|
||||||
let mut title: Option<String> = None;
|
let mut title: Option<String> = None;
|
||||||
let mut reaction: Option<Reaction> = None;
|
let mut reaction: Option<Reaction> = None;
|
||||||
|
|
@ -161,14 +162,8 @@ impl Args for Options {
|
||||||
unknown => anyhow::bail!("unknown operation '{}'", unknown),
|
unknown => anyhow::bail!("unknown operation '{}'", unknown),
|
||||||
},
|
},
|
||||||
Value(val) if op.is_some() => {
|
Value(val) if op.is_some() => {
|
||||||
let val = val
|
let val = string(&val);
|
||||||
.to_str()
|
id = Some(Rev::from(val));
|
||||||
.ok_or_else(|| anyhow!("issue id specified is not UTF-8"))?;
|
|
||||||
|
|
||||||
id = Some(
|
|
||||||
IssueId::from_str(val)
|
|
||||||
.map_err(|_| anyhow!("invalid issue id '{}'", val))?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(anyhow!(arg.unexpected()));
|
return Err(anyhow!(arg.unexpected()));
|
||||||
|
|
@ -225,16 +220,19 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
show_issue(&issue)?;
|
show_issue(&issue)?;
|
||||||
}
|
}
|
||||||
Operation::Show { id } => {
|
Operation::Show { id } => {
|
||||||
|
let id = id.resolve(&repo.backend)?;
|
||||||
let issue = issues
|
let issue = issues
|
||||||
.get(&id)?
|
.get(&id)?
|
||||||
.context("No issue with the given ID exists")?;
|
.context("No issue with the given ID exists")?;
|
||||||
show_issue(&issue)?;
|
show_issue(&issue)?;
|
||||||
}
|
}
|
||||||
Operation::State { id, state } => {
|
Operation::State { id, state } => {
|
||||||
|
let id = id.resolve(&repo.backend)?;
|
||||||
let mut issue = issues.get_mut(&id)?;
|
let mut issue = issues.get_mut(&id)?;
|
||||||
issue.lifecycle(state, &signer)?;
|
issue.lifecycle(state, &signer)?;
|
||||||
}
|
}
|
||||||
Operation::React { id, reaction } => {
|
Operation::React { id, reaction } => {
|
||||||
|
let id = id.resolve(&repo.backend)?;
|
||||||
if let Ok(mut issue) = issues.get_mut(&id) {
|
if let Ok(mut issue) = issues.get_mut(&id) {
|
||||||
let (comment_id, _) = term::io::comment_select(&issue).unwrap();
|
let (comment_id, _) = term::io::comment_select(&issue).unwrap();
|
||||||
issue.react(*comment_id, reaction, &signer)?;
|
issue.react(*comment_id, reaction, &signer)?;
|
||||||
|
|
@ -325,6 +323,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
t.print();
|
t.print();
|
||||||
}
|
}
|
||||||
Operation::Delete { id } => {
|
Operation::Delete { id } => {
|
||||||
|
let id = id.resolve(&repo.backend)?;
|
||||||
issues.remove(&id, &signer)?;
|
issues.remove(&id, &signer)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
|
|
||||||
|
use crate::git::Rev;
|
||||||
use crate::terminal as term;
|
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::RevisionIx;
|
||||||
use radicle::cob::patch::{Patch, PatchId, Patches};
|
use radicle::cob::patch::{Patch, PatchId, Patches};
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
|
|
@ -72,7 +73,7 @@ pub enum State {
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub id: PatchId,
|
pub id: Rev,
|
||||||
pub interactive: bool,
|
pub interactive: bool,
|
||||||
pub revision: Option<RevisionIx>,
|
pub revision: Option<RevisionIx>,
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +83,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<PatchId> = None;
|
let mut id: Option<Rev> = None;
|
||||||
let mut revision: Option<RevisionIx> = None;
|
let mut revision: Option<RevisionIx> = None;
|
||||||
let mut interactive = false;
|
let mut interactive = false;
|
||||||
|
|
||||||
|
|
@ -103,14 +104,8 @@ impl Args for Options {
|
||||||
revision = Some(id);
|
revision = Some(id);
|
||||||
}
|
}
|
||||||
Value(val) => {
|
Value(val) => {
|
||||||
let val = val
|
let val = string(&val);
|
||||||
.to_str()
|
id = Some(Rev::from(val));
|
||||||
.ok_or_else(|| anyhow!("patch id specified is not UTF-8"))?;
|
|
||||||
|
|
||||||
id = Some(
|
|
||||||
PatchId::from_str(val)
|
|
||||||
.map_err(|_| anyhow!("invalid patch id '{}'", val))?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => return Err(anyhow::anyhow!(arg.unexpected())),
|
_ => return Err(anyhow::anyhow!(arg.unexpected())),
|
||||||
}
|
}
|
||||||
|
|
@ -149,10 +144,10 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
//
|
//
|
||||||
// Get patch information
|
// Get patch information
|
||||||
//
|
//
|
||||||
let patch_id = options.id;
|
let patch_id = options.id.resolve(&repository.backend)?;
|
||||||
let mut patch = patches
|
let mut patch = patches
|
||||||
.get_mut(&patch_id)
|
.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 head = repo.head()?;
|
||||||
let branch = head
|
let branch = head
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,9 @@ use radicle::cob::patch::PatchId;
|
||||||
use radicle::{prelude::*, Node};
|
use radicle::{prelude::*, Node};
|
||||||
|
|
||||||
use crate::commands::rad_fetch as fetch;
|
use crate::commands::rad_fetch as fetch;
|
||||||
|
use crate::git::Rev;
|
||||||
use crate::terminal as term;
|
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;
|
use crate::terminal::patch::Message;
|
||||||
|
|
||||||
pub const HELP: Help = Help {
|
pub const HELP: Help = Help {
|
||||||
|
|
@ -70,17 +71,17 @@ pub enum Operation {
|
||||||
message: Message,
|
message: Message,
|
||||||
},
|
},
|
||||||
Show {
|
Show {
|
||||||
patch_id: PatchId,
|
patch_id: Rev,
|
||||||
},
|
},
|
||||||
Update {
|
Update {
|
||||||
patch_id: Option<PatchId>,
|
patch_id: Option<Rev>,
|
||||||
message: Message,
|
message: Message,
|
||||||
},
|
},
|
||||||
Delete {
|
Delete {
|
||||||
patch_id: PatchId,
|
patch_id: Rev,
|
||||||
},
|
},
|
||||||
Checkout {
|
Checkout {
|
||||||
patch_id: PatchId,
|
patch_id: Rev,
|
||||||
},
|
},
|
||||||
List,
|
List,
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +114,7 @@ impl Args for Options {
|
||||||
Long("message") | Short('m') => {
|
Long("message") | Short('m') => {
|
||||||
if message != Message::Blank {
|
if message != Message::Blank {
|
||||||
// We skip this code when `no-message` is specified.
|
// 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);
|
message.append(&txt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -156,17 +157,11 @@ impl Args for Options {
|
||||||
"c" | "checkout" => op = Some(OperationName::Checkout),
|
"c" | "checkout" => op = Some(OperationName::Checkout),
|
||||||
unknown => anyhow::bail!("unknown operation '{}'", unknown),
|
unknown => anyhow::bail!("unknown operation '{}'", unknown),
|
||||||
},
|
},
|
||||||
Value(val) if op == Some(OperationName::Show) && patch_id.is_none() => {
|
Value(val)
|
||||||
patch_id = Some(term::cob::parse_patch_id(val)?);
|
if op.is_some() && op != Some(OperationName::List) && patch_id.is_none() =>
|
||||||
}
|
{
|
||||||
Value(val) if op == Some(OperationName::Update) && patch_id.is_none() => {
|
let val = string(&val);
|
||||||
patch_id = Some(term::cob::parse_patch_id(val)?);
|
patch_id = Some(Rev::from(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)?);
|
|
||||||
}
|
}
|
||||||
_ => return Err(anyhow::anyhow!(arg.unexpected())),
|
_ => return Err(anyhow::anyhow!(arg.unexpected())),
|
||||||
}
|
}
|
||||||
|
|
@ -218,13 +213,18 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
Operation::List => {
|
Operation::List => {
|
||||||
list::run(&repository, &profile, Some(workdir))?;
|
list::run(&repository, &profile, Some(workdir))?;
|
||||||
}
|
}
|
||||||
Operation::Show { ref patch_id } => {
|
Operation::Show { patch_id } => {
|
||||||
show::run(&repository, &workdir, patch_id)?;
|
let patch_id = patch_id.resolve(&repository.backend)?;
|
||||||
|
show::run(&repository, &workdir, &patch_id)?;
|
||||||
}
|
}
|
||||||
Operation::Update {
|
Operation::Update {
|
||||||
patch_id,
|
ref patch_id,
|
||||||
ref message,
|
ref message,
|
||||||
} => {
|
} => {
|
||||||
|
let patch_id = patch_id
|
||||||
|
.as_ref()
|
||||||
|
.map(|id| id.resolve(&repository.backend))
|
||||||
|
.transpose()?;
|
||||||
update::run(
|
update::run(
|
||||||
&repository,
|
&repository,
|
||||||
&profile,
|
&profile,
|
||||||
|
|
@ -235,10 +235,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Operation::Delete { patch_id } => {
|
Operation::Delete { patch_id } => {
|
||||||
|
let patch_id = patch_id.resolve(&repository.backend)?;
|
||||||
delete::run(&repository, &profile, &patch_id)?;
|
delete::run(&repository, &profile, &patch_id)?;
|
||||||
}
|
}
|
||||||
Operation::Checkout { ref patch_id } => {
|
Operation::Checkout { patch_id } => {
|
||||||
checkout::run(&repository, &workdir, patch_id)?;
|
let patch_id = patch_id.resolve(&repository.backend)?;
|
||||||
|
checkout::run(&repository, &workdir, &patch_id)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
|
|
||||||
use radicle::cob::patch::{PatchId, Patches, RevisionIx, Verdict};
|
use radicle::cob::patch::{Patches, RevisionIx, Verdict};
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::rad;
|
use radicle::rad;
|
||||||
|
|
||||||
|
use crate::git::Rev;
|
||||||
use crate::terminal as term;
|
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;
|
use crate::terminal::patch::Message;
|
||||||
|
|
||||||
pub const HELP: Help = Help {
|
pub const HELP: Help = Help {
|
||||||
|
|
@ -46,7 +47,7 @@ Markdown supported.
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub id: PatchId,
|
pub id: Rev,
|
||||||
pub revision: Option<RevisionIx>,
|
pub revision: Option<RevisionIx>,
|
||||||
pub message: Message,
|
pub message: Message,
|
||||||
pub sync: bool,
|
pub sync: bool,
|
||||||
|
|
@ -60,7 +61,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<PatchId> = None;
|
let mut id: Option<Rev> = None;
|
||||||
let mut revision: Option<RevisionIx> = None;
|
let mut revision: Option<RevisionIx> = None;
|
||||||
let mut message = Message::default();
|
let mut message = Message::default();
|
||||||
let mut sync = true;
|
let mut sync = true;
|
||||||
|
|
@ -109,14 +110,7 @@ impl Args for Options {
|
||||||
verdict = Some(Verdict::Reject);
|
verdict = Some(Verdict::Reject);
|
||||||
}
|
}
|
||||||
Value(val) => {
|
Value(val) => {
|
||||||
let val = val
|
id = Some(Rev::from(string(&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))?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => return Err(anyhow::anyhow!(arg.unexpected())),
|
_ => 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"))?;
|
.context(format!("couldn't load project {id} from local state"))?;
|
||||||
let mut patches = Patches::open(&repository)?;
|
let mut patches = Patches::open(&repository)?;
|
||||||
|
|
||||||
let patch_id = options.id;
|
let patch_id = options.id.resolve(&repository.backend)?;
|
||||||
let mut patch = patches
|
let mut patch = patches
|
||||||
.get_mut(&patch_id)
|
.get_mut(&patch_id)
|
||||||
.context(format!("couldn't find patch {patch_id} locally"))?;
|
.context(format!("couldn't find patch {patch_id} locally"))?;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! Git-related functions and types.
|
//! Git-related functions and types.
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
@ -10,6 +11,7 @@ use std::str::FromStr;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
|
|
||||||
|
use radicle::cob::ObjectId;
|
||||||
use radicle::crypto::ssh;
|
use radicle::crypto::ssh;
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::git::raw as git2;
|
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_PROGRAM: &str = "gpg.ssh.program";
|
||||||
pub const CONFIG_GPG_SSH_ALLOWED_SIGNERS: &str = "gpg.ssh.allowedSignersFile";
|
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<ObjectId, git2::Error> {
|
||||||
|
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<String> for Rev {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Rev(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the git repository in the current directory.
|
/// Get the git repository in the current directory.
|
||||||
pub fn repository() -> Result<Repository, anyhow::Error> {
|
pub fn repository() -> Result<Repository, anyhow::Error> {
|
||||||
match Repository::open(".") {
|
match Repository::open(".") {
|
||||||
|
|
|
||||||
|
|
@ -101,3 +101,7 @@ pub fn addr(val: &OsString) -> anyhow::Result<Address> {
|
||||||
let val = val.to_string_lossy();
|
let val = val.to_string_lossy();
|
||||||
Address::from_str(&val).map_err(|_| anyhow!("invalid address '{}'", val))
|
Address::from_str(&val).map_err(|_| anyhow!("invalid address '{}'", val))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn string(val: &OsString) -> String {
|
||||||
|
val.to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue