cli: Implement `rad cob show` with new meaning
This commit is contained in:
parent
6147a8a2fb
commit
6d11ea17c8
|
|
@ -0,0 +1,183 @@
|
||||||
|
Well known COBs, for example issues and patches, can not only be showed via porcelain commands such as
|
||||||
|
`rad issue show` and `rad patch show`, but also using the plumbing command `rad cob show`.
|
||||||
|
While humans likely prefer to use `rad issue show` and `rad patch show`, this command makes integration
|
||||||
|
with other software components easier.
|
||||||
|
|
||||||
|
First create an issue.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad issue open --title "spice harvester broken" --description "Fremen have attacked, maybe we went too far?" --no-announce
|
||||||
|
╭──────────────────────────────────────────────────╮
|
||||||
|
│ Title spice harvester broken │
|
||||||
|
│ Issue 9de644864342d7a505eb8d58d1ef20e5bb05de2e │
|
||||||
|
│ Author z6MknSL…StBU8Vi (you) │
|
||||||
|
│ Status open │
|
||||||
|
│ │
|
||||||
|
│ Fremen have attacked, maybe we went too far? │
|
||||||
|
╰──────────────────────────────────────────────────╯
|
||||||
|
```
|
||||||
|
|
||||||
|
The issue is now listed under our project.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad issue list
|
||||||
|
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│ ● ID Title Author Labels Assignees Opened │
|
||||||
|
├──────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ ● 9de6448 spice harvester broken z6MknSL…StBU8Vi (you) now │
|
||||||
|
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's create a patch, too.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git checkout -b spice-harvester-broken
|
||||||
|
$ touch TREATY.md
|
||||||
|
$ git add TREATY.md
|
||||||
|
$ git commit -v -m "Start drafting peace treaty"
|
||||||
|
[spice-harvester-broken 575ed68] Start drafting peace treaty
|
||||||
|
1 file changed, 0 insertions(+), 0 deletions(-)
|
||||||
|
create mode 100644 TREATY.md
|
||||||
|
$ git push rad -o patch.message="Start drafting peace treaty" -o patch.message="See details." HEAD:refs/patches
|
||||||
|
```
|
||||||
|
|
||||||
|
Patch can be listed.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad patch
|
||||||
|
╭────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│ ● ID Title Author Reviews Head + - Updated │
|
||||||
|
├────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ ● d1f7f86 Start drafting peace treaty z6MknSL…StBU8Vi (you) - 575ed68 +0 -0 now │
|
||||||
|
╰────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
|
```
|
||||||
|
|
||||||
|
Both issue and patch COBs can be listed.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad cob list --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue
|
||||||
|
9de644864342d7a505eb8d58d1ef20e5bb05de2e
|
||||||
|
$ rad cob list --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch
|
||||||
|
d1f7f869fde9fac19c1779c4c2e77e8361333f91
|
||||||
|
```
|
||||||
|
|
||||||
|
We can show the issue COB.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad cob show --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object 9de644864342d7a505eb8d58d1ef20e5bb05de2e
|
||||||
|
{
|
||||||
|
"assignees": [],
|
||||||
|
"title": "spice harvester broken",
|
||||||
|
"state": {
|
||||||
|
"status": "open"
|
||||||
|
},
|
||||||
|
"labels": [],
|
||||||
|
"thread": {
|
||||||
|
"comments": {
|
||||||
|
"9de644864342d7a505eb8d58d1ef20e5bb05de2e": {
|
||||||
|
"author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
|
||||||
|
"reactions": [],
|
||||||
|
"resolved": false,
|
||||||
|
"body": "Fremen have attacked, maybe we went too far?",
|
||||||
|
"edits": [
|
||||||
|
{
|
||||||
|
"author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
|
||||||
|
"timestamp": 1671125284000,
|
||||||
|
"body": "Fremen have attacked, maybe we went too far?",
|
||||||
|
"embeds": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timeline": [
|
||||||
|
"9de644864342d7a505eb8d58d1ef20e5bb05de2e"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We can show the patch COB too.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad cob show --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch --object d1f7f869fde9fac19c1779c4c2e77e8361333f91
|
||||||
|
{
|
||||||
|
"title": "Start drafting peace treaty",
|
||||||
|
"author": {
|
||||||
|
"id": "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"status": "open"
|
||||||
|
},
|
||||||
|
"target": "delegates",
|
||||||
|
"labels": [],
|
||||||
|
"merges": {},
|
||||||
|
"revisions": {
|
||||||
|
"d1f7f869fde9fac19c1779c4c2e77e8361333f91": {
|
||||||
|
"author": {
|
||||||
|
"id": "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
|
||||||
|
},
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
|
||||||
|
"timestamp": 1671125284000,
|
||||||
|
"body": "See details.",
|
||||||
|
"embeds": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"base": "f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354",
|
||||||
|
"oid": "575ed68c716d6aae81ea6b718fd9ac66a8eae532",
|
||||||
|
"discussion": {
|
||||||
|
"comments": {},
|
||||||
|
"timeline": []
|
||||||
|
},
|
||||||
|
"reviews": {},
|
||||||
|
"timestamp": 1671125284000,
|
||||||
|
"resolves": [],
|
||||||
|
"reactions": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"assignees": [],
|
||||||
|
"timeline": [
|
||||||
|
"d1f7f869fde9fac19c1779c4c2e77e8361333f91"
|
||||||
|
],
|
||||||
|
"reviews": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally let's update the issue and see the output of `rad cob show` also changes.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad issue label 9de6448 --add bug --no-announce
|
||||||
|
$ rad cob show --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object 9de644864342d7a505eb8d58d1ef20e5bb05de2e
|
||||||
|
{
|
||||||
|
"assignees": [],
|
||||||
|
"title": "spice harvester broken",
|
||||||
|
"state": {
|
||||||
|
"status": "open"
|
||||||
|
},
|
||||||
|
"labels": [
|
||||||
|
"bug"
|
||||||
|
],
|
||||||
|
"thread": {
|
||||||
|
"comments": {
|
||||||
|
"9de644864342d7a505eb8d58d1ef20e5bb05de2e": {
|
||||||
|
"author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
|
||||||
|
"reactions": [],
|
||||||
|
"resolved": false,
|
||||||
|
"body": "Fremen have attacked, maybe we went too far?",
|
||||||
|
"edits": [
|
||||||
|
{
|
||||||
|
"author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
|
||||||
|
"timestamp": 1671125284000,
|
||||||
|
"body": "Fremen have attacked, maybe we went too far?",
|
||||||
|
"embeds": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timeline": [
|
||||||
|
"9de644864342d7a505eb8d58d1ef20e5bb05de2e"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -6,6 +6,9 @@ use chrono::prelude::*;
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
use radicle::cob;
|
use radicle::cob;
|
||||||
use radicle::cob::Op;
|
use radicle::cob::Op;
|
||||||
|
use radicle::identity::Identity;
|
||||||
|
use radicle::issue::cache::Issues;
|
||||||
|
use radicle::patch::cache::Patches;
|
||||||
use radicle::prelude::RepoId;
|
use radicle::prelude::RepoId;
|
||||||
use radicle::storage::ReadStorage;
|
use radicle::storage::ReadStorage;
|
||||||
use radicle_cob::object::collaboration::list;
|
use radicle_cob::object::collaboration::list;
|
||||||
|
|
@ -24,7 +27,8 @@ Usage
|
||||||
|
|
||||||
rad cob <command> [<option>...]
|
rad cob <command> [<option>...]
|
||||||
rad cob list --repo <rid> --type <typename>
|
rad cob list --repo <rid> --type <typename>
|
||||||
rad cob log --repo <rid> --type <typename> --object <oid> [<option>..]
|
rad cob log --repo <rid> --type <typename> --object <oid> [<option>...]
|
||||||
|
rad cob show --repo <rid> --type <typename> --object <oid> [<option>...]
|
||||||
|
|
||||||
Commands
|
Commands
|
||||||
|
|
||||||
|
|
@ -35,6 +39,10 @@ Log options
|
||||||
|
|
||||||
--format (pretty | json) Desired output format (default: pretty)
|
--format (pretty | json) Desired output format (default: pretty)
|
||||||
|
|
||||||
|
Show options
|
||||||
|
|
||||||
|
--format json Desired output format (default: json)
|
||||||
|
|
||||||
Other options
|
Other options
|
||||||
|
|
||||||
--help Print help
|
--help Print help
|
||||||
|
|
@ -45,11 +53,13 @@ Other options
|
||||||
enum OperationName {
|
enum OperationName {
|
||||||
List,
|
List,
|
||||||
Log,
|
Log,
|
||||||
|
Show,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Operation {
|
enum Operation {
|
||||||
List,
|
List,
|
||||||
Log(Rev),
|
Log(Rev),
|
||||||
|
Show(Rev),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Format {
|
enum Format {
|
||||||
|
|
@ -80,6 +90,7 @@ impl Args for Options {
|
||||||
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() {
|
||||||
"list" => op = Some(OperationName::List),
|
"list" => op = Some(OperationName::List),
|
||||||
"log" => op = Some(OperationName::Log),
|
"log" => op = Some(OperationName::Log),
|
||||||
|
"show" => op = Some(OperationName::Show),
|
||||||
unknown => anyhow::bail!("unknown operation '{unknown}'"),
|
unknown => anyhow::bail!("unknown operation '{unknown}'"),
|
||||||
},
|
},
|
||||||
Long("type") | Short('t') => {
|
Long("type") | Short('t') => {
|
||||||
|
|
@ -104,10 +115,12 @@ impl Args for Options {
|
||||||
Long("help") | Short('h') => {
|
Long("help") | Short('h') => {
|
||||||
return Err(Error::Help.into());
|
return Err(Error::Help.into());
|
||||||
}
|
}
|
||||||
Long("format") if op == Some(OperationName::Log) => {
|
Long("format")
|
||||||
|
if op == Some(OperationName::Log) || op == Some(OperationName::Show) =>
|
||||||
|
{
|
||||||
let v: String = term::args::string(&parser.value()?);
|
let v: String = term::args::string(&parser.value()?);
|
||||||
match v.as_ref() {
|
match v.as_ref() {
|
||||||
"pretty" => format = Some(Format::Pretty),
|
"pretty" if op == Some(OperationName::Log) => format = Some(Format::Pretty),
|
||||||
"json" => format = Some(Format::Json),
|
"json" => format = Some(Format::Json),
|
||||||
unknown => anyhow::bail!("unknown format '{unknown}'"),
|
unknown => anyhow::bail!("unknown format '{unknown}'"),
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +135,10 @@ impl Args for Options {
|
||||||
match op.ok_or_else(|| anyhow!("a command must be specified"))? {
|
match op.ok_or_else(|| anyhow!("a command must be specified"))? {
|
||||||
OperationName::List => Operation::List,
|
OperationName::List => Operation::List,
|
||||||
OperationName::Log => Operation::Log(oid.ok_or_else(|| {
|
OperationName::Log => Operation::Log(oid.ok_or_else(|| {
|
||||||
anyhow!("an object id must be specified with `--object")
|
anyhow!("an object id must be specified with `--object`")
|
||||||
|
})?),
|
||||||
|
OperationName::Show => Operation::Show(oid.ok_or_else(|| {
|
||||||
|
anyhow!("an object id must be specified with `--object`")
|
||||||
})?),
|
})?),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -160,6 +176,31 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Operation::Show(oid) => {
|
||||||
|
let oid = &oid.resolve(&repo.backend)?;
|
||||||
|
|
||||||
|
if options.type_name == cob::patch::TYPENAME.clone() {
|
||||||
|
let patches = profile.patches(&repo)?;
|
||||||
|
let Some(patch) = patches.get(oid)? else {
|
||||||
|
anyhow::bail!(cob::store::Error::NotFound(options.type_name, *oid))
|
||||||
|
};
|
||||||
|
serde_json::to_writer_pretty(std::io::stdout(), &patch)?
|
||||||
|
} else if options.type_name == cob::issue::TYPENAME.clone() {
|
||||||
|
let issues = profile.issues(&repo)?;
|
||||||
|
let Some(issue) = issues.get(oid)? else {
|
||||||
|
anyhow::bail!(cob::store::Error::NotFound(options.type_name, *oid))
|
||||||
|
};
|
||||||
|
serde_json::to_writer_pretty(std::io::stdout(), &issue)?
|
||||||
|
} else if options.type_name == cob::identity::TYPENAME.clone() {
|
||||||
|
let Some(cob) = cob::get::<Identity, _>(&repo, &options.type_name, oid)? else {
|
||||||
|
anyhow::bail!(cob::store::Error::NotFound(options.type_name, *oid))
|
||||||
|
};
|
||||||
|
serde_json::to_writer_pretty(std::io::stdout(), &cob.object)?
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("the type name '{}' is unknown", options.type_name);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ fn rad_issue() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rad_cob() {
|
fn rad_cob_log() {
|
||||||
let mut environment = Environment::new();
|
let mut environment = Environment::new();
|
||||||
let profile = environment.profile(config::profile("alice"));
|
let profile = environment.profile(config::profile("alice"));
|
||||||
let home = &profile.home;
|
let home = &profile.home;
|
||||||
|
|
@ -169,6 +169,20 @@ fn rad_cob() {
|
||||||
test("examples/rad-cob-log.md", &working, Some(home), []).unwrap();
|
test("examples/rad-cob-log.md", &working, Some(home), []).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rad_cob_show() {
|
||||||
|
let mut environment = Environment::new();
|
||||||
|
let profile = environment.profile(config::profile("alice"));
|
||||||
|
let home = &profile.home;
|
||||||
|
let working = environment.tmp().join("working");
|
||||||
|
|
||||||
|
// Setup a test repository.
|
||||||
|
fixtures::repository(&working);
|
||||||
|
|
||||||
|
test("examples/rad-init.md", &working, Some(home), []).unwrap();
|
||||||
|
test("examples/rad-cob-show.md", &working, Some(home), []).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rad_init() {
|
fn rad_init() {
|
||||||
let mut environment = Environment::new();
|
let mut environment = Environment::new();
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,8 @@ pub enum Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An evolving identity document.
|
/// An evolving identity document.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Identity {
|
pub struct Identity {
|
||||||
/// The canonical identifier for this identity.
|
/// The canonical identifier for this identity.
|
||||||
/// This is the object id of the initial document blob.
|
/// This is the object id of the initial document blob.
|
||||||
|
|
@ -612,7 +613,7 @@ impl<R: ReadRepository> cob::Evaluate<R> for Identity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||||
pub enum Verdict {
|
pub enum Verdict {
|
||||||
/// An accepting verdict must supply the [`Signature`] over the
|
/// An accepting verdict must supply the [`Signature`] over the
|
||||||
/// new proposed [`Doc`].
|
/// new proposed [`Doc`].
|
||||||
|
|
@ -656,7 +657,7 @@ impl std::fmt::Display for State {
|
||||||
///
|
///
|
||||||
/// Once a revision has reached the quorum threshold of the previous
|
/// Once a revision has reached the quorum threshold of the previous
|
||||||
/// [`Identity`] it is then adopted as the current identity.
|
/// [`Identity`] it is then adopted as the current identity.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||||
pub struct Revision {
|
pub struct Revision {
|
||||||
/// The id of this revision. Points to a commit.
|
/// The id of this revision. Points to a commit.
|
||||||
pub id: RevisionId,
|
pub id: RevisionId,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue