cli: Implement `rad cob show` with new meaning

This commit is contained in:
Lorenz Leutgeb 2024-05-07 17:20:22 +02:00 committed by cloudhead
parent 6147a8a2fb
commit 6d11ea17c8
No known key found for this signature in database
4 changed files with 247 additions and 8 deletions

View File

@ -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"
]
}
}
```

View File

@ -6,6 +6,9 @@ use chrono::prelude::*;
use nonempty::NonEmpty;
use radicle::cob;
use radicle::cob::Op;
use radicle::identity::Identity;
use radicle::issue::cache::Issues;
use radicle::patch::cache::Patches;
use radicle::prelude::RepoId;
use radicle::storage::ReadStorage;
use radicle_cob::object::collaboration::list;
@ -24,7 +27,8 @@ Usage
rad cob <command> [<option>...]
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
@ -35,6 +39,10 @@ Log options
--format (pretty | json) Desired output format (default: pretty)
Show options
--format json Desired output format (default: json)
Other options
--help Print help
@ -45,11 +53,13 @@ Other options
enum OperationName {
List,
Log,
Show,
}
enum Operation {
List,
Log(Rev),
Show(Rev),
}
enum Format {
@ -80,6 +90,7 @@ impl Args for Options {
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
"list" => op = Some(OperationName::List),
"log" => op = Some(OperationName::Log),
"show" => op = Some(OperationName::Show),
unknown => anyhow::bail!("unknown operation '{unknown}'"),
},
Long("type") | Short('t') => {
@ -104,10 +115,12 @@ impl Args for Options {
Long("help") | Short('h') => {
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()?);
match v.as_ref() {
"pretty" => format = Some(Format::Pretty),
"pretty" if op == Some(OperationName::Log) => format = Some(Format::Pretty),
"json" => format = Some(Format::Json),
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"))? {
OperationName::List => Operation::List,
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(())

View File

@ -156,7 +156,7 @@ fn rad_issue() {
}
#[test]
fn rad_cob() {
fn rad_cob_log() {
let mut environment = Environment::new();
let profile = environment.profile(config::profile("alice"));
let home = &profile.home;
@ -169,6 +169,20 @@ fn rad_cob() {
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]
fn rad_init() {
let mut environment = Environment::new();

View File

@ -133,7 +133,8 @@ pub enum Error {
}
/// An evolving identity document.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Identity {
/// The canonical identifier for this identity.
/// 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 {
/// An accepting verdict must supply the [`Signature`] over the
/// new proposed [`Doc`].
@ -656,7 +657,7 @@ impl std::fmt::Display for State {
///
/// Once a revision has reached the quorum threshold of the previous
/// [`Identity`] it is then adopted as the current identity.
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Revision {
/// The id of this revision. Points to a commit.
pub id: RevisionId,