Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Implement `rad cob show` with new meaning
✗ CI failure Lorenz Leutgeb committed 1 year ago
commit 6d11ea17c88163ad4a5047c0ba4f7b7fd756e99d
parent 6147a8a2fb16613c7b3dc2a999c4fa5f024bf90b
1 failed (1 total) View logs
4 files changed +247 -8
added radicle-cli/examples/rad-cob-show.md
@@ -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"
+
    ]
+
  }
+
}
+
```
modified radicle-cli/src/commands/cob.rs
@@ -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(())
modified radicle-cli/tests/commands.rs
@@ -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;
@@ -170,6 +170,20 @@ fn rad_cob() {
}

#[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();
    let profile = environment.profile(config::profile("alice"));
modified radicle/src/cob/identity.rs
@@ -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,