rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 heartwood6d11ea17c88163ad4a5047c0ba4f7b7fd756e99d
{
"request": "trigger",
"version": 1,
"event_type": "push",
"repository": {
"id": "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
"name": "heartwood",
"description": "Radicle Heartwood Protocol & Stack",
"private": false,
"default_branch": "master",
"delegates": [
"did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
"did:key:z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW",
"did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"did:key:z6MkgFq6z5fkF2hioLLSNu1zP2qEL1aHXHZzGH1FLFGAnBGz",
"did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz"
]
},
"pusher": {
"id": "did:key:z6MkpZZu1RbkQrTmeQDekiAfQ2ApmVK7GRq56vysvhNWMY73",
"alias": null
},
"before": "6d11ea17c88163ad4a5047c0ba4f7b7fd756e99d",
"after": "6d11ea17c88163ad4a5047c0ba4f7b7fd756e99d",
"branch": "master",
"commits": [
"6d11ea17c88163ad4a5047c0ba4f7b7fd756e99d"
]
}
{
"response": "triggered",
"run_id": {
"id": "4bc217ee-09cc-4454-9d39-f7ffee002465"
},
"info_url": "https://cci.rad.levitte.org//4bc217ee-09cc-4454-9d39-f7ffee002465.html"
}
Started at: 2025-10-21 19:34:51.063326+02:00
Commands:
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 .
✓ Creating checkout in ./...
✓ Remote cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT added
✓ Remote-tracking branch cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT/master created for z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT
✓ Remote cloudhead@z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW added
✓ Remote-tracking branch cloudhead@z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW/master created for z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW
✓ Remote fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM added
✓ Remote-tracking branch fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM/master created for z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM
✓ Remote erikli@z6MkgFq6z5fkF2hioLLSNu1zP2qEL1aHXHZzGH1FLFGAnBGz added
✓ Remote-tracking branch erikli@z6MkgFq6z5fkF2hioLLSNu1zP2qEL1aHXHZzGH1FLFGAnBGz/master created for z6MkgFq6z5fkF2hioLLSNu1zP2qEL1aHXHZzGH1FLFGAnBGz
✓ Remote lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz added
✓ Remote-tracking branch lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz/master created for z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz
✓ Repository successfully cloned under /opt/radcis/ci.rad.levitte.org/cci/state/4bc217ee-09cc-4454-9d39-f7ffee002465/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 125 issues · 15 patches │
╰────────────────────────────────────╯
Run `cd ./.` to go to the repository directory.
Exit code: 0
$ git config advice.detachedHead false
Exit code: 0
$ git checkout 6d11ea17c88163ad4a5047c0ba4f7b7fd756e99d
HEAD is now at 6d11ea17 cli: Implement `rad cob show` with new meaning
Exit code: 0
$ git show 6d11ea17c88163ad4a5047c0ba4f7b7fd756e99d
commit 6d11ea17c88163ad4a5047c0ba4f7b7fd756e99d
Author: Lorenz Leutgeb <lorenz@leutgeb.xyz>
Date: Tue May 7 17:20:22 2024 +0200
cli: Implement `rad cob show` with new meaning
diff --git a/radicle-cli/examples/rad-cob-show.md b/radicle-cli/examples/rad-cob-show.md
new file mode 100644
index 00000000..12d035f4
--- /dev/null
+++ b/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"
+ ]
+ }
+}
+```
diff --git a/radicle-cli/src/commands/cob.rs b/radicle-cli/src/commands/cob.rs
index bb8d8391..07b17266 100644
--- a/radicle-cli/src/commands/cob.rs
+++ b/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(())
diff --git a/radicle-cli/tests/commands.rs b/radicle-cli/tests/commands.rs
index a80969b1..34434645 100644
--- a/radicle-cli/tests/commands.rs
+++ b/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;
@@ -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();
diff --git a/radicle/src/cob/identity.rs b/radicle/src/cob/identity.rs
index 191d2871..bf21aad7 100644
--- a/radicle/src/cob/identity.rs
+++ b/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,
Exit code: 0
shell: 'cargo --version rustc --version cargo fmt --check cargo clippy --all-targets --workspace -- --deny clippy::all cargo build --all-targets --workspace cargo doc --workspace cargo test --workspace --no-fail-fast '
Commands:
$ podman run --name 4bc217ee-09cc-4454-9d39-f7ffee002465 -v /opt/radcis/ci.rad.levitte.org/cci/state/4bc217ee-09cc-4454-9d39-f7ffee002465/s:/4bc217ee-09cc-4454-9d39-f7ffee002465/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/4bc217ee-09cc-4454-9d39-f7ffee002465/w:/4bc217ee-09cc-4454-9d39-f7ffee002465/w -w /4bc217ee-09cc-4454-9d39-f7ffee002465/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:bookworm bash /4bc217ee-09cc-4454-9d39-f7ffee002465/s/script.sh
+ cargo --version
info: syncing channel updates for '1.77-x86_64-unknown-linux-gnu'
info: latest update on 2024-04-09, rust version 1.77.2 (25ef9e3d8 2024-04-09)
info: downloading component 'cargo'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: installing component 'cargo'
info: installing component 'rust-std'
info: installing component 'rustc'
cargo 1.77.2 (e52e36006 2024-03-26)
+ rustc --version
rustc 1.77.2 (25ef9e3d8 2024-04-09)
+ cargo fmt --check
error: 'cargo-fmt' is not installed for the toolchain '1.77-x86_64-unknown-linux-gnu'.
To install, run `rustup component add --toolchain 1.77-x86_64-unknown-linux-gnu rustfmt`
Exit code: 1
{
"response": "finished",
"result": "failure"
}