From 2a0f6fd3c5588c0779b05d295b118cdc4adba9fe Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Mon, 25 Nov 2024 11:30:01 +0000 Subject: [PATCH] cli: extend `rad cob log` behaviour Use the COB stream behaviour for implementing `rad cob log` and introducing the from and until options. --- CHANGELOG.md | 3 + crates/radicle-cli/examples/rad-cob-log.md | 30 +-- .../examples/rad-cob-operations.md | 241 ++++++++++++++++++ crates/radicle-cli/examples/rad-id.md | 16 +- crates/radicle-cli/src/commands/cob.rs | 133 ++++++++-- crates/radicle-cli/tests/commands.rs | 5 + 6 files changed, 377 insertions(+), 51 deletions(-) create mode 100644 crates/radicle-cli/examples/rad-cob-operations.md diff --git a/CHANGELOG.md b/CHANGELOG.md index c95ae7b1..b806514e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## New Features +- `rad cob log` now supports the arguments `--from` and `--to` which can be used + to range over particular operations on a COB. + ## Fixed Bugs ## 1.3.0 - 2025-08-12 diff --git a/crates/radicle-cli/examples/rad-cob-log.md b/crates/radicle-cli/examples/rad-cob-log.md index 2fa0c056..58b42ca8 100644 --- a/crates/radicle-cli/examples/rad-cob-log.md +++ b/crates/radicle-cli/examples/rad-cob-log.md @@ -68,13 +68,13 @@ author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi date Thu, 15 Dec 2022 17:28:04 +0000 { - "body": "Flux capacitor power requirements exceed current supply", - "type": "comment" + "type": "comment", + "body": "Flux capacitor power requirements exceed current supply" } { - "title": "flux capacitor underpowered", - "type": "edit" + "type": "edit", + "title": "flux capacitor underpowered" } ``` @@ -91,16 +91,16 @@ author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi date Thu, 15 Dec 2022 17:28:04 +0000 { - "base": "f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354", + "type": "revision", "description": "See details.", - "oid": "3e674d1a1df90807e934f9ae5da2591dd6848a33", - "type": "revision" + "base": "f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354", + "oid": "3e674d1a1df90807e934f9ae5da2591dd6848a33" } { - "target": "delegates", + "type": "edit", "title": "Define power requirements", - "type": "edit" + "target": "delegates" } ``` @@ -117,10 +117,10 @@ author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi date Thu, 15 Dec 2022 17:28:04 +0000 { + "type": "label", "labels": [ "bug" - ], - "type": "label" + ] } commit d87dcfe8c2b3200e78b128d9b959cfdf7063fefe @@ -129,13 +129,13 @@ author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi date Thu, 15 Dec 2022 17:28:04 +0000 { - "body": "Flux capacitor power requirements exceed current supply", - "type": "comment" + "type": "comment", + "body": "Flux capacitor power requirements exceed current supply" } { - "title": "flux capacitor underpowered", - "type": "edit" + "type": "edit", + "title": "flux capacitor underpowered" } ``` diff --git a/crates/radicle-cli/examples/rad-cob-operations.md b/crates/radicle-cli/examples/rad-cob-operations.md new file mode 100644 index 00000000..53c31e03 --- /dev/null +++ b/crates/radicle-cli/examples/rad-cob-operations.md @@ -0,0 +1,241 @@ +The `rad cob` command provides a subcommand, `log`, for inspecting the +operations of a COB. + +To demonstrate, we will first create an issue and interact with it: + +``` +$ rad issue open --title "flux capacitor underpowered" --description "Flux capacitor power requirements exceed current supply" --no-announce +╭─────────────────────────────────────────────────────────╮ +│ Title flux capacitor underpowered │ +│ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │ +│ Author alice (you) │ +│ Status open │ +│ │ +│ Flux capacitor power requirements exceed current supply │ +╰─────────────────────────────────────────────────────────╯ +$ rad issue react d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --to d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --emoji ✨ --no-announce +$ rad issue comment d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --message "Max power!" --no-announce +╭─────────────────────────╮ +│ alice (you) now 3c849c9 │ +│ Max power! │ +╰─────────────────────────╯ +$ rad issue assign d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-announce +``` + +Now, let's see the list of operations using `rad cob log`: + +``` +$ rad cob log --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe +commit 376ba71113603004eae3c1b125c58cdc41d36b73 +resource 0656c217f917c3e06234771e9ecae53aba5e173e +parent 3c849c9b555b18be9a1f6c71fb254ba000de8cfe +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "assign", + "assignees": [ + "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk" + ] + } + +commit 3c849c9b555b18be9a1f6c71fb254ba000de8cfe +resource 0656c217f917c3e06234771e9ecae53aba5e173e +parent 256908937f3cda8df522d5a3ba442eb935c3f11b +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "comment", + "body": "Max power!", + "replyTo": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe" + } + +commit 256908937f3cda8df522d5a3ba442eb935c3f11b +resource 0656c217f917c3e06234771e9ecae53aba5e173e +parent d87dcfe8c2b3200e78b128d9b959cfdf7063fefe +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "comment.react", + "id": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe", + "reaction": "✨", + "active": true + } + +commit d87dcfe8c2b3200e78b128d9b959cfdf7063fefe +resource 0656c217f917c3e06234771e9ecae53aba5e173e +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "comment", + "body": "Flux capacitor power requirements exceed current supply" + } + + { + "type": "edit", + "title": "flux capacitor underpowered" + } + +``` + +We can also limit the range of operations, using the `--from` and `--until` +options. We will need some commit revisions to use for those options, so let's +look at what those revision are by using `rad cob log`: + +``` +$ rad cob log --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe +commit 376ba71113603004eae3c1b125c58cdc41d36b73 +resource 0656c217f917c3e06234771e9ecae53aba5e173e +parent 3c849c9b555b18be9a1f6c71fb254ba000de8cfe +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "assign", + "assignees": [ + "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk" + ] + } + +commit 3c849c9b555b18be9a1f6c71fb254ba000de8cfe +resource 0656c217f917c3e06234771e9ecae53aba5e173e +parent 256908937f3cda8df522d5a3ba442eb935c3f11b +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "comment", + "body": "Max power!", + "replyTo": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe" + } + +commit 256908937f3cda8df522d5a3ba442eb935c3f11b +resource 0656c217f917c3e06234771e9ecae53aba5e173e +parent d87dcfe8c2b3200e78b128d9b959cfdf7063fefe +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "comment.react", + "id": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe", + "reaction": "✨", + "active": true + } + +commit d87dcfe8c2b3200e78b128d9b959cfdf7063fefe +resource 0656c217f917c3e06234771e9ecae53aba5e173e +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "comment", + "body": "Flux capacitor power requirements exceed current supply" + } + + { + "type": "edit", + "title": "flux capacitor underpowered" + } + +``` + +If we provide only the `--from` option, the operations we get back start from that +revision and go until the end: + +``` +$ rad cob log --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --from 3c849c9b555b18be9a1f6c71fb254ba000de8cfe +commit 376ba71113603004eae3c1b125c58cdc41d36b73 +resource 0656c217f917c3e06234771e9ecae53aba5e173e +parent 3c849c9b555b18be9a1f6c71fb254ba000de8cfe +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "assign", + "assignees": [ + "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk" + ] + } + +commit 3c849c9b555b18be9a1f6c71fb254ba000de8cfe +resource 0656c217f917c3e06234771e9ecae53aba5e173e +parent 256908937f3cda8df522d5a3ba442eb935c3f11b +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "comment", + "body": "Max power!", + "replyTo": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe" + } + +``` + +Conversely, if we provide only the `--until` option, the operations we get back +start from the beginning and stop at that revision: + +``` +$ rad cob log --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --until 256908937f3cda8df522d5a3ba442eb935c3f11b +commit 256908937f3cda8df522d5a3ba442eb935c3f11b +resource 0656c217f917c3e06234771e9ecae53aba5e173e +parent d87dcfe8c2b3200e78b128d9b959cfdf7063fefe +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "comment.react", + "id": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe", + "reaction": "✨", + "active": true + } + +commit d87dcfe8c2b3200e78b128d9b959cfdf7063fefe +resource 0656c217f917c3e06234771e9ecae53aba5e173e +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "comment", + "body": "Flux capacitor power requirements exceed current supply" + } + + { + "type": "edit", + "title": "flux capacitor underpowered" + } + +``` + +Finally, if we provide both, we get back that exact range: + +``` +$ rad cob log --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --from 256908937f3cda8df522d5a3ba442eb935c3f11b --until 3c849c9b555b18be9a1f6c71fb254ba000de8cfe +commit 3c849c9b555b18be9a1f6c71fb254ba000de8cfe +resource 0656c217f917c3e06234771e9ecae53aba5e173e +parent 256908937f3cda8df522d5a3ba442eb935c3f11b +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "comment", + "body": "Max power!", + "replyTo": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe" + } + +commit 256908937f3cda8df522d5a3ba442eb935c3f11b +resource 0656c217f917c3e06234771e9ecae53aba5e173e +parent d87dcfe8c2b3200e78b128d9b959cfdf7063fefe +author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +date Thu, 15 Dec 2022 17:28:04 +0000 + + { + "type": "comment.react", + "id": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe", + "reaction": "✨", + "active": true + } + +``` diff --git a/crates/radicle-cli/examples/rad-id.md b/crates/radicle-cli/examples/rad-id.md index 2bc93ed8..c919e7e6 100644 --- a/crates/radicle-cli/examples/rad-id.md +++ b/crates/radicle-cli/examples/rad-id.md @@ -100,12 +100,12 @@ author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi date Thu, 15 Dec 2022 17:28:04 +0000 { - "blob": "053541ba7b90534b35dd8718e0ceaa408979b02b", - "description": "Add Bob as a delegate", - "parent": "0656c217f917c3e06234771e9ecae53aba5e173e", - "signature": "z3AyzixN2eWLtRfQWowtBXwWyRH3iJ8oJ25W6KFYFw5ANLntbzfavge15muNU6AVAUkxSxQvgg9yh2gupbUecavQY", + "type": "revision", "title": "Add Bob", - "type": "revision" + "description": "Add Bob as a delegate", + "blob": "053541ba7b90534b35dd8718e0ceaa408979b02b", + "parent": "0656c217f917c3e06234771e9ecae53aba5e173e", + "signature": "z3AyzixN2eWLtRfQWowtBXwWyRH3iJ8oJ25W6KFYFw5ANLntbzfavge15muNU6AVAUkxSxQvgg9yh2gupbUecavQY" } commit 0656c217f917c3e06234771e9ecae53aba5e173e @@ -113,11 +113,11 @@ author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi date Thu, 15 Dec 2022 17:28:04 +0000 { + "type": "revision", + "title": "Initial revision", "blob": "d96f425412c9f8ad5d9a9a05c9831d0728e2338d", "parent": null, - "signature": "z5nGqUvrmfiSyLjNCHWTWYvVMcPUZcvo9TxPKzEKXYBdSgUzbrqf1cYsmpGgbQvYunnsrLSsubEmxZaRdKM4quqQR", - "title": "Initial revision", - "type": "revision" + "signature": "z5nGqUvrmfiSyLjNCHWTWYvVMcPUZcvo9TxPKzEKXYBdSgUzbrqf1cYsmpGgbQvYunnsrLSsubEmxZaRdKM4quqQR" } ``` diff --git a/crates/radicle-cli/src/commands/cob.rs b/crates/radicle-cli/src/commands/cob.rs index ae241acd..15a37ea2 100644 --- a/crates/radicle-cli/src/commands/cob.rs +++ b/crates/radicle-cli/src/commands/cob.rs @@ -11,10 +11,10 @@ use nonempty::NonEmpty; use radicle::cob; use radicle::cob::store::CobAction; +use radicle::cob::stream::CobStream as _; +use radicle::git; use radicle::prelude::*; -use radicle::storage::git; - -use serde_json::json; +use radicle::storage; use crate::git::Rev; use crate::terminal as term; @@ -54,6 +54,10 @@ Create, Update options Log options --format (pretty | json) Desired output format (default: pretty) + --from Git object ID of the commit of the operation to + start iterating at. + --until Git object ID of the commit of the operation to + stop iterating at. Show options @@ -92,6 +96,8 @@ enum Operation { type_name: FilteredTypeName, oid: Rev, format: Format, + from: Option, + until: Option, }, Migrate, Show { @@ -173,7 +179,10 @@ impl std::fmt::Display for FilteredTypeName { } impl Embed { - fn try_into_bytes(self, repo: &git::Repository) -> anyhow::Result> { + fn try_into_bytes( + self, + repo: &storage::git::Repository, + ) -> anyhow::Result> { Ok(match self.content { EmbedContent::Hash(hash) => cob::Embed { name: self.name, @@ -217,6 +226,8 @@ impl Args for Options { let mut message: Option = None; let mut embeds: Vec = vec![]; let mut actions: Option = None; + let mut from: Option = None; + let mut until: Option = None; while let Some(arg) = parser.next()? { match (&op, &arg) { @@ -279,6 +290,14 @@ impl Args for Options { (Update | Create, Value(val)) => { actions = Some(PathBuf::from(term::args::string(val))); } + (Log, Long("from")) => { + let v = parser.value()?; + from = Some(term::args::rev(&v)?); + } + (Log, Long("until")) => { + let v = parser.value()?; + until = Some(term::args::rev(&v)?); + } _ => return Err(anyhow!(arg.unexpected())), } } @@ -317,6 +336,8 @@ impl Args for Options { type_name, oid: oids.pop().ok_or_else(missing_oid)?, format, + from, + until, }, Migrate => Operation::Migrate, Show => { @@ -422,15 +443,44 @@ pub fn run(Options { op }: Options, ctx: impl term::Context) -> anyhow::Result<( type_name, oid, format, + from, + until, } => { let repo = storage.repository(rid)?; let oid = oid.resolve(&repo.backend)?; - let ops = cob::store::ops(&oid, type_name.as_ref(), &repo)?; - for op in ops.into_iter().rev() { - match format { - Format::Json => print_op_json(op)?, - Format::Pretty => print_op_pretty(op)?, + let from = from.map(|from| from.resolve(&repo.backend)).transpose()?; + let until = until + .map(|until| until.resolve(&repo.backend)) + .transpose()?; + + match type_name { + Issue => operations::( + &cob::issue::TYPENAME, + oid, + from, + until, + &repo, + format, + )?, + Patch => operations::( + &cob::patch::TYPENAME, + oid, + from, + until, + &repo, + format, + )?, + Identity => operations::( + &cob::identity::TYPENAME, + oid, + from, + until, + &repo, + format, + )?, + Other(type_name) => { + operations::(&type_name, oid, from, until, &repo, format)? } } } @@ -509,7 +559,7 @@ pub fn run(Options { op }: Options, ctx: impl term::Context) -> anyhow::Result<( fn show( oids: Vec, - repo: &git::Repository, + repo: &storage::git::Repository, type_name: FilteredTypeName, profile: &Profile, ) -> Result<(), anyhow::Error> { @@ -578,7 +628,10 @@ fn show( Ok(()) } -fn print_op_pretty(op: cob::Op>) -> anyhow::Result<()> { +fn print_op_pretty(op: cob::Op) -> anyhow::Result<()> +where + A: serde::Serialize, +{ let time = DateTime::::from( std::time::UNIX_EPOCH + std::time::Duration::from_secs(op.timestamp.as_secs()), ) @@ -597,8 +650,7 @@ fn print_op_pretty(op: cob::Op>) -> anyhow::Result<()> { term::print(format!("date {time}")); term::blank(); for action in op.actions { - let obj: serde_json::Value = serde_json::from_slice(&action)?; - let val = serde_json::to_string_pretty(&obj)?; + let val = serde_json::to_string_pretty(&action)?; for line in val.lines() { term::indented(term::format::dim(line)); } @@ -607,21 +659,11 @@ fn print_op_pretty(op: cob::Op>) -> anyhow::Result<()> { Ok(()) } -fn print_op_json(op: cob::Op>) -> anyhow::Result<()> { - let mut ser = json!(op); - ser.as_object_mut() - .expect("ops must serialize to objects") - .insert( - "actions".to_string(), - json!(op - .actions - .iter() - .map(|action: &Vec| -> Result { - serde_json::from_slice(action) - }) - .collect::, _>>()?), - ); - term::print(ser); +fn print_op_json(op: cob::Op) -> anyhow::Result<()> +where + A: serde::Serialize, +{ + term::print(serde_json::to_value(&op)?); Ok(()) } @@ -650,3 +692,38 @@ where NonEmpty::from_vec(read_jsonl(reader)?) .ok_or_else(|| anyhow!("at least one action is required")) } + +fn operations( + typename: &cob::TypeName, + oid: cob::ObjectId, + from: Option, + until: Option, + repo: &storage::git::Repository, + format: Format, +) -> anyhow::Result<()> +where + A: serde::Serialize, + A: for<'de> serde::Deserialize<'de>, +{ + let history = cob::stream::CobRange::new(typename, &oid); + let stream = cob::stream::Stream::::new(&repo.backend, history, typename.clone()); + let iter = match (from, until) { + (None, None) => stream.all()?, + (None, Some(until)) => stream.until(until)?, + (Some(from), None) => stream.since(from)?, + (Some(from), Some(until)) => stream.range(from, until)?, + }; + + // Reverse + let iter = iter.collect::>().into_iter().rev(); + + for op in iter { + let op = op?; + match format { + Format::Json => print_op_json(op)?, + Format::Pretty => print_op_pretty(op)?, + } + } + + Ok(()) +} diff --git a/crates/radicle-cli/tests/commands.rs b/crates/radicle-cli/tests/commands.rs index fffeee2d..9c7ae275 100644 --- a/crates/radicle-cli/tests/commands.rs +++ b/crates/radicle-cli/tests/commands.rs @@ -164,6 +164,11 @@ fn rad_cob_migrate() { .unwrap(); } +#[test] +fn rad_cob_operations() { + Environment::alice(["rad-init", "rad-cob-operations"]); +} + #[test] #[ignore = "part of many other tests"] fn rad_init() {