Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
fixup! cli: rad cob ops
✗ CI failure Lorenz Leutgeb committed 10 months ago
commit 243b1c77d445092337237b6c6e173c2d90e9cef9
parent 4805ee3ad4302e3763e229e3e43e466524edabaf
1 failed 1 pending (2 total) View logs
5 files changed +204 -310
modified 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
modified 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"
    }

```
modified crates/radicle-cli/examples/rad-cob-operations.md
@@ -1,4 +1,4 @@
-
The `rad cob` command provides a subcommand, `ops`, for inspecting the
+
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:
@@ -22,97 +22,63 @@ $ rad issue comment d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --message "Max powe
$ rad issue assign d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-announce
```

-
Now, let's see the list of operations using `rad cob ops`:
+
Now, let's see the list of operations using `rad cob log`:

```
-
$ rad cob ops --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
-
{
-
  "id": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe",
-
  "actions": [
+
$ 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": "comment",
-
      "body": "Flux capacitor power requirements exceed current supply"
-
    },
+
      "type": "assign",
+
      "assignees": [
+
        "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
+
      ]
+
    }
+

+
commit   3c849c9b555b18be9a1f6c71fb254ba000de8cfe
+
resource 0656c217f917c3e06234771e9ecae53aba5e173e
+
parent   256908937f3cda8df522d5a3ba442eb935c3f11b
+
author   z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
date     Thu, 15 Dec 2022 17:28:04 +0000
+

    {
-
      "type": "edit",
-
      "title": "flux capacitor underpowered"
+
      "type": "comment",
+
      "body": "Max power!",
+
      "replyTo": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe"
    }
-
  ],
-
  "author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
  "timestamp": 1671125284000,
-
  "parents": [],
-
  "related": [],
-
  "identity": "0656c217f917c3e06234771e9ecae53aba5e173e",
-
  "manifest": {
-
    "typeName": "xyz.radicle.issue",
-
    "version": 1
-
  }
-
}
-
{
-
  "id": "256908937f3cda8df522d5a3ba442eb935c3f11b",
-
  "actions": [
+

+
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
    }
-
  ],
-
  "author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
  "timestamp": 1671125284000,
-
  "parents": [
-
    "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe"
-
  ],
-
  "related": [],
-
  "identity": "0656c217f917c3e06234771e9ecae53aba5e173e",
-
  "manifest": {
-
    "typeName": "xyz.radicle.issue",
-
    "version": 1
-
  }
-
}
-
{
-
  "id": "3c849c9b555b18be9a1f6c71fb254ba000de8cfe",
-
  "actions": [
+

+
commit   d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
+
resource 0656c217f917c3e06234771e9ecae53aba5e173e
+
author   z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
date     Thu, 15 Dec 2022 17:28:04 +0000
+

    {
      "type": "comment",
-
      "body": "Max power!",
-
      "replyTo": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe"
+
      "body": "Flux capacitor power requirements exceed current supply"
    }
-
  ],
-
  "author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
  "timestamp": 1671125284000,
-
  "parents": [
-
    "256908937f3cda8df522d5a3ba442eb935c3f11b"
-
  ],
-
  "related": [],
-
  "identity": "0656c217f917c3e06234771e9ecae53aba5e173e",
-
  "manifest": {
-
    "typeName": "xyz.radicle.issue",
-
    "version": 1
-
  }
-
}
-
{
-
  "id": "376ba71113603004eae3c1b125c58cdc41d36b73",
-
  "actions": [
+

    {
-
      "type": "assign",
-
      "assignees": [
-
        "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
-
      ]
+
      "type": "edit",
+
      "title": "flux capacitor underpowered"
    }
-
  ],
-
  "author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
  "timestamp": 1671125284000,
-
  "parents": [
-
    "3c849c9b555b18be9a1f6c71fb254ba000de8cfe"
-
  ],
-
  "related": [],
-
  "identity": "0656c217f917c3e06234771e9ecae53aba5e173e",
-
  "manifest": {
-
    "typeName": "xyz.radicle.issue",
-
    "version": 1
-
  }
-
}
+

```

We can also limit the range of operations, using the `--from` and `--until`
@@ -128,10 +94,10 @@ author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date     Thu, 15 Dec 2022 17:28:04 +0000

    {
+
      "type": "assign",
      "assignees": [
        "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
-
      ],
-
      "type": "assign"
+
      ]
    }

commit   3c849c9b555b18be9a1f6c71fb254ba000de8cfe
@@ -141,9 +107,9 @@ author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date     Thu, 15 Dec 2022 17:28:04 +0000

    {
+
      "type": "comment",
      "body": "Max power!",
-
      "replyTo": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe",
-
      "type": "comment"
+
      "replyTo": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe"
    }

commit   256908937f3cda8df522d5a3ba442eb935c3f11b
@@ -153,10 +119,10 @@ author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date     Thu, 15 Dec 2022 17:28:04 +0000

    {
-
      "active": true,
+
      "type": "comment.react",
      "id": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe",
      "reaction": "✨",
-
      "type": "comment.react"
+
      "active": true
    }

commit   d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
@@ -165,13 +131,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"
    }

```
@@ -180,148 +146,96 @@ If we provide only the `--from` option, the operations we get back start from th
revision and go until the end:

```
-
$ rad cob ops --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --from 3c849c9b555b18be9a1f6c71fb254ba000de8cfe
-
{
-
  "id": "3c849c9b555b18be9a1f6c71fb254ba000de8cfe",
-
  "actions": [
-
    {
-
      "type": "comment",
-
      "body": "Max power!",
-
      "replyTo": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe"
-
    }
-
  ],
-
  "author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
  "timestamp": 1671125284000,
-
  "parents": [
-
    "256908937f3cda8df522d5a3ba442eb935c3f11b"
-
  ],
-
  "related": [],
-
  "identity": "0656c217f917c3e06234771e9ecae53aba5e173e",
-
  "manifest": {
-
    "typeName": "xyz.radicle.issue",
-
    "version": 1
-
  }
-
}
-
{
-
  "id": "376ba71113603004eae3c1b125c58cdc41d36b73",
-
  "actions": [
+
$ 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"
      ]
    }
-
  ],
-
  "author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
  "timestamp": 1671125284000,
-
  "parents": [
-
    "3c849c9b555b18be9a1f6c71fb254ba000de8cfe"
-
  ],
-
  "related": [],
-
  "identity": "0656c217f917c3e06234771e9ecae53aba5e173e",
-
  "manifest": {
-
    "typeName": "xyz.radicle.issue",
-
    "version": 1
-
  }
-
}
+

+
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 ops --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --until 256908937f3cda8df522d5a3ba442eb935c3f11b
-
{
-
  "id": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe",
-
  "actions": [
+
$ 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"
    }
-
  ],
-
  "author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
  "timestamp": 1671125284000,
-
  "parents": [],
-
  "related": [],
-
  "identity": "0656c217f917c3e06234771e9ecae53aba5e173e",
-
  "manifest": {
-
    "typeName": "xyz.radicle.issue",
-
    "version": 1
-
  }
-
}
-
{
-
  "id": "256908937f3cda8df522d5a3ba442eb935c3f11b",
-
  "actions": [
-
    {
-
      "type": "comment.react",
-
      "id": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe",
-
      "reaction": "✨",
-
      "active": true
-
    }
-
  ],
-
  "author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
  "timestamp": 1671125284000,
-
  "parents": [
-
    "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe"
-
  ],
-
  "related": [],
-
  "identity": "0656c217f917c3e06234771e9ecae53aba5e173e",
-
  "manifest": {
-
    "typeName": "xyz.radicle.issue",
-
    "version": 1
-
  }
-
}
+

```

Finally, if we provide both, we get back that exact range:

```
-
$ rad cob ops --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --from 256908937f3cda8df522d5a3ba442eb935c3f11b --until 3c849c9b555b18be9a1f6c71fb254ba000de8cfe
-
{
-
  "id": "256908937f3cda8df522d5a3ba442eb935c3f11b",
-
  "actions": [
+
$ 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
    }
-
  ],
-
  "author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
  "timestamp": 1671125284000,
-
  "parents": [
-
    "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe"
-
  ],
-
  "related": [],
-
  "identity": "0656c217f917c3e06234771e9ecae53aba5e173e",
-
  "manifest": {
-
    "typeName": "xyz.radicle.issue",
-
    "version": 1
-
  }
-
}
-
{
-
  "id": "3c849c9b555b18be9a1f6c71fb254ba000de8cfe",
-
  "actions": [
-
    {
-
      "type": "comment",
-
      "body": "Max power!",
-
      "replyTo": "d87dcfe8c2b3200e78b128d9b959cfdf7063fefe"
-
    }
-
  ],
-
  "author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
  "timestamp": 1671125284000,
-
  "parents": [
-
    "256908937f3cda8df522d5a3ba442eb935c3f11b"
-
  ],
-
  "related": [],
-
  "identity": "0656c217f917c3e06234771e9ecae53aba5e173e",
-
  "manifest": {
-
    "typeName": "xyz.radicle.issue",
-
    "version": 1
-
  }
-
}
+

```
modified crates/radicle-cli/examples/rad-id.md
@@ -100,12 +100,12 @@ author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date     Thu, 15 Dec 2022 17:28:04 +0000

    {
-
      "blob": "053541ba7b90534b35dd8718e0ceaa408979b02b",
+
      "type": "revision",
+
      "title": "Add Bob",
      "description": "Add Bob as a delegate",
+
      "blob": "053541ba7b90534b35dd8718e0ceaa408979b02b",
      "parent": "0656c217f917c3e06234771e9ecae53aba5e173e",
-
      "signature": "z3AyzixN2eWLtRfQWowtBXwWyRH3iJ8oJ25W6KFYFw5ANLntbzfavge15muNU6AVAUkxSxQvgg9yh2gupbUecavQY",
-
      "title": "Add Bob",
-
      "type": "revision"
+
      "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"
    }

```
modified crates/radicle-cli/src/commands/cob.rs
@@ -16,8 +16,6 @@ use radicle::git;
use radicle::prelude::*;
use radicle::storage;

-
use serde_json::json;
-

use crate::git::Rev;
use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
@@ -34,7 +32,6 @@ Usage
    rad cob create  --repo <rid> --type <typename> <filename> [<option>...]
    rad cob list    --repo <rid> --type <typename>
    rad cob log     --repo <rid> --type <typename> --object <oid> [<option>...]
-
    rad cob ops     --repo <rid> --type <typename> --object <oid> [<option>...]
    rad cob migrate [<option>...]
    rad cob show    --repo <rid> --type <typename> --object <oid> [<option>...]
    rad cob update  --repo <rid> --type <typename> --object <oid> <filename>
@@ -45,7 +42,6 @@ Commands
    create                      Create a new COB of a given type given initial actions
    list                        List all COBs of a given type (--object is not needed)
    log                         Print a log of all raw operations on a COB
-
    ops                         Print all the operations for a a COB
    migrate                     Migrate the COB database to the latest version
    update                      Add actions to a COB
    show                        Print the state of COBs
@@ -58,16 +54,15 @@ Create, Update options
Log options

    --format (pretty | json)    Desired output format (default: pretty)
+
    --from <oid>                Git object ID of the commit of the operation to
+
                                start iterating at.
+
    --until <oid>               Git object ID of the commit of the operation to
+
                                stop iterating at.

Show options

    --format json               Desired output format (default: json)

-
Ops options
-

-
    --from <oid>                Where to start the action iteration
-
    --until <oid>               Where to end the action iteration
-

Other options

    --help                      Print help
@@ -82,7 +77,6 @@ enum OperationName {
    Log,
    Migrate,
    Show,
-
    Operations,
}

enum Operation {
@@ -102,6 +96,8 @@ enum Operation {
        type_name: FilteredTypeName,
        oid: Rev,
        format: Format,
+
        from: Option<Rev>,
+
        until: Option<Rev>,
    },
    Migrate,
    Show {
@@ -117,13 +113,6 @@ enum Operation {
        actions: PathBuf,
        embeds: Vec<Embed>,
    },
-
    Operations {
-
        rid: RepoId,
-
        rev: Rev,
-
        from: Option<Rev>,
-
        until: Option<Rev>,
-
        type_name: FilteredTypeName,
-
    },
}

enum Format {
@@ -225,7 +214,6 @@ impl Args for Options {
                "log" => Log,
                "migrate" => Migrate,
                "show" => Show,
-
                "ops" => Operations,
                unknown => bail!("unknown operation '{unknown}'"),
            },
            Some(arg) => return Err(anyhow!(arg.unexpected())),
@@ -253,7 +241,7 @@ impl Args for Options {
                    let v = string(&parser.value()?);
                    type_name = Some(FilteredTypeName::from(cob::TypeName::from_str(&v)?));
                }
-
                (Update | Log | Show | Operations, Long("object") | Short('o')) => {
+
                (Update | Log | Show, Long("object") | Short('o')) => {
                    let v = string(&parser.value()?);
                    oids.push(Rev::from(v));
                }
@@ -302,11 +290,11 @@ impl Args for Options {
                (Update | Create, Value(val)) => {
                    actions = Some(PathBuf::from(term::args::string(val)));
                }
-
                (Operations, Long("from")) => {
+
                (Log, Long("from")) => {
                    let v = parser.value()?;
                    from = Some(term::args::rev(&v)?);
                }
-
                (Operations, Long("until")) => {
+
                (Log, Long("until")) => {
                    let v = parser.value()?;
                    until = Some(term::args::rev(&v)?);
                }
@@ -348,6 +336,8 @@ impl Args for Options {
                        type_name,
                        oid: oids.pop().ok_or_else(missing_oid)?,
                        format,
+
                        from,
+
                        until,
                    },
                    Migrate => Operation::Migrate,
                    Show => {
@@ -370,15 +360,6 @@ impl Args for Options {
                        })?,
                        embeds,
                    },
-
                    OperationName::Operations => Operation::Operations {
-
                        rid,
-
                        rev: oids.pop().ok_or_else(|| {
-
                            anyhow!("an object id must be specified with `--object`")
-
                        })?,
-
                        from,
-
                        until,
-
                        type_name,
-
                    },
                },
            },
            vec![],
@@ -462,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::Action>(
+
                    &cob::issue::TYPENAME,
+
                    oid,
+
                    from,
+
                    until,
+
                    &repo,
+
                    format,
+
                )?,
+
                Patch => operations::<cob::patch::Action>(
+
                    &cob::patch::TYPENAME,
+
                    oid,
+
                    from,
+
                    until,
+
                    &repo,
+
                    format,
+
                )?,
+
                Identity => operations::<cob::identity::Action>(
+
                    &cob::identity::TYPENAME,
+
                    oid,
+
                    from,
+
                    until,
+
                    &repo,
+
                    format,
+
                )?,
+
                Other(type_name) => {
+
                    operations::<serde_json::Value>(&type_name, oid, from, until, &repo, format)?
                }
            }
        }
@@ -543,38 +553,6 @@ pub fn run(Options { op }: Options, ctx: impl term::Context) -> anyhow::Result<(

            println!("{oid}");
        }
-
        Operations {
-
            rid,
-
            rev: id,
-
            from,
-
            until,
-
            type_name,
-
        } => {
-
            let repo = storage.repository(rid)?;
-
            let id = id.resolve(&repo.backend)?;
-
            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::Action>(&cob::issue::TYPENAME, id, from, until, &repo)?
-
                }
-
                Patch => {
-
                    operations::<cob::patch::Action>(&cob::patch::TYPENAME, id, from, until, &repo)?
-
                }
-
                Identity => operations::<cob::identity::Action>(
-
                    &cob::identity::TYPENAME,
-
                    id,
-
                    from,
-
                    until,
-
                    &repo,
-
                )?,
-
                Other(type_name) => {
-
                    operations::<serde_json::Value>(&type_name, id, from, until, &repo)?
-
                }
-
            }
-
        }
    }
    Ok(())
}
@@ -650,7 +628,10 @@ fn show(
    Ok(())
}

-
fn print_op_pretty(op: cob::Op<Vec<u8>>) -> anyhow::Result<()> {
+
fn print_op_pretty<A>(op: cob::Op<A>) -> anyhow::Result<()>
+
where
+
    A: serde::Serialize,
+
{
    let time = DateTime::<Utc>::from(
        std::time::UNIX_EPOCH + std::time::Duration::from_secs(op.timestamp.as_secs()),
    )
@@ -669,8 +650,7 @@ fn print_op_pretty(op: cob::Op<Vec<u8>>) -> 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));
        }
@@ -679,21 +659,11 @@ fn print_op_pretty(op: cob::Op<Vec<u8>>) -> anyhow::Result<()> {
    Ok(())
}

-
fn print_op_json(op: cob::Op<Vec<u8>>) -> 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<u8>| -> Result<serde_json::Value, _> {
-
                    serde_json::from_slice(action)
-
                })
-
                .collect::<Result<Vec<serde_json::Value>, _>>()?),
-
        );
-
    term::print(ser);
+
fn print_op_json<A>(op: cob::Op<A>) -> anyhow::Result<()>
+
where
+
    A: serde::Serialize,
+
{
+
    term::print(serde_json::to_value(&op)?);
    Ok(())
}

@@ -729,6 +699,7 @@ fn operations<A>(
    from: Option<git::Oid>,
    until: Option<git::Oid>,
    repo: &storage::git::Repository,
+
    format: Format,
) -> anyhow::Result<()>
where
    A: serde::Serialize,
@@ -743,9 +714,15 @@ where
        (Some(from), Some(until)) => stream.range(from, until)?,
    };

-
    for action in iter {
-
        let action = action?;
-
        println!("{}", serde_json::to_string_pretty(&action)?);
+
    // Reverse
+
    let iter = iter.collect::<Vec<_>>().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(())