rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 heartwoodf30760d6bb86d2978a5ed4df8ee45b9aa97778b4
{
"request": "trigger",
"version": 1,
"event_type": "patch",
"repository": {
"id": "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
"name": "heartwood",
"description": "Radicle Heartwood Protocol & Stack",
"private": false,
"default_branch": "master",
"delegates": [
"did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
"did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz"
]
},
"action": "Created",
"patch": {
"id": "6833bcc7c9278a489e7eda02a7e5fc82caef6f62",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"title": "cli/cob: Create and Update, External COBs",
"state": {
"status": "merged",
"conflicts": []
},
"before": "3018223328d566c003e2bf37959302c6f314be42",
"after": "f30760d6bb86d2978a5ed4df8ee45b9aa97778b4",
"commits": [
"f30760d6bb86d2978a5ed4df8ee45b9aa97778b4",
"e4d23fe56a7f7329ac98991bc55bb043ddede617",
"547614a5bbdca85e1cbd205b37cae7e25ba01273"
],
"target": "3419821106e274c96f56948a05d2f51f28f5a6c7",
"labels": [],
"assignees": [],
"revisions": [
{
"id": "6833bcc7c9278a489e7eda02a7e5fc82caef6f62",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "- Rewrite command/option validation\n- Introduce `rad cob create` to create a new COB\n- Introduce `rad cob update` to append actions to a COB\n- Allow COBs with external evaluation via `rad-cob-*` helpers\n\nRecording of a very early version, where `rad cob show` still is broken: <https://asciinema.org/a/9m7QkOuyaeKcJ3HCRdnA90yKk>",
"base": "3403a66d0fc9c9cfab97eddaf5adeab40dc3bf23",
"oid": "7021c7c0fbf8695f81bb43e77a25225a3d98cce5",
"timestamp": 1716660913
},
{
"id": "d34785755b874ad1f8abeaeac6f540e85ed83946",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "",
"base": "3403a66d0fc9c9cfab97eddaf5adeab40dc3bf23",
"oid": "ac11b6f304f7cc3e27c398e19e7cf42b05ad530a",
"timestamp": 1716661199
},
{
"id": "bd4c5531c1777dfc79858b3cac63b3534bf17a34",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "",
"base": "3403a66d0fc9c9cfab97eddaf5adeab40dc3bf23",
"oid": "de23bd3232f27eb45a721dfdba1d6b7bf06f552d",
"timestamp": 1716843764
},
{
"id": "555eb5b7c6df7e9a699da4410e2c8aa522a489db",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "",
"base": "ee1c867800eacea37d489d966622d0aff0af0b70",
"oid": "bc5675e9033b1228d76cb481583f95964e42a1f4",
"timestamp": 1722338373
},
{
"id": "be936f387ce0385b07f8024900b5e96057ff1b52",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "",
"base": "ee1c867800eacea37d489d966622d0aff0af0b70",
"oid": "cc5c5fe60872ba0bf3fee3632a32af73078019b8",
"timestamp": 1722339365
},
{
"id": "949deb030907436cfa9b1d83cb2678e249464402",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "",
"base": "ee1c867800eacea37d489d966622d0aff0af0b70",
"oid": "f3b3ebc5ffae64586a19c1dc9d4e2d0d7e0af8e0",
"timestamp": 1722342676
},
{
"id": "aaa53a8405a33bd35a6fd28c35cf83684311dc5e",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "",
"base": "86a04439168454ab7e2ca4ba69b2337d581c0ce0",
"oid": "f71c7507876ddad17df5ec854ada9bfba9abdf8d",
"timestamp": 1724060278
},
{
"id": "0c176812a69397d0038945bc7518fb512f721323",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "",
"base": "86a04439168454ab7e2ca4ba69b2337d581c0ce0",
"oid": "c014abe721e7eda8f2f6133c9f976fd758e9c2bf",
"timestamp": 1724060810
},
{
"id": "adfed4cd1755b1cc55b984056df6c94f9760144d",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Improvements based on Fintan's comments.",
"base": "86a04439168454ab7e2ca4ba69b2337d581c0ce0",
"oid": "1be073ec5bf0684144d0d4102d2001437ad3651d",
"timestamp": 1724250244
},
{
"id": "f6b70ccac5272ebc74f207b2ef2f6c53427ceda1",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Fixes after review by Fintan.",
"base": "4e112aaeaad8919c10f0a30c9f43dab541d653fa",
"oid": "c02d4aff638ab5e491b41634707f85bdce770cc5",
"timestamp": 1724958533
},
{
"id": "6488f045391af42e0f213c430489e85634272545",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "",
"base": "4e112aaeaad8919c10f0a30c9f43dab541d653fa",
"oid": "1963e43ba98a1691b6cc98362421ed80d29260b2",
"timestamp": 1725035730
},
{
"id": "1024652048ccd536ade7de7b442a466590f0f9a4",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Rebase (no conflicts).",
"base": "d39ba83cfc7c7685548d18652fef039e25b19276",
"oid": "ed0de6afc2a73f806108efd59d82007e7d8e867d",
"timestamp": 1725724990
},
{
"id": "587e5ce6e6788ada20dc6b68f79ea467b5d9adca",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Work in feedback by Alexis.",
"base": "d39ba83cfc7c7685548d18652fef039e25b19276",
"oid": "6afba41e13c74ea47abe81048df3ecd52d7fe23c",
"timestamp": 1728054091
},
{
"id": "021715933d16742125df04bae797a0faa731c7b6",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Rewrite external COBs interaction with helper",
"base": "d9c76893a144fd787654613f2bfb919613014a71",
"oid": "74941ad86f65d180122c67a368ab1e7957b07243",
"timestamp": 1734002782
},
{
"id": "a51c0bd563ae7db5a7d68230b5bcc52bf17c84d5",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "",
"base": "d9c76893a144fd787654613f2bfb919613014a71",
"oid": "9f51bf94e213aa24a13f04fa5e5c4755d7eb2f47",
"timestamp": 1734084202
},
{
"id": "98141c8fe6540998f0d871e03788049db10c5758",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Small improvements.",
"base": "d9c76893a144fd787654613f2bfb919613014a71",
"oid": "296f56a3cdd5da4740b88467fce44c4aceb1d44d",
"timestamp": 1734193295
},
{
"id": "9b783a5fa82aaa4b65e1d9ced47a0e20879a3b93",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Work in Fintan's feedback.",
"base": "d9c76893a144fd787654613f2bfb919613014a71",
"oid": "afbae03bf916c7dc6450b67e063b1890ee14e3a3",
"timestamp": 1736611474
},
{
"id": "ebec0f063e947a1885ca57f38cde109c8e3af4b4",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Fixes after review.",
"base": "6dcd56275e606396ec82f4e95fab1d736e8152a4",
"oid": "78ee75ffcbfad4bf20432da5934105761beb1108",
"timestamp": 1745494474
},
{
"id": "eb0b8d935658babf2d1f193a1afe15df3452b519",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Fix handling of embeds.",
"base": "6dcd56275e606396ec82f4e95fab1d736e8152a4",
"oid": "df87ab5a1de3dc925c1df46b29c7aaf9afebfa03",
"timestamp": 1745495350
},
{
"id": "61a1be3afd23a133f70c08956e452db1ebdefec4",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Oops, now fixed handling of embeds.",
"base": "6dcd56275e606396ec82f4e95fab1d736e8152a4",
"oid": "3dd71f594d36e161c1fe33b5ec46303e62838d86",
"timestamp": 1745495446
},
{
"id": "533f4553e862da638e715ba507607b4b1786748e",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Rebase and implement validation.",
"base": "3018223328d566c003e2bf37959302c6f314be42",
"oid": "ec914bd3db243e190962808609e32bf2054d6a26",
"timestamp": 1745826229
},
{
"id": "717189f73c7d5796f438cd6caab11da49e9cabbf",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Remove some noise",
"base": "3018223328d566c003e2bf37959302c6f314be42",
"oid": "eaa88907ffa0bfb8b92394845cba234341f1d334",
"timestamp": 1745826642
},
{
"id": "da3b3b41ea8db68a6bb45e554ca9628e362fce30",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Minor fix",
"base": "3018223328d566c003e2bf37959302c6f314be42",
"oid": "3224f8a3f04cc579766cb4f6c3f51ed5ca7604ec",
"timestamp": 1745836356
},
{
"id": "68a9a8590c7e399654e8d298d1eb9543c2c1bb44",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Rename \"require reference\" to \"produce identifier\".",
"base": "3018223328d566c003e2bf37959302c6f314be42",
"oid": "e70a14aa5ef80b883d238715feb9a7a09e345ecf",
"timestamp": 1745839090
},
{
"id": "aeb13c8d607899dc379dc54b72c982abbd41d84a",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Format",
"base": "3018223328d566c003e2bf37959302c6f314be42",
"oid": "8fdf26eef69e59ec38a4f3af44a4a621d7370adf",
"timestamp": 1745842540
},
{
"id": "0dcf7de11ad07082d1a28154d41e51d15411b49d",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"description": "Fix mysterious testing bug together with Fintan.",
"base": "3018223328d566c003e2bf37959302c6f314be42",
"oid": "f30760d6bb86d2978a5ed4df8ee45b9aa97778b4",
"timestamp": 1745850032
}
]
}
}
{
"response": "triggered",
"run_id": {
"id": "44992653-4652-4d7a-b3ea-3e50c517eabd"
},
"info_url": "https://cci.rad.levitte.org//44992653-4652-4d7a-b3ea-3e50c517eabd.html"
}
Started at: 2026-09-08 13:34:41.260832+02:00
Commands:
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 .
✓ Creating checkout in ./...
✓ Remote cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT added
✓ Remote-tracking branch cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT/master created for z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT
✓ Remote fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM added
✓ Remote-tracking branch fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM/master created for z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM
✓ 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/44992653-4652-4d7a-b3ea-3e50c517eabd/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 189 issues · 39 patches │
╰────────────────────────────────────╯
Run `cd ./.` to go to the repository directory.
Exit code: 0
$ rad patch checkout 6833bcc7c9278a489e7eda02a7e5fc82caef6f62
✓ Switched to branch patch/6833bcc at revision 0dcf7de
✓ Branch patch/6833bcc setup to track rad/patches/6833bcc7c9278a489e7eda02a7e5fc82caef6f62
Exit code: 0
$ git config advice.detachedHead false
Exit code: 0
$ git checkout f30760d6bb86d2978a5ed4df8ee45b9aa97778b4
HEAD is now at f30760d6 cob: Add CobAction::produces_identifier and validation
Exit code: 0
$ rad patch show 6833bcc7c9278a489e7eda02a7e5fc82caef6f62 -p
╭───────────────────────────────────────────────────────────────────────────────╮
│ Title cli/cob: Create and Update, External COBs │
│ Patch 6833bcc7c9278a489e7eda02a7e5fc82caef6f62 │
│ Author lorenz z6MkkPv…WX5sTEz │
│ Head f30760d6bb86d2978a5ed4df8ee45b9aa97778b4 │
│ Base 3018223328d566c003e2bf37959302c6f314be42 │
│ Target master │
│ Branches patch/6833bcc │
│ Commits ahead 0, behind 1079 │
│ Status merged │
│ │
│ - Rewrite command/option validation │
│ - Introduce `rad cob create` to create a new COB │
│ - Introduce `rad cob update` to append actions to a COB │
│ - Allow COBs with external evaluation via `rad-cob-*` helpers │
│ │
│ Recording of a very early version, where `rad cob show` still is broken: │
│ <https://asciinema.org/a/9m7QkOuyaeKcJ3HCRdnA90yKk> │
├───────────────────────────────────────────────────────────────────────────────┤
│ f30760d cob: Add CobAction::produces_identifier and validation │
│ e4d23fe cli: Introduce `cob [create|update]` │
│ 547614a cli-test: Add current working directory to PATH │
├───────────────────────────────────────────────────────────────────────────────┤
│ ● Revision 6833bcc @ 3403a66..7021c7c by lorenz z6MkkPv…WX5sTEz 2 years ago │
│ ↑ Revision d347857 @ 3403a66..ac11b6f by lorenz z6MkkPv…WX5sTEz 2 years ago │
│ ↑ Revision bd4c553 @ 3403a66..de23bd3 by lorenz z6MkkPv…WX5sTEz 2 years ago │
│ ↑ Revision 555eb5b @ ee1c867..bc5675e by lorenz z6MkkPv…WX5sTEz 2 years ago │
│ ↑ Revision be936f3 @ ee1c867..cc5c5fe by lorenz z6MkkPv…WX5sTEz 2 years ago │
│ ↑ Revision 949deb0 @ ee1c867..f3b3ebc by lorenz z6MkkPv…WX5sTEz 2 years ago │
│ ↑ Revision aaa53a8 @ 86a0443..f71c750 by lorenz z6MkkPv…WX5sTEz 2 years ago │
│ ↑ Revision 0c17681 @ 86a0443..c014abe by lorenz z6MkkPv…WX5sTEz 2 years ago │
│ ↑ Revision adfed4c @ 86a0443..1be073e by lorenz z6MkkPv…WX5sTEz 2 years ago │
│ ↑ Revision f6b70cc @ 4e112aa..c02d4af by lorenz z6MkkPv…WX5sTEz 2 years ago │
│ ↑ Revision 6488f04 @ 4e112aa..1963e43 by lorenz z6MkkPv…WX5sTEz 2 years ago │
│ ↑ Revision 1024652 @ d39ba83..ed0de6a by lorenz z6MkkPv…WX5sTEz 2 years ago │
│ ↑ Revision 587e5ce @ d39ba83..6afba41 by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision 0217159 @ d9c7689..74941ad by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision a51c0bd @ d9c7689..9f51bf9 by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision 98141c8 @ d9c7689..296f56a by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision 9b783a5 @ d9c7689..afbae03 by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ └─ ⋄ reviewed by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision ebec0f0 @ 6dcd562..78ee75f by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision eb0b8d9 @ 6dcd562..df87ab5 by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision 61a1be3 @ 6dcd562..3dd71f5 by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision 533f455 @ 3018223..ec914bd by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision 717189f @ 3018223..eaa8890 by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision da3b3b4 @ 3018223..3224f8a by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision 68a9a85 @ 3018223..e70a14a by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision aeb13c8 @ 3018223..8fdf26e by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision 0dcf7de @ 3018223..f30760d by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ └─ ✓ merged by fintohaps z6Mkire…SQZ3voM │
╰───────────────────────────────────────────────────────────────────────────────╯
commit f30760d6bb86d2978a5ed4df8ee45b9aa97778b4
Author: Lorenz Leutgeb <lorenz@leutgeb.xyz>
Date: Mon Apr 28 08:46:20 2025 +0200
cob: Add CobAction::produces_identifier and validation
Transactions will now fail/deny when adding multiple actions that
produce an identifier.
diff --git a/radicle/src/cob/identity.rs b/radicle/src/cob/identity.rs
index e1162dfcf..73dc74e3a 100644
--- a/radicle/src/cob/identity.rs
+++ b/radicle/src/cob/identity.rs
@@ -77,7 +77,11 @@ pub enum Action {
RevisionRedact { revision: RevisionId },
}
-impl CobAction for Action {}
+impl CobAction for Action {
+ fn produces_identifier(&self) -> bool {
+ matches!(self, Self::Revision { .. })
+ }
+}
/// Error applying an operation onto a state.
#[derive(Error, Debug)]
diff --git a/radicle/src/cob/issue.rs b/radicle/src/cob/issue.rs
index 825c12f2f..a53d20c67 100644
--- a/radicle/src/cob/issue.rs
+++ b/radicle/src/cob/issue.rs
@@ -935,7 +935,11 @@ pub enum Action {
},
}
-impl CobAction for Action {}
+impl CobAction for Action {
+ fn produces_identifier(&self) -> bool {
+ matches!(self, Self::Comment { .. })
+ }
+}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
@@ -946,8 +950,8 @@ mod test {
use crate::cob::{store::CobWithType, ActorId, Reaction};
use crate::git::Oid;
use crate::issue::cache::Issues as _;
- use crate::test;
use crate::test::arbitrary;
+ use crate::{assert_matches, test};
#[test]
fn test_concurrency() {
@@ -1688,6 +1692,29 @@ mod test {
assert_eq!(issue.comments().count(), 1);
}
+ #[test]
+ fn test_invalid_tx_reference() {
+ let test::setup::NodeWithRepo { node, repo, .. } = test::setup::NodeWithRepo::default();
+ let mut issues = Cache::no_cache(&*repo).unwrap();
+ let issue = issues
+ .create(
+ "My first issue",
+ "Blah blah blah.",
+ &[],
+ &[],
+ [],
+ &node.signer,
+ )
+ .unwrap();
+
+ // Comments require references, so adding two of them to the same transaction errors.
+ let mut tx: Transaction<Issue, test::storage::git::Repository> =
+ Transaction::<Issue, _>::default();
+ tx.comment("First reply", *issue.id, vec![]).unwrap();
+ let err = tx.comment("Second reply", *issue.id, vec![]).unwrap_err();
+ assert_matches!(err, cob::store::Error::ClashingIdentifiers(_, _));
+ }
+
#[test]
fn test_invalid_cob() {
use crate::crypto::test::signer::MockSigner;
diff --git a/radicle/src/cob/patch.rs b/radicle/src/cob/patch.rs
index e700a490e..1666c1d39 100644
--- a/radicle/src/cob/patch.rs
+++ b/radicle/src/cob/patch.rs
@@ -320,6 +320,16 @@ impl CobAction for Action {
_ => vec![],
}
}
+
+ fn produces_identifier(&self) -> bool {
+ matches!(
+ self,
+ Self::Revision { .. }
+ | Self::RevisionComment { .. }
+ | Self::Review { .. }
+ | Self::ReviewComment { .. }
+ )
+ }
}
/// Output of a merge.
diff --git a/radicle/src/cob/store.rs b/radicle/src/cob/store.rs
index 0511f0c72..bd67b2d3d 100644
--- a/radicle/src/cob/store.rs
+++ b/radicle/src/cob/store.rs
@@ -22,6 +22,23 @@ pub trait CobAction: Debug {
fn parents(&self) -> Vec<git::Oid> {
Vec::new()
}
+
+ /// The outcome of some actions are to be referred later.
+ /// For example, one action may create a comment, followed by another
+ /// action that may create a reply to the comment, referring to it.
+ /// Since actions are stored as part of [`crate::cob::op::Op`],
+ /// and operations are the smallest identifiable units,
+ /// this may lead to ambiguity.
+ /// It would not be possible to to, say, address one particular comment out
+ /// of two, if the corresponding actions of creations were part of the
+ /// same operation.
+ /// To help avoid this, implementations signal whether specific actions
+ /// require "their own" identifier.
+ /// This allows checking for multiple such actions before creating an
+ /// operation.
+ fn produces_identifier(&self) -> bool {
+ false
+ }
}
/// A collaborative object. Can be materialized from an operation history.
@@ -111,6 +128,8 @@ pub enum Error {
#[source]
err: git::raw::Error,
},
+ #[error("transaction already contains action {0} which produces an identifier, denying to add action {1} which also produces an identifier")]
+ ClashingIdentifiers(String, String),
}
/// Storage for collaborative objects of a specific type `T` in a single repository.
@@ -340,6 +359,13 @@ pub struct Transaction<T: Cob + cob::Evaluate<R>, R> {
actions: Vec<T::Action>,
embeds: Vec<Embed<Uri>>,
+ // Internal state kept for validation of the transaction.
+ // If an action that produces an identifier is added to
+ // the transaction, then this will track its index,
+ // so that adding a second action that produces an identifier
+ // can fail with a useful error.
+ produces_identifier: Option<usize>,
+
repo: PhantomData<R>,
type_name: TypeName,
}
@@ -349,6 +375,7 @@ impl<T: Cob + CobWithType + cob::Evaluate<R>, R> Default for Transaction<T, R> {
Self {
actions: Vec::new(),
embeds: Vec::new(),
+ produces_identifier: None,
repo: PhantomData,
type_name: T::type_name().clone(),
}
@@ -363,6 +390,7 @@ where
Self {
actions,
embeds,
+ produces_identifier: None,
repo: PhantomData,
type_name,
}
@@ -405,15 +433,29 @@ where
{
/// Add an action to this transaction.
pub fn push(&mut self, action: T::Action) -> Result<(), Error> {
+ if action.produces_identifier() {
+ if let Some(index) = self.produces_identifier {
+ return Err(Error::ClashingIdentifiers(
+ serde_json::to_string(&self.actions[index])?,
+ serde_json::to_string(&action)?,
+ ));
+ } else {
+ self.produces_identifier = Some(self.actions.len())
+ }
+ }
+
self.actions.push(action);
Ok(())
}
/// Add actions to this transaction.
- pub fn extend(&mut self, actions: impl IntoIterator<Item = T::Action>) -> Result<(), Error> {
- self.actions.extend(actions);
-
+ /// Note that we cannot implement [`std::iter::Extend`] because [`Self::push`]
+ /// validates the action being pushed, and therefore is falliable.
+ pub fn extend<I: IntoIterator<Item = T::Action>>(&mut self, actions: I) -> Result<(), Error> {
+ for action in actions {
+ self.push(action)?;
+ }
Ok(())
}
diff --git a/radicle/src/cob/thread.rs b/radicle/src/cob/thread.rs
index 08670dc6b..7ae4c3bf1 100644
--- a/radicle/src/cob/thread.rs
+++ b/radicle/src/cob/thread.rs
@@ -272,7 +272,11 @@ pub enum Action {
},
}
-impl cob::store::CobAction for Action {}
+impl cob::store::CobAction for Action {
+ fn produces_identifier(&self) -> bool {
+ matches!(self, Self::Comment { .. })
+ }
+}
impl From<Action> for nonempty::NonEmpty<Action> {
fn from(action: Action) -> Self {
commit e4d23fe56a7f7329ac98991bc55bb043ddede617
Author: Lorenz Leutgeb <lorenz@leutgeb.xyz>
Date: Wed May 22 16:44:31 2024 +0200
cli: Introduce `cob [create|update]`
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d706d4c4c..12f64eb13 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -36,6 +36,9 @@ Make sure all tests are passing with:
$ cargo test --workspace
+Some tests require `jq`. If `jq` is not detected, these tests will succeed
+without effectively testing anything.
+
## Checking the docs
If you make documentation changes, you may want to check whether there are any
diff --git a/flake.nix b/flake.nix
index 1cd448147..4cfa9f786 100644
--- a/flake.nix
+++ b/flake.nix
@@ -179,7 +179,7 @@
cargoExtraArgs = "-p ${name}";
doCheck = false;
- nativeBuildInputs = with pkgs; [asciidoctor installShellFiles];
+ nativeBuildInputs = with pkgs; [asciidoctor installShellFiles jq];
postInstall = ''
for page in ${lib.escapeShellArgs pages}; do
asciidoctor -d manpage -b manpage $page
diff --git a/radicle-cli/examples/rad-cob-multiset b/radicle-cli/examples/rad-cob-multiset
new file mode 100755
index 000000000..23f6de35b
--- /dev/null
+++ b/radicle-cli/examples/rad-cob-multiset
@@ -0,0 +1,32 @@
+#! /usr/bin/env -S jq --from-file --sort-keys --compact-output
+#
+# Models a multiset as a JSON object.
+#
+# Takes an operation which contains actions, each in one of two shapes:
+#
+# Add x:
+#
+# { "+": $x }
+#
+# Remove x:
+#
+# { "-": $x }
+#
+# (where `$x` is a string).
+#
+# These actions are reduced to a multiset represented by an
+# object. The key being the item to count (corresponding to `$x` above),
+# and the value being the count itself (corresponding to how many times
+# `$x` was added, minus how many times `$x` was removed).
+#
+# Errors if any unrecognizable action is encountered.
+#
+# For an example, see `rad-cob-multiset.md`.
+reduce
+ .op.actions[]
+as {"+": $p, "-": $m} (
+ .value;
+ .[$p // $m // ("invalid" | halt_error)] |= (
+ [. + (if $p then 1 else -1 end), 0] | max
+ )
+)
diff --git a/radicle-cli/examples/rad-cob-multiset.md b/radicle-cli/examples/rad-cob-multiset.md
new file mode 100644
index 000000000..bd52803a1
--- /dev/null
+++ b/radicle-cli/examples/rad-cob-multiset.md
@@ -0,0 +1,72 @@
+This example demonstrates handling of arbitrary Collaborative Objects (COBs) by means of an external program.
+The external program is called a "COB helper" (analogous to "remote helpers" that can be used to extend Git).
+It consumes the current state of the COB and at least one operation (all in [JSON]) to be applied to it via files whose names are passed as arguments.
+It returns the resulting COB by printing it to standard output in [JSON].
+
+For the sake of example, consider a simplified shopping list, where every item on the list is associated with an integral quantity:
+
+ - 5 Bananas
+ - 3 Zucchini
+ - 1 Bar of Chocolate
+
+One data structure that maps to this concept very well is a [multiset] (sometimes also called "bag").
+So, let us introduce a new COB type with the name `com.example.multiset` that implements multisets, which we will then use to model our shopping list.
+
+The COB we implement should allow two actions:
+
+ - The action `+`, which adds an item or, if already present, increases the associated quantity.
+ - The action `-`, which decreases the associated quantity of an item, if it is non-zero.
+
+We model actions as objects in [JSON], and a sequence of actions in [JSON Lines].
+An example sequence of actions looks as follows:
+
+``` ./groceries.jsonl
+{ "+": "jelly" }
+{ "+": "peanut butter" }
+{ "-": "jelly" }
+{ "-": "jelly" }
+{ "+": "salad" }
+{ "+": "salad" }
+```
+
+Starting with an empty grocery list, the expected result after evaluating all actions is:
+
+ - 0 Jelly (this could be omitted)
+ - 1 Peanut Butter
+ - 2 Salad
+
+We have a COB helper, named `rad-cob-multiset`, that implements evaluation of these actions using [jq].
+It reads the current state of the grocery list and operations containing actions from files given as arguments and writes the resulting grocery list to standard output.
+
+We do not invoke the program directly, but instead use `rad cob create`:
+
+```
+$ rad cob create --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type com.example.multiset --message "Create grocery shopping multiset" groceries.jsonl
+9bba8e6f83ef56b11151ef6ad02cc4595f982aab
+```
+
+We can verify that the COB evaluated as expected:
+
+```
+$ rad cob show --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type com.example.multiset --object 9bba8e6f83ef56b11151ef6ad02cc4595f982aab
+{"jelly":0,"peanut butter":1,"salad":2}
+```
+
+To apply actions to COBs that already exist, we can use `rad cob update`:
+
+```
+$ rad cob update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type com.example.multiset --object 9bba8e6f83ef56b11151ef6ad02cc4595f982aab --message "Modify grocery shopping multiset" groceries.jsonl
+d36aac77be13c1ca80edbfe7b7bf9b42c723f019
+```
+
+Again, we verify the result with `rad cob show`:
+
+```
+$ rad cob show --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type com.example.multiset --object 9bba8e6f83ef56b11151ef6ad02cc4595f982aab
+{"jelly":0,"peanut butter":2,"salad":4}
+```
+
+[multisets]: https://wikipedia.org/wiki/Multiset
+[JSON]: https://tools.ietf.org/html/std90
+[JSON Lines]: https://jsonlines.org/
+[jq]: https://github.com/jqlang/jq
\ No newline at end of file
diff --git a/radicle-cli/examples/rad-cob-update-identity.md b/radicle-cli/examples/rad-cob-update-identity.md
new file mode 100644
index 000000000..40083e8c2
--- /dev/null
+++ b/radicle-cli/examples/rad-cob-update-identity.md
@@ -0,0 +1,6 @@
+Updating the repository identity via `rad cob update` is forbidden:
+
+``` (fail)
+$ rad cob update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.id --object 0656c217f917c3e06234771e9ecae53aba5e173e --message "Danger" /dev/null
+✗ Error: Update of collaborative objects of type xyz.radicle.id is not supported.
+```
\ No newline at end of file
diff --git a/radicle-cli/examples/rad-cob-update.md b/radicle-cli/examples/rad-cob-update.md
new file mode 100644
index 000000000..86516b8bf
--- /dev/null
+++ b/radicle-cli/examples/rad-cob-update.md
@@ -0,0 +1,178 @@
+First off, we set up a patch.
+
+```
+$ git checkout -b changes
+$ touch README.md
+$ git add README.md
+$ git commit --message "Add README, just for the fun"
+[changes 03c02af] Add README, just for the fun
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+ create mode 100644 README.md
+```
+
+``` (stderr)
+$ git push rad -o patch.message="Add README, just for the fun" HEAD:refs/patches
+✓ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 opened
+To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ * [new reference] HEAD -> refs/patches
+```
+
+```
+$ touch LICENSE
+$ git add LICENSE
+$ git commit -v -m "Define the LICENSE"
+[changes 8945f61] Define the LICENSE
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+ create mode 100644 LICENSE
+```
+
+``` (stderr)
+$ git push -f -o patch.message="Add License"
+✓ Patch 89f7afb updated to revision 5d78dd5376453e25df5988ec86951c99cb73742c
+To compare against your previous revision 89f7afb, run:
+
+ git range-diff f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 03c02af4b12a593d17a06d38fae50a57fc3c339a 8945f6189adf027892c85ac57f7e9341049c2537
+
+To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 03c02af..8945f61 changes -> patches/89f7afb1511b976482b21f6b2f39aef7f4fb88a2
+```
+
+Let's look at the patch, to see what it looks like before editing it:
+
+```
+$ rad patch show 89f7afb
+╭─────────────────────────────────────────────────────────────────────╮
+│ Title Add README, just for the fun │
+│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
+│ Author alice (you) │
+│ Head 8945f6189adf027892c85ac57f7e9341049c2537 │
+│ Branches changes │
+│ Commits ahead 2, behind 0 │
+│ Status open │
+├─────────────────────────────────────────────────────────────────────┤
+│ 8945f61 Define the LICENSE │
+│ 03c02af Add README, just for the fun │
+├─────────────────────────────────────────────────────────────────────┤
+│ ● opened by alice (you) (03c02af) now │
+│ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
+╰─────────────────────────────────────────────────────────────────────╯
+```
+
+We can change the title and description of the patch itself by using a
+multi-line message (using two `--message` options here):
+
+```
+$ rad patch edit 89f7afb --message "Add Metadata" --message "Add README & LICENSE" --no-announce
+$ rad patch show 89f7afb
+╭─────────────────────────────────────────────────────────────────────╮
+│ Title Add Metadata │
+│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
+│ Author alice (you) │
+│ Head 8945f6189adf027892c85ac57f7e9341049c2537 │
+│ Branches changes │
+│ Commits ahead 2, behind 0 │
+│ Status open │
+│ │
+│ Add README & LICENSE │
+├─────────────────────────────────────────────────────────────────────┤
+│ 8945f61 Define the LICENSE │
+│ 03c02af Add README, just for the fun │
+├─────────────────────────────────────────────────────────────────────┤
+│ ● opened by alice (you) (03c02af) now │
+│ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
+╰─────────────────────────────────────────────────────────────────────╯
+```
+
+We prepare the file `revision-edit.json` which contains one action (thus one line) to be applied.
+
+``` ./revision-edit.jsonl
+{"type": "revision.edit", "description": "Add README and LICENSE", "revision": "89f7afb1511b976482b21f6b2f39aef7f4fb88a2"}
+```
+
+We now use `rad cob update` to edit the patch another time, rewriting the description.
+The action itself is of type `revision.edit` and carries the parameters `revision`,
+specifying the revision for which the description should be changed, and `description`,
+specifying the new description.
+
+```
+$ rad cob update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch --object 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 --message "Edit patch" revision-edit.jsonl
+79b816e92735c49b33d93a31890fdf040b36234c
+$ rad patch show --verbose 89f7afb
+╭─────────────────────────────────────────────────────────────────────╮
+│ Title Add Metadata │
+│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
+│ Author alice (you) │
+│ Head 8945f6189adf027892c85ac57f7e9341049c2537 │
+│ Base f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 │
+│ Branches changes │
+│ Commits ahead 2, behind 0 │
+│ Status open │
+│ │
+│ Add README and LICENSE │
+├─────────────────────────────────────────────────────────────────────┤
+│ 8945f61 Define the LICENSE │
+│ 03c02af Add README, just for the fun │
+├─────────────────────────────────────────────────────────────────────┤
+│ ● opened by alice (you) (03c02af) now │
+│ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
+╰─────────────────────────────────────────────────────────────────────╯
+```
+
+Notice that the patch now has the description `Add README and LICENSE`.
+
+We may use `rad cob update` to create a new revision altogether, as well.
+Let's create yet another commit, an empty one this time, and do that.
+
+```
+$ git commit --allow-empty --message="Dummy commit for a new revision"
+[changes f1339dd] Dummy commit for a new revision
+```
+
+We prepare the file `revision-create.jsonl` which contains one action.
+
+``` ./revision.jsonl
+{"type": "revision", "description": "A new revision", "base": "f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354", "oid": "f1339dd109e538c6b3a7fed3e72403e1b4db08c9"}
+```
+
+Attempting to create the new revision right away would fail:
+
+``` (fail)
+$ rad cob update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch --object 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 --message "Create new revision" revision.jsonl
+✗ Error: store: update error: failed to read 'f1339dd109e538c6b3a7fed3e72403e1b4db08c9' from git odb
+```
+
+Since we are not using the remote helper `git-remote-rad` here, we need to push
+the new commit to storage manually. See `fn patch_open` in `/radicle-remote-helper/src/push.rs`
+for more details.
+
+```
+$ git push rad HEAD:tmp/heads/f1339dd109e538c6b3a7fed3e72403e1b4db08c9
+$ git push rad :tmp/heads/f1339dd109e538c6b3a7fed3e72403e1b4db08c9
+```
+
+Now we can invoke `rad cob update`:
+
+```
+$ rad cob update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch --object 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 --message "Create new revision" revision.jsonl
+2da9c025a1d14d93c4f2cec60a7878afbc5e2a3c
+$ rad patch show 89f7afb
+╭─────────────────────────────────────────────────────────────────────╮
+│ Title Add Metadata │
+│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
+│ Author alice (you) │
+│ Head f1339dd109e538c6b3a7fed3e72403e1b4db08c9 │
+│ Branches changes │
+│ Commits ahead 3, behind 0 │
+│ Status open │
+│ │
+│ Add README and LICENSE │
+├─────────────────────────────────────────────────────────────────────┤
+│ f1339dd Dummy commit for a new revision │
+│ 8945f61 Define the LICENSE │
+│ 03c02af Add README, just for the fun │
+├─────────────────────────────────────────────────────────────────────┤
+│ ● opened by alice (you) (03c02af) now │
+│ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
+│ ↑ updated to 2da9c025a1d14d93c4f2cec60a7878afbc5e2a3c (f1339dd) now │
+╰─────────────────────────────────────────────────────────────────────╯
+```
\ No newline at end of file
diff --git a/radicle-cli/src/commands/cob.rs b/radicle-cli/src/commands/cob.rs
index 4c44d7d13..1e116ae81 100644
--- a/radicle-cli/src/commands/cob.rs
+++ b/radicle-cli/src/commands/cob.rs
@@ -1,21 +1,19 @@
use std::ffi::OsString;
-use std::io;
-use std::io::Write;
+use std::path::PathBuf;
use std::str::FromStr;
+use std::{fs, io};
+
+use anyhow::{anyhow, bail};
-use anyhow::anyhow;
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::cob::store::CobAction;
+use radicle::prelude::*;
use radicle::storage::git;
-use radicle::storage::ReadStorage;
-use radicle::Profile;
-use radicle_cob::object::collaboration::list;
+
use serde_json::json;
use crate::git::Rev;
@@ -30,33 +28,47 @@ pub const HELP: Help = Help {
Usage
rad cob <command> [<option>...]
- rad cob list --repo <rid> --type <typename>
- rad cob log --repo <rid> --type <typename> --object <oid> [<option>...]
- rad cob show --repo <rid> --type <typename> --object <oid> [<option>...]
+
+ 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 migrate [<option>...]
+ rad cob show --repo <rid> --type <typename> --object <oid> [<option>...]
+ rad cob update --repo <rid> --type <typename> --object <oid> <filename>
+ [<option>...]
Commands
- list List all COBs of a given type (--object is not needed)
- log Print a log of all raw operations on a COB
- migrate Migrate the COB database to the latest version
+ 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
+ migrate Migrate the COB database to the latest version
+ update Add actions to a COB
+ show Print the state of COBs
+
+Create, Update options
+
+ --embed-file <name> <path> Supply embed of given name via file at given path
+ --embed-hash <name> <oid> Supply embed of given name via object ID of blob
Log options
- --format (pretty | json) Desired output format (default: pretty)
+ --format (pretty | json) Desired output format (default: pretty)
Show options
- --format json Desired output format (default: json)
+ --format json Desired output format (default: json)
Other options
- --help Print help
+ --help Print help
"#,
};
-#[derive(PartialEq)]
+#[derive(Clone, Copy, PartialEq)]
enum OperationName {
+ Update,
+ Create,
List,
Log,
Migrate,
@@ -64,20 +76,36 @@ enum OperationName {
}
enum Operation {
+ Create {
+ rid: RepoId,
+ type_name: FilteredTypeName,
+ message: String,
+ actions: PathBuf,
+ embeds: Vec<Embed>,
+ },
List {
- repo: RepoId,
- type_name: cob::TypeName,
+ rid: RepoId,
+ type_name: FilteredTypeName,
},
Log {
- repo: RepoId,
- rev: Rev,
- type_name: cob::TypeName,
+ rid: RepoId,
+ type_name: FilteredTypeName,
+ oid: Rev,
+ format: Format,
},
Migrate,
Show {
- repo: RepoId,
- revs: Vec<Rev>,
- type_name: cob::TypeName,
+ rid: RepoId,
+ type_name: FilteredTypeName,
+ oids: Vec<Rev>,
+ },
+ Update {
+ rid: RepoId,
+ type_name: FilteredTypeName,
+ oid: Rev,
+ message: String,
+ actions: PathBuf,
+ embeds: Vec<Embed>,
},
}
@@ -88,132 +116,290 @@ enum Format {
pub struct Options {
op: Operation,
- format: Format,
+}
+
+/// A precursor to [`cob::Embed`] used for parsing
+/// that can be initialized without relying on a [`git::Repository`].
+struct Embed {
+ name: String,
+ content: EmbedContent,
+}
+
+enum EmbedContent {
+ Path(PathBuf),
+ Hash(Rev),
+}
+
+/// A thin wrapper around [`cob::TypeName`] used for parsing.
+/// Well known COB type names are captured as variants,
+/// with [`FilteredTypeName::Other`] as an escape hatch for type names
+/// that are not well known.
+enum FilteredTypeName {
+ Issue,
+ Patch,
+ Identity,
+ Other(cob::TypeName),
+}
+
+impl From<cob::TypeName> for FilteredTypeName {
+ fn from(value: cob::TypeName) -> Self {
+ if value == *cob::issue::TYPENAME {
+ FilteredTypeName::Issue
+ } else if value == *cob::patch::TYPENAME {
+ FilteredTypeName::Patch
+ } else if value == *cob::identity::TYPENAME {
+ FilteredTypeName::Identity
+ } else {
+ FilteredTypeName::Other(value)
+ }
+ }
+}
+
+impl AsRef<cob::TypeName> for FilteredTypeName {
+ fn as_ref(&self) -> &cob::TypeName {
+ match self {
+ FilteredTypeName::Issue => &cob::issue::TYPENAME,
+ FilteredTypeName::Patch => &cob::patch::TYPENAME,
+ FilteredTypeName::Identity => &cob::identity::TYPENAME,
+ FilteredTypeName::Other(value) => value,
+ }
+ }
+}
+
+impl std::fmt::Display for FilteredTypeName {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.as_ref().fmt(f)
+ }
+}
+
+impl Embed {
+ fn try_into_bytes(self, repo: &git::Repository) -> anyhow::Result<cob::Embed<cob::Uri>> {
+ Ok(match self.content {
+ EmbedContent::Hash(hash) => cob::Embed {
+ name: self.name,
+ content: hash.resolve::<git::Oid>(&repo.backend)?.into(),
+ },
+ EmbedContent::Path(path) => {
+ cob::Embed::store(self.name, &std::fs::read(path)?, &repo.backend)?
+ }
+ })
+ }
}
impl Args for Options {
fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
use lexopt::prelude::*;
+ use term::args::string;
+ use OperationName::*;
let mut parser = lexopt::Parser::from_args(args);
- let mut op: Option<OperationName> = None;
- let mut type_name: Option<cob::TypeName> = None;
- let mut revs: Vec<Rev> = vec![];
+
+ let op = match parser.next()? {
+ None | Some(Long("help") | Short('h')) => {
+ return Err(Error::Help.into());
+ }
+ Some(Value(val)) => match val.to_string_lossy().as_ref() {
+ "update" => Update,
+ "create" => Create,
+ "list" => List,
+ "log" => Log,
+ "migrate" => Migrate,
+ "show" => Show,
+ unknown => bail!("unknown operation '{unknown}'"),
+ },
+ Some(arg) => return Err(anyhow!(arg.unexpected())),
+ };
+
+ let mut type_name: Option<FilteredTypeName> = None;
+ let mut oids: Vec<Rev> = vec![];
let mut rid: Option<RepoId> = None;
- let mut format: Option<Format> = None;
+ let mut format: Format = Format::Pretty;
+ let mut message: Option<String> = None;
+ let mut embeds: Vec<Embed> = vec![];
+ let mut actions: Option<PathBuf> = None;
while let Some(arg) = parser.next()? {
- match arg {
- Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
- "list" => op = Some(OperationName::List),
- "log" => op = Some(OperationName::Log),
- "migrate" => op = Some(OperationName::Migrate),
- "show" => op = Some(OperationName::Show),
- unknown => anyhow::bail!("unknown operation '{unknown}'"),
- },
- Long("type") | Short('t') => {
- let v = parser.value()?;
- let v = term::args::string(&v);
- let v = cob::TypeName::from_str(&v)?;
-
- type_name = Some(v);
+ match (&op, &arg) {
+ (_, Long("help") | Short('h')) => {
+ return Err(Error::Help.into());
}
- Long("object") => {
- let v = parser.value()?;
- let v = term::args::string(&v);
-
- revs.push(Rev::from(v));
+ (_, Long("repo") | Short('r')) => {
+ rid = Some(term::args::rid(&parser.value()?)?);
+ }
+ (_, Long("type") | Short('t')) => {
+ let v = string(&parser.value()?);
+ type_name = Some(FilteredTypeName::from(cob::TypeName::from_str(&v)?));
+ }
+ (Update | Log | Show, Long("object") | Short('o')) => {
+ let v = string(&parser.value()?);
+ oids.push(Rev::from(v));
+ }
+ (Update | Create, Long("message") | Short('m')) => {
+ message = Some(string(&parser.value()?));
}
- Long("repo") => {
- let v = parser.value()?;
- let v = term::args::rid(&v)?;
+ (Log | Show | Update, Long("format")) => {
+ format = match (op, string(&parser.value()?).as_ref()) {
+ (Log, "pretty") => Format::Pretty,
+ (Log | Show | Update, "json") => Format::Json,
+ (_, unknown) => bail!("unknown format '{unknown}'"),
+ };
+ }
+ (Update | Create, Long("embed-file")) => {
+ let mut values = parser.values()?;
+
+ let name = values
+ .next()
+ .map(|s| term::args::string(&s))
+ .ok_or(anyhow!("expected name of embed"))?;
- rid = Some(v);
+ let content = EmbedContent::Path(PathBuf::from(
+ values
+ .next()
+ .ok_or(anyhow!("expected path to file to embed"))?,
+ ));
+
+ embeds.push(Embed { name, content });
}
- Long("help") | Short('h') => {
- return Err(Error::Help.into());
+ (Update | Create, Long("embed-hash")) => {
+ let mut values = parser.values()?;
+
+ let name = values
+ .next()
+ .map(|s| term::args::string(&s))
+ .ok_or(anyhow!("expected name of embed"))?;
+
+ let content = EmbedContent::Hash(Rev::from(term::args::string(
+ &values
+ .next()
+ .ok_or(anyhow!("expected hash of file to embed"))?,
+ )));
+
+ embeds.push(Embed { name, content });
}
- Long("format")
- if op == Some(OperationName::Log) || op == Some(OperationName::Show) =>
- {
- let v: String = term::args::string(&parser.value()?);
- match v.as_ref() {
- "pretty" if op == Some(OperationName::Log) => format = Some(Format::Pretty),
- "json" => format = Some(Format::Json),
- unknown => anyhow::bail!("unknown format '{unknown}'"),
- }
+ (Update | Create, Value(val)) => {
+ actions = Some(PathBuf::from(term::args::string(val)));
}
- _ => return Err(anyhow::anyhow!(arg.unexpected())),
+ _ => return Err(anyhow!(arg.unexpected())),
}
}
- let repo = rid.ok_or_else(|| anyhow!("a repository id must be specified with `--repo`"));
+
+ if op == OperationName::Migrate {
+ return Ok((
+ Options {
+ op: Operation::Migrate,
+ },
+ vec![],
+ ));
+ }
+
+ let rid = rid.ok_or_else(|| anyhow!("a repository id must be specified with `--repo`"))?;
let type_name =
- type_name.ok_or_else(|| anyhow!("an object type must be specified with `--type`"));
+ type_name.ok_or_else(|| anyhow!("an object type must be specified with `--type`"))?;
+
+ let missing_oid = || anyhow!("an object id must be specified with `--object`");
+ let missing_message = || anyhow!("a message must be specified with `--message`");
Ok((
Options {
- op: {
- match op.ok_or_else(|| anyhow!("a command must be specified"))? {
- OperationName::List => Operation::List {
- repo: repo?,
- type_name: type_name?,
- },
- OperationName::Log => Operation::Log {
- repo: repo?,
- rev: revs.pop().ok_or_else(|| {
- anyhow!("an object id must be specified with `--object`")
- })?,
- type_name: type_name?,
- },
- OperationName::Migrate => Operation::Migrate,
- OperationName::Show => {
- if revs.is_empty() {
- anyhow::bail!("an object id must be specified with `--object`")
- }
- Operation::Show {
- repo: repo?,
- revs,
- type_name: type_name?,
- }
+ op: match op {
+ Create => Operation::Create {
+ rid,
+ type_name,
+ message: message.ok_or_else(missing_message)?,
+ actions: actions.ok_or_else(|| {
+ anyhow!("a file containing initial actions must be specified")
+ })?,
+ embeds,
+ },
+ List => Operation::List { rid, type_name },
+ Log => Operation::Log {
+ rid,
+ type_name,
+ oid: oids.pop().ok_or_else(missing_oid)?,
+ format,
+ },
+ Migrate => Operation::Migrate,
+ Show => {
+ if oids.is_empty() {
+ return Err(missing_oid());
+ }
+ Operation::Show {
+ rid,
+ oids,
+ type_name,
}
}
+ Update => Operation::Update {
+ rid,
+ type_name,
+ oid: oids.pop().ok_or_else(missing_oid)?,
+ message: message.ok_or_else(missing_message)?,
+ actions: actions.ok_or_else(|| {
+ anyhow!("a file containing actions must be specified")
+ })?,
+ embeds,
+ },
},
- format: format.unwrap_or(Format::Pretty),
},
vec![],
))
}
}
-pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
+pub fn run(Options { op }: Options, ctx: impl term::Context) -> anyhow::Result<()> {
+ use cob::store::Store;
+ use FilteredTypeName::*;
+ use Operation::*;
+
let profile = ctx.profile()?;
let storage = &profile.storage;
- match options.op {
- Operation::List { repo, type_name } => {
- let repo = storage.repository(repo)?;
- let cobs = list::<NonEmpty<cob::Entry>, _>(&repo, &type_name)?;
- for cob in cobs {
- println!("{}", cob.id);
- }
- }
- Operation::Log {
- repo,
- rev: oid,
+ match op {
+ Create {
+ rid,
type_name,
+ message,
+ embeds,
+ actions,
} => {
- let repo = storage.repository(repo)?;
- let oid = oid.resolve(&repo.backend)?;
- let ops = cob::store::ops(&oid, &type_name, &repo)?;
-
- for op in ops.into_iter().rev() {
- match options.format {
- Format::Json => print_op_json(op)?,
- Format::Pretty => print_op_pretty(op)?,
+ let signer = &profile.signer()?;
+ let repo = storage.repository_mut(rid)?;
+
+ let reader = io::BufReader::new(fs::File::open(actions)?);
+
+ let embeds = embeds
+ .into_iter()
+ .map(|embed| embed.try_into_bytes(&repo))
+ .collect::<anyhow::Result<Vec<_>>>()?;
+
+ let oid = match type_name {
+ Patch => {
+ let store: Store<cob::patch::Patch, _> = Store::open(&repo)?;
+ let actions = read_jsonl_actions(reader)?;
+ let (oid, _) = store.create(&message, actions, embeds, signer)?;
+ oid
}
- }
+ Issue => {
+ let store: Store<cob::issue::Issue, _> = Store::open(&repo)?;
+ let actions = read_jsonl_actions(reader)?;
+ let (oid, _) = store.create(&message, actions, embeds, signer)?;
+ oid
+ }
+ Identity => anyhow::bail!(
+ "Creation of collaborative objects of type {} is not supported.",
+ &type_name
+ ),
+ Other(type_name) => {
+ let store: Store<cob::external::External, _> =
+ Store::open_for(&type_name, &repo)?;
+ let actions = read_jsonl_actions(reader)?;
+ let (oid, _) = store.create(&message, actions, embeds, signer)?;
+ oid
+ }
+ };
+ println!("{}", oid);
}
- Operation::Migrate => {
+ Migrate => {
let mut db = profile.cobs_db_mut()?;
if db.check_version().is_ok() {
term::success!("Collaborative objects database is already up to date");
@@ -224,71 +410,175 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
);
}
}
- Operation::Show {
- repo,
- revs,
+ List { rid, type_name } => {
+ let repo = storage.repository(rid)?;
+ let cobs = radicle_cob::list::<NonEmpty<cob::Entry>, _>(&repo, type_name.as_ref())?;
+ for cob in cobs {
+ println!("{}", cob.id);
+ }
+ }
+ Log {
+ rid,
type_name,
+ oid,
+ format,
} => {
- let repo = storage.repository(repo)?;
+ let repo = storage.repository(rid)?;
+ let oid = oid.resolve(&repo.backend)?;
+ let ops = cob::store::ops(&oid, type_name.as_ref(), &repo)?;
- if let Err(e) = show(revs, &repo, type_name, &profile) {
- if let Some(err) = e.downcast_ref::<io::Error>() {
- if err.kind() == io::ErrorKind::BrokenPipe {
+ for op in ops.into_iter().rev() {
+ match format {
+ Format::Json => print_op_json(op)?,
+ Format::Pretty => print_op_pretty(op)?,
+ }
+ }
+ }
+ Show {
+ rid,
+ oids,
+ type_name,
+ } => {
+ let repo = storage.repository(rid)?;
+ if let Err(e) = show(oids, &repo, type_name, &profile) {
+ if let Some(err) = e.downcast_ref::<std::io::Error>() {
+ if err.kind() == std::io::ErrorKind::BrokenPipe {
return Ok(());
}
}
return Err(e);
}
}
- }
+ Update {
+ rid,
+ type_name,
+ oid,
+ message,
+ actions,
+ embeds,
+ } => {
+ let signer = &profile.signer()?;
+ let repo = storage.repository_mut(rid)?;
+ let reader = io::BufReader::new(fs::File::open(actions)?);
+ let oid = &oid.resolve(&repo.backend)?;
+ let embeds = embeds
+ .into_iter()
+ .map(|embed| embed.try_into_bytes(&repo))
+ .collect::<anyhow::Result<Vec<_>>>()?;
+
+ let oid = match type_name {
+ Patch => {
+ let actions: Vec<cob::patch::Action> = read_jsonl(reader)?;
+ let mut patches = profile.patches_mut(&repo)?;
+ let mut patch = patches.get_mut(oid)?;
+ patch.transaction(&message, &profile.signer()?, |tx| {
+ tx.extend(actions)?;
+ tx.embed(embeds)?;
+ Ok(())
+ })?
+ }
+ Issue => {
+ let actions: Vec<cob::issue::Action> = read_jsonl(reader)?;
+ let mut issues = profile.issues_mut(&repo)?;
+ let mut issue = issues.get_mut(oid)?;
+ issue.transaction(&message, &profile.signer()?, |tx| {
+ tx.extend(actions)?;
+ tx.embed(embeds)?;
+ Ok(())
+ })?
+ }
+ Identity => anyhow::bail!(
+ "Update of collaborative objects of type {} is not supported.",
+ &type_name
+ ),
+ Other(type_name) => {
+ use cob::external::{Action, External};
+ let actions: Vec<Action> = read_jsonl(reader)?;
+ let mut store: Store<External, _> = Store::open_for(&type_name, &repo)?;
+ let tx = cob::store::Transaction::new(type_name.clone(), actions, embeds);
+ let (_, oid) = tx.commit(&message, *oid, &mut store, signer)?;
+ oid
+ }
+ };
+ println!("{}", oid);
+ }
+ }
Ok(())
}
fn show(
- revs: Vec<Rev>,
+ oids: Vec<Rev>,
repo: &git::Repository,
- type_name: cob::TypeName,
+ type_name: FilteredTypeName,
profile: &Profile,
) -> Result<(), anyhow::Error> {
+ use io::Write as _;
let mut stdout = std::io::stdout();
- if type_name == cob::patch::TYPENAME.clone() {
- let patches = term::cob::patches(profile, repo)?;
- for oid in revs {
- let oid = &oid.resolve(&repo.backend)?;
- let Some(patch) = patches.get(oid)? else {
- anyhow::bail!(cob::store::Error::NotFound(type_name, *oid));
- };
- serde_json::to_writer(&stdout, &patch)?;
- stdout.write_all(b"\n")?;
+ match type_name {
+ FilteredTypeName::Identity => {
+ use cob::identity;
+ for oid in oids {
+ let oid = &oid.resolve(&repo.backend)?;
+ let Some(cob) = cob::get::<identity::Identity, _>(repo, type_name.as_ref(), oid)?
+ else {
+ bail!(cob::store::Error::NotFound(
+ type_name.as_ref().clone(),
+ *oid
+ ));
+ };
+ serde_json::to_writer(&stdout, &cob.object)?;
+ stdout.write_all(b"\n")?;
+ }
}
- } else if type_name == cob::issue::TYPENAME.clone() {
- let issues = term::cob::issues(profile, repo)?;
- for oid in revs {
- let oid = &oid.resolve(&repo.backend)?;
- let Some(issue) = issues.get(oid)? else {
- anyhow::bail!(cob::store::Error::NotFound(type_name, *oid))
- };
- serde_json::to_writer(&stdout, &issue)?;
- stdout.write_all(b"\n")?;
+ FilteredTypeName::Issue => {
+ use radicle::issue::cache::Issues as _;
+ let issues = term::cob::issues(profile, repo)?;
+ for oid in oids {
+ let oid = &oid.resolve(&repo.backend)?;
+ let Some(issue) = issues.get(oid)? else {
+ bail!(cob::store::Error::NotFound(
+ type_name.as_ref().clone(),
+ *oid
+ ))
+ };
+ serde_json::to_writer(&stdout, &issue)?;
+ stdout.write_all(b"\n")?;
+ }
}
- } else if type_name == cob::identity::TYPENAME.clone() {
- for oid in revs {
- let oid = &oid.resolve(&repo.backend)?;
- let Some(cob) = cob::get::<Identity, _>(repo, &type_name, oid)? else {
- anyhow::bail!(cob::store::Error::NotFound(type_name, *oid));
- };
- serde_json::to_writer(&stdout, &cob.object)?;
- stdout.write_all(b"\n")?;
+ FilteredTypeName::Patch => {
+ use radicle::patch::cache::Patches as _;
+ let patches = term::cob::patches(profile, repo)?;
+ for oid in oids {
+ let oid = &oid.resolve(&repo.backend)?;
+ let Some(patch) = patches.get(oid)? else {
+ bail!(cob::store::Error::NotFound(
+ type_name.as_ref().clone(),
+ *oid
+ ));
+ };
+ serde_json::to_writer(&stdout, &patch)?;
+ stdout.write_all(b"\n")?;
+ }
+ }
+ FilteredTypeName::Other(type_name) => {
+ let store =
+ cob::store::Store::<cob::external::External, _>::open_for(&type_name, repo)?;
+ for oid in oids {
+ let oid = &oid.resolve(&repo.backend)?;
+ let cob = store
+ .get(oid)?
+ .ok_or_else(|| anyhow!(cob::store::Error::NotFound(type_name.clone(), *oid)))?;
+ serde_json::to_writer(&stdout, &cob)?;
+ stdout.write_all(b"\n")?;
+ }
}
- } else {
- anyhow::bail!("the type name '{type_name}' is unknown");
}
Ok(())
}
-fn print_op_pretty(op: Op<Vec<u8>>) -> anyhow::Result<()> {
+fn print_op_pretty(op: cob::Op<Vec<u8>>) -> anyhow::Result<()> {
let time = DateTime::<Utc>::from(
std::time::UNIX_EPOCH + std::time::Duration::from_secs(op.timestamp.as_secs()),
)
@@ -317,18 +607,46 @@ fn print_op_pretty(op: Op<Vec<u8>>) -> anyhow::Result<()> {
Ok(())
}
-fn print_op_json(op: Op<Vec<u8>>) -> anyhow::Result<()> {
+fn print_op_json(op: cob::Op<Vec<u8>>) -> anyhow::Result<()> {
let mut ser = json!(op);
- ser.as_object_mut().unwrap().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>, _>>()?),
- );
+ 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);
Ok(())
}
+
+/// Naive implementation for reading JSONL streams,
+/// see <https://jsonlines.org/>.
+fn read_jsonl<R, T>(reader: io::BufReader<R>) -> anyhow::Result<Vec<T>>
+where
+ R: io::Read,
+ T: serde::de::DeserializeOwned,
+{
+ use io::BufRead as _;
+ let mut result: Vec<T> = Vec::new();
+ for line in reader.lines() {
+ result.push(serde_json::from_str(&line?)?);
+ }
+ Ok(result)
+}
+
+/// Tiny utility to read a [`NonEmpty`] of COB actions.
+/// This is used for `rad cob create` and `rad cob update`.
+fn read_jsonl_actions<R, A>(reader: io::BufReader<R>) -> anyhow::Result<NonEmpty<A>>
+where
+ R: io::Read,
+ A: CobAction + serde::de::DeserializeOwned,
+{
+ NonEmpty::from_vec(read_jsonl(reader)?)
+ .ok_or_else(|| anyhow!("at least one action is required"))
+}
diff --git a/radicle-cli/tests/commands.rs b/radicle-cli/tests/commands.rs
index 61875ba78..cfaadc498 100644
--- a/radicle-cli/tests/commands.rs
+++ b/radicle-cli/tests/commands.rs
@@ -158,6 +158,92 @@ fn rad_issue() {
test("examples/rad-issue.md", &working, Some(home), []).unwrap();
}
+#[test]
+fn rad_cob_update() {
+ let mut environment = Environment::new();
+ let profile = environment.profile(config::profile("alice"));
+ let working = environment.tmp().join("working");
+ let home = &profile.home;
+
+ let base = Path::new(env!("CARGO_MANIFEST_DIR"));
+
+ std::fs::create_dir_all(base).unwrap();
+ std::fs::create_dir_all(working.clone()).unwrap();
+
+ // Setup a test repository.
+ fixtures::repository(&working);
+
+ test("examples/rad-init.md", &working, Some(home), []).unwrap();
+ test("examples/rad-cob-update.md", &working, Some(home), []).unwrap();
+}
+
+#[test]
+fn rad_cob_update_identity() {
+ let mut environment = Environment::new();
+ let profile = environment.profile(config::profile("alice"));
+ let working = environment.tmp().join("working");
+ let home = &profile.home;
+
+ let base = Path::new(env!("CARGO_MANIFEST_DIR"));
+
+ std::fs::create_dir_all(base).unwrap();
+ std::fs::create_dir_all(working.clone()).unwrap();
+
+ // Setup a test repository.
+ fixtures::repository(&working);
+
+ test("examples/rad-init.md", &working, Some(home), []).unwrap();
+ test(
+ "examples/rad-cob-update-identity.md",
+ &working,
+ Some(home),
+ [],
+ )
+ .unwrap();
+}
+
+#[test]
+fn rad_cob_multiset() {
+ {
+ // `rad-cob-multiset` is a `jq` script, which requires `jq` to be installed.
+ // We test whether `jq` is installed, and have this test succeed if it is not.
+ // Programmatic skipping of tests is not supported as of 2024-08.
+
+ let output = std::process::Command::new("/usr/bin/env")
+ .arg("jq")
+ .arg("-V")
+ .output()
+ .unwrap();
+
+ if !output.status.success() {
+ log::warn!(target: "test", "`jq` not found. Succeeding prematurely. {:?}", output);
+ return;
+ }
+ }
+
+ let mut environment = Environment::new();
+ let profile = environment.profile(config::profile("alice"));
+ let home = &profile.home;
+ let working = environment.tmp().join("working");
+
+ let base = Path::new(env!("CARGO_MANIFEST_DIR"));
+ std::fs::create_dir_all(base).unwrap();
+ std::fs::create_dir_all(working.clone()).unwrap();
+
+ // Copy over the script that implements the multiset COB.
+ std::fs::copy(
+ base.join("examples").join("rad-cob-multiset"),
+ working.join("rad-cob-multiset"),
+ )
+ .unwrap();
+
+ // Setup a test repository.
+ fixtures::repository(&working);
+
+ test("examples/rad-init.md", &working, Some(home), []).unwrap();
+ test("examples/rad-cob-multiset.md", &working, Some(home), []).unwrap();
+}
+
#[test]
fn rad_cob_log() {
let mut environment = Environment::new();
diff --git a/radicle-node/src/worker/fetch.rs b/radicle-node/src/worker/fetch.rs
index 7e1f615e0..bf9bb3bc7 100644
--- a/radicle-node/src/worker/fetch.rs
+++ b/radicle-node/src/worker/fetch.rs
@@ -342,7 +342,7 @@ fn update_or_remove<R, C, T>(
) -> Result<(), error::Cache>
where
R: cob::Store + ReadRepository,
- T: cob::Evaluate<R> + cob::store::Cob,
+ T: cob::Evaluate<R> + cob::store::Cob + cob::store::CobWithType,
C: cob::cache::Update<T> + cob::cache::Remove<T>,
{
match store.get(&tid.id) {
diff --git a/radicle/src/cob.rs b/radicle/src/cob.rs
index c8ec0a745..4c79ec3be 100644
--- a/radicle/src/cob.rs
+++ b/radicle/src/cob.rs
@@ -1,6 +1,7 @@
#![warn(clippy::unwrap_used)]
pub mod cache;
pub mod common;
+pub mod external;
pub mod identity;
pub mod issue;
pub mod job;
diff --git a/radicle/src/cob/external.rs b/radicle/src/cob/external.rs
new file mode 100644
index 000000000..69bd49ec1
--- /dev/null
+++ b/radicle/src/cob/external.rs
@@ -0,0 +1,240 @@
+//! # External Collaborative Objects
+//!
+//! This module provides an interface for external helper programs to provide
+//! the evaluation logic of Collaborative Objects (COBs).
+//!
+//! An external COB is one that relies on an external program (so called
+//! "helper", is an executable file, for example a script, or a binary), that
+//! implements the evaluation logic for that particular COB:
+//! Whenever an operation is to be applied to an external COB, the helper is
+//! invoked with the current state of the COB and the operation to be applied.
+//! It then returns the new state of the COB, according to its internal logic.
+//! This concept is borrowed from Git, which supports [remote helpers] and
+//! [credential helpers].
+//!
+//! External COBs must be based on JSON, that is, the COB itself and associated
+//! actions must serialize to and deserialize from JSON.
+//! Further, the helper must be able to communicate to Radicle using
+//! [JSON Lines] (see further details below).
+//!
+//! # Invocation
+//!
+//! The helper is invoked by Radicle without command line arguments.
+//! In the future, more arguments might be added.
+//!
+//! # Helper Protocol
+//!
+//! Radicle and the helper communicate back and forth [JSON Lines] via standard
+//! streams.
+//!
+//! 1. The helper must read and process at least one JSON Line (containing one
+//! operation) from standard input, which represents the operation to be
+//! applied to the COB, along with possible concurrent operations.
+//! 2. The helper must write the new state of COB to standard output in a JSON
+//! Line.
+//! 3. The helper may read additional JSON Lines from standard input, these are
+//! to be applied "on top of" the previous operations.
+//! 4. The helper must reply with the state of the COB in a JSON Line after
+//! processing each operation.
+//! 5. The helper must exit with a status code of zero on success, and a
+//! non-zero status code on failure.
+//! 6. The helper may write to standard error for logging and debugging
+//! purposes.
+//!
+//! # Syntax of Operations
+//!
+//! The operations sent from Radicle to the helper are of the following shape:
+//!
+//! ```json
+//! {
+//! "title": "Operation as sent to helper"
+//! "type": "object",
+//! "properties": {
+//! "concurrent": {
+//! "type": "array",
+//! "items": {
+//! "$ref": "#/definitions/radicle::cob::Op"
+//! }
+//! },
+//! "value": {
+//! "type": "object",
+//! "properties": {
+//! "prop": {
+//! "$ref": "#/definitions/radicle::cob::external::External"
+//! }
+//! }
+//! },
+//! "op": {
+//! "type": "object",
+//! "properties": {
+//! "prop": {
+//! "$ref": "#/definitions/radicle::cob::Op"
+//! }
+//! }
+//! }
+//! },
+//! }
+//! ```
+//!
+//! [JSON Lines]: https://jsonlines.org/
+//! [credential helpers]: https://git-scm.com/doc/credential-helpers
+//! [remote helpers]: https://git-scm.com/docs/gitremote-helpers
+
+use std::collections::HashMap;
+use std::io::{Error as IoError, ErrorKind};
+use std::process::{Command, Stdio};
+
+use serde::{Deserialize, Serialize};
+use thiserror::Error;
+
+use serde_json::{from_slice, to_writer, Error as JsonError, Map, Value};
+
+use crate::cob::object::collaboration::Evaluate;
+use crate::cob::op::{Op as CobOp, OpEncodingError};
+use crate::cob::store::{Cob, CobAction};
+use crate::git::Oid;
+use crate::storage::ReadRepository;
+
+/// This prefix is used to generate the name of the command,
+/// which is executed by the helper to apply operations.
+static COB_EXTERNAL_COMMAND_PREFIX: &str = "rad-cob-";
+
+#[derive(PartialEq, Debug, Serialize, Deserialize)]
+pub struct External(Value);
+
+impl Default for External {
+ fn default() -> Self {
+ Self(Value::Object(Map::default()))
+ }
+}
+
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("op decoding failed: {0}")]
+ Op(#[from] OpEncodingError),
+ #[error("serde_json: {0}")]
+ Serde(#[from] JsonError),
+ #[error("io: {0}")]
+ Io(#[from] IoError),
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct Action {
+ #[serde(default)]
+ parents: Vec<Oid>,
+
+ #[serde(flatten)]
+ map: HashMap<String, Value>,
+}
+
+impl CobAction for Action {
+ fn parents(&self) -> Vec<Oid> {
+ self.parents.clone()
+ }
+}
+
+impl From<Action> for nonempty::NonEmpty<Action> {
+ fn from(action: Action) -> Self {
+ Self::new(action)
+ }
+}
+
+pub type Op = CobOp<Action>;
+
+impl External {
+ fn handle<R: ReadRepository>(
+ &mut self,
+ op: Op,
+ concurrent: Vec<Op>,
+ _repo: &R,
+ ) -> Result<(), Error> {
+ let command_name = {
+ let prefix = String::from(COB_EXTERNAL_COMMAND_PREFIX);
+ let type_name = op.manifest.type_name.to_string();
+ let suffix = type_name
+ .rsplit_once('.')
+ .map(|(_, suffix)| suffix)
+ .unwrap_or(type_name.as_str());
+ prefix + suffix
+ };
+
+ let child = Command::new(command_name)
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()?;
+
+ let Some(stdin) = &child.stdin else {
+ return Err(Error::Io(IoError::new(
+ ErrorKind::BrokenPipe,
+ "stdin not available",
+ )));
+ };
+
+ #[derive(Serialize)]
+ struct OpMessage {
+ value: Value,
+ op: Op,
+ concurrent: Vec<Op>,
+ }
+
+ to_writer(
+ stdin,
+ &OpMessage {
+ value: self.0.clone(),
+ op,
+ concurrent,
+ },
+ )?;
+
+ self.0 = from_slice(&child.wait_with_output()?.stdout)?;
+ Ok(())
+ }
+}
+
+impl Cob for External {
+ type Action = Action;
+ type Error = Error;
+
+ fn from_root<R: ReadRepository>(
+ op: super::Op<Self::Action>,
+ repo: &R,
+ ) -> Result<Self, Self::Error> {
+ let mut root = Self::default();
+ root.handle(op, vec![], repo)?;
+ Ok(root)
+ }
+
+ fn op<'a, R: ReadRepository, I: IntoIterator<Item = &'a super::Entry>>(
+ &mut self,
+ op: super::Op<Self::Action>,
+ concurrent: I,
+ repo: &R,
+ ) -> Result<(), <Self as Cob>::Error> {
+ let concurrent: Vec<Op> = concurrent
+ .into_iter()
+ .map(Op::try_from)
+ .collect::<Result<Vec<Op>, _>>()?;
+ self.handle(op, concurrent, repo)
+ }
+}
+
+impl<R: ReadRepository> Evaluate<R> for External {
+ type Error = Error;
+
+ fn init(entry: &radicle_cob::Entry, store: &R) -> Result<Self, Self::Error> {
+ Self::from_root(Op::try_from(entry)?, store)
+ }
+
+ fn apply<'a, I: Iterator<Item = (&'a radicle_git_ext::Oid, &'a radicle_cob::Entry)>>(
+ &mut self,
+ entry: &radicle_cob::Entry,
+ concurrent: I,
+ repo: &R,
+ ) -> Result<(), Self::Error> {
+ let concurrent: Vec<Op> = concurrent
+ .map(|(_, e)| e)
+ .map(Op::try_from)
+ .collect::<Result<Vec<Op>, _>>()?;
+ self.handle(Op::try_from(entry)?, concurrent, repo)
+ }
+}
diff --git a/radicle/src/cob/identity.rs b/radicle/src/cob/identity.rs
index bfa8aed85..e1162dfcf 100644
--- a/radicle/src/cob/identity.rs
+++ b/radicle/src/cob/identity.rs
@@ -155,6 +155,12 @@ pub struct Identity {
timeline: Vec<EntryId>,
}
+impl cob::store::CobWithType for Identity {
+ fn type_name() -> &'static TypeName {
+ &TYPENAME
+ }
+}
+
impl std::ops::Deref for Identity {
type Target = Revision;
@@ -206,6 +212,8 @@ impl Identity {
object: &ObjectId,
repo: &R,
) -> Result<Identity, store::Error> {
+ use cob::store::CobWithType;
+
cob::get::<Self, _>(repo, Self::type_name(), object)
.map(|r| r.map(|cob| cob.object))?
.ok_or_else(move || store::Error::NotFound(TYPENAME.clone(), *object))
@@ -293,10 +301,6 @@ impl store::Cob for Identity {
type Action = Action;
type Error = ApplyError;
- fn type_name() -> &'static TypeName {
- &TYPENAME
- }
-
fn from_root<R: ReadRepository>(op: Op, repo: &R) -> Result<Self, Self::Error> {
let mut actions = op.actions.into_iter();
let Some(Action::Revision {
diff --git a/radicle/src/cob/issue.rs b/radicle/src/cob/issue.rs
index 8c826ed0c..825c12f2f 100644
--- a/radicle/src/cob/issue.rs
+++ b/radicle/src/cob/issue.rs
@@ -142,13 +142,15 @@ pub struct Issue {
pub(super) thread: Thread,
}
-impl store::Cob for Issue {
- type Action = Action;
- type Error = Error;
-
+impl cob::store::CobWithType for Issue {
fn type_name() -> &'static TypeName {
&TYPENAME
}
+}
+
+impl store::Cob for Issue {
+ type Action = Action;
+ type Error = Error;
fn from_root<R: ReadRepository>(op: Op, repo: &R) -> Result<Self, Self::Error> {
let doc = op.identity_doc(repo)?.ok_or(Error::MissingIdentity)?;
@@ -941,7 +943,7 @@ mod test {
use pretty_assertions::assert_eq;
use super::*;
- use crate::cob::{ActorId, Reaction};
+ use crate::cob::{store::CobWithType, ActorId, Reaction};
use crate::git::Oid;
use crate::issue::cache::Issues as _;
use crate::test;
diff --git a/radicle/src/cob/job.rs b/radicle/src/cob/job.rs
index 961ff4875..84d849a7a 100644
--- a/radicle/src/cob/job.rs
+++ b/radicle/src/cob/job.rs
@@ -24,6 +24,8 @@ use crate::git;
use crate::prelude::ReadRepository;
use crate::storage::{Oid, WriteRepository};
+use super::store::CobWithType;
+
/// The name of this COB type. Note that this is a "beta" COB, which
/// means it's not meant for others to rely on yet, and we may change
/// it without warning.
@@ -213,10 +215,6 @@ impl Cob for Job {
type Action = Action;
type Error = Error;
- fn type_name() -> &'static TypeName {
- &TYPENAME
- }
-
fn from_root<R: ReadRepository>(op: Op, _repo: &R) -> Result<Self, Self::Error> {
let mut actions = op.actions.into_iter();
let Some(Action::Trigger { commit }) = actions.next() else {
@@ -245,6 +243,12 @@ impl Cob for Job {
}
}
+impl CobWithType for Job {
+ fn type_name() -> &'static TypeName {
+ &TYPENAME
+ }
+}
+
impl<R: ReadRepository> cob::Evaluate<R> for Job {
type Error = Error;
diff --git a/radicle/src/cob/patch.rs b/radicle/src/cob/patch.rs
index 831100ab7..e700a490e 100644
--- a/radicle/src/cob/patch.rs
+++ b/radicle/src/cob/patch.rs
@@ -91,7 +91,7 @@ pub enum Error {
/// This error indicates that the operations are not being applied
/// in causal order, which is a requirement for this CRDT.
///
- /// For example, this can occur if an operation references anothern operation
+ /// For example, this can occur if an operation references another operation
/// that hasn't happened yet.
#[error("causal dependency {0:?} missing")]
Missing(EntryId),
@@ -1156,13 +1156,15 @@ impl Patch {
}
}
-impl store::Cob for Patch {
- type Action = Action;
- type Error = Error;
-
+impl cob::store::CobWithType for Patch {
fn type_name() -> &'static TypeName {
&TYPENAME
}
+}
+
+impl store::Cob for Patch {
+ type Action = Action;
+ type Error = Error;
fn from_root<R: ReadRepository>(op: Op, repo: &R) -> Result<Self, Self::Error> {
let doc = op.identity_doc(repo)?.ok_or(Error::MissingIdentity)?;
diff --git a/radicle/src/cob/store.rs b/radicle/src/cob/store.rs
index a66b27e27..0511f0c72 100644
--- a/radicle/src/cob/store.rs
+++ b/radicle/src/cob/store.rs
@@ -31,9 +31,6 @@ pub trait Cob: Sized + PartialEq + Debug {
/// Error returned by `apply` function.
type Error: std::error::Error + Send + Sync + 'static;
- /// The object type name.
- fn type_name() -> &'static TypeName;
-
/// Initialize a collarorative object from a root operation.
fn from_root<R: ReadRepository>(op: Op<Self::Action>, repo: &R) -> Result<Self, Self::Error>;
@@ -50,7 +47,10 @@ pub trait Cob: Sized + PartialEq + Debug {
fn from_history<R: ReadRepository>(
history: &crate::cob::History,
repo: &R,
- ) -> Result<Self, test::HistoryError<Self>> {
+ ) -> Result<Self, test::HistoryError<Self>>
+ where
+ Self: CobWithType,
+ {
test::from_history::<R, Self>(history, repo)
}
@@ -73,6 +73,15 @@ pub trait Cob: Sized + PartialEq + Debug {
}
}
+/// Implementations are statically associated with a particular
+/// type name of a collaborative object.
+///
+/// In most cases, this trait should be used in tandem with [`Cob`].
+pub trait CobWithType {
+ /// The type name of the collaborative object type which backs this implementation.
+ fn type_name() -> &'static TypeName;
+}
+
/// Store error.
#[derive(Debug, thiserror::Error)]
pub enum Error {
@@ -109,6 +118,7 @@ pub struct Store<'a, T, R> {
identity: Option<git::Oid>,
repo: &'a R,
witness: PhantomData<T>,
+ type_name: &'a TypeName,
}
impl<T, R> AsRef<R> for Store<'_, T, R> {
@@ -122,11 +132,12 @@ where
R: ReadRepository + cob::Store,
{
/// Open a new generic store.
- pub fn open(repo: &'a R) -> Result<Self, Error> {
+ pub fn open_for(type_name: &'a TypeName, repo: &'a R) -> Result<Self, Error> {
Ok(Self {
repo,
identity: None,
witness: PhantomData,
+ type_name,
})
}
@@ -136,10 +147,41 @@ where
repo: self.repo,
witness: self.witness,
identity: Some(identity),
+ type_name: self.type_name,
}
}
}
+impl<'a, T, R> Store<'a, T, R>
+where
+ R: ReadRepository + cob::Store,
+ T: CobWithType,
+{
+ /// Open a new generic store.
+ pub fn open(repo: &'a R) -> Result<Self, Error> {
+ Ok(Self {
+ repo,
+ identity: None,
+ witness: PhantomData,
+ type_name: T::type_name(),
+ })
+ }
+}
+
+impl<T, R> Store<'_, T, R>
+where
+ R: ReadRepository + cob::Store,
+ T: Cob + cob::Evaluate<R>,
+{
+ pub fn transaction(
+ &self,
+ actions: Vec<T::Action>,
+ embeds: Vec<Embed<Uri>>,
+ ) -> Transaction<T, R> {
+ Transaction::new(self.type_name.clone(), actions, embeds)
+ }
+}
+
impl<T, R> Store<'_, T, R>
where
R: ReadRepository + SignRepository + cob::Store,
@@ -149,6 +191,7 @@ where
/// Update an object.
pub fn update<G: Signer>(
&self,
+ type_name: &TypeName,
object_id: ObjectId,
message: &str,
actions: impl Into<NonEmpty<T::Action>>,
@@ -175,7 +218,7 @@ where
signer.public_key(),
Update {
object_id,
- type_name: T::type_name().clone(),
+ type_name: type_name.clone(),
message: message.to_owned(),
embeds,
changes,
@@ -215,7 +258,7 @@ where
parents,
signer.public_key(),
Create {
- type_name: T::type_name().clone(),
+ type_name: self.type_name.clone(),
version: Version::default(),
message: message.to_owned(),
embeds,
@@ -225,7 +268,7 @@ where
// Nb. We can't sign our refs before the identity refs exist, which are created after
// the identity COB is created. Therefore we manually sign refs when creating identity
// COBs.
- if T::type_name() != &*crate::cob::identity::TYPENAME {
+ if self.type_name != &*crate::cob::identity::TYPENAME {
self.repo
.sign_refs(signer)
.map_err(|e| Error::SignRefs(Box::new(e)))?;
@@ -235,13 +278,13 @@ where
/// Remove an object.
pub fn remove<G: Signer>(&self, id: &ObjectId, signer: &G) -> Result<(), Error> {
- let name = git::refs::storage::cob(signer.public_key(), T::type_name(), id);
+ let name = git::refs::storage::cob(signer.public_key(), self.type_name, id);
match self
.repo
.reference_oid(signer.public_key(), &name.strip_namespace())
{
Ok(_) => {
- cob::remove(self.repo, signer.public_key(), T::type_name(), id)?;
+ cob::remove(self.repo, signer.public_key(), self.type_name, id)?;
self.repo
.sign_refs(signer)
.map_err(|e| Error::SignRefs(Box::new(e)))?;
@@ -259,12 +302,12 @@ where
impl<'a, T, R> Store<'a, T, R>
where
R: ReadRepository + cob::Store,
- T: cob::Evaluate<R> + Cob,
+ T: Cob + cob::Evaluate<R>,
T::Action: Serialize,
{
/// Get an object.
pub fn get(&self, id: &ObjectId) -> Result<Option<T>, Error> {
- cob::get::<T, _>(self.repo, T::type_name(), id)
+ cob::get::<T, _>(self.repo, self.type_name, id)
.map(|r| r.map(|cob| cob.object))
.map_err(Error::from)
}
@@ -273,7 +316,7 @@ where
pub fn all(
&self,
) -> Result<impl ExactSizeIterator<Item = Result<(ObjectId, T), Error>> + 'a, Error> {
- let raw = cob::list::<T, _>(self.repo, T::type_name())?;
+ let raw = cob::list::<T, _>(self.repo, self.type_name)?;
Ok(raw.into_iter().map(|o| Ok((*o.id(), o.object))))
}
@@ -285,7 +328,7 @@ where
/// Return objects count.
pub fn count(&self) -> Result<usize, Error> {
- let raw = cob::list::<T, _>(self.repo, T::type_name())?;
+ let raw = cob::list::<T, _>(self.repo, self.type_name)?;
Ok(raw.len())
}
@@ -296,15 +339,18 @@ where
pub struct Transaction<T: Cob + cob::Evaluate<R>, R> {
actions: Vec<T::Action>,
embeds: Vec<Embed<Uri>>,
+
repo: PhantomData<R>,
+ type_name: TypeName,
}
-impl<T: Cob + cob::Evaluate<R>, R> Default for Transaction<T, R> {
+impl<T: Cob + CobWithType + cob::Evaluate<R>, R> Default for Transaction<T, R> {
fn default() -> Self {
Self {
actions: Vec::new(),
embeds: Vec::new(),
repo: PhantomData,
+ type_name: T::type_name().clone(),
}
}
}
@@ -312,6 +358,20 @@ impl<T: Cob + cob::Evaluate<R>, R> Default for Transaction<T, R> {
impl<T, R> Transaction<T, R>
where
T: Cob + cob::Evaluate<R>,
+{
+ pub fn new(type_name: TypeName, actions: Vec<T::Action>, embeds: Vec<Embed<Uri>>) -> Self {
+ Self {
+ actions,
+ embeds,
+ repo: PhantomData,
+ type_name,
+ }
+ }
+}
+
+impl<T, R> Transaction<T, R>
+where
+ T: Cob + CobWithType + cob::Evaluate<R>,
{
/// Create a new transaction to be used as the initial set of operations for a COB.
pub fn initial<G, F, Tx>(
@@ -337,14 +397,26 @@ where
store.create(message, actions, tx.embeds, signer)
}
+}
- /// Add an operation to this transaction.
+impl<T, R> Transaction<T, R>
+where
+ T: Cob + cob::Evaluate<R>,
+{
+ /// Add an action to this transaction.
pub fn push(&mut self, action: T::Action) -> Result<(), Error> {
self.actions.push(action);
Ok(())
}
+ /// Add actions to this transaction.
+ pub fn extend(&mut self, actions: impl IntoIterator<Item = T::Action>) -> Result<(), Error> {
+ self.actions.extend(actions);
+
+ Ok(())
+ }
+
/// Embed media into the transaction.
pub fn embed(&mut self, embeds: impl IntoIterator<Item = Embed<Uri>>) -> Result<(), Error> {
self.embeds.extend(embeds);
@@ -372,7 +444,7 @@ where
head,
object: CollaborativeObject { object, .. },
..
- } = store.update(id, msg, actions, self.embeds, signer)?;
+ } = store.update(&self.type_name, id, msg, actions, self.embeds, signer)?;
Ok((object, head))
}
@@ -424,7 +496,7 @@ pub mod test {
/// Turn a history into a concrete type, by traversing the history and applying each operation
/// to the state, skipping branches that return errors.
- pub fn from_history<R: ReadRepository, T: Cob>(
+ pub fn from_history<R: ReadRepository, T: Cob + CobWithType>(
history: &crate::cob::History,
repo: &R,
) -> Result<T, HistoryError<T>> {
diff --git a/radicle/src/cob/test.rs b/radicle/src/cob/test.rs
index d008d5389..571812167 100644
--- a/radicle/src/cob/test.rs
+++ b/radicle/src/cob/test.rs
@@ -22,7 +22,7 @@ use crate::profile::env;
use crate::storage::ReadRepository;
use crate::test::arbitrary;
-use super::store::Cob;
+use super::store::{Cob, CobWithType};
use super::thread;
/// Convenience type for building histories.
@@ -57,6 +57,7 @@ impl HistoryBuilder<thread::Thread> {
impl<T: Cob> HistoryBuilder<T>
where
+ T: CobWithType,
T::Action: for<'de> Deserialize<'de> + Serialize + Eq + 'static,
{
pub fn new<G: Signer>(actions: &[T::Action], time: Timestamp, signer: &G) -> HistoryBuilder<T> {
@@ -133,12 +134,13 @@ impl<A> Deref for HistoryBuilder<A> {
}
/// Create a new test history.
-pub fn history<T: Cob, G: Signer>(
+pub fn history<T, G: Signer>(
actions: &[T::Action],
time: Timestamp,
signer: &G,
) -> HistoryBuilder<T>
where
+ T: Cob + CobWithType,
T::Action: Serialize + Eq + 'static,
{
HistoryBuilder::new(actions, time, signer)
@@ -163,13 +165,14 @@ impl<G> Actor<G> {
impl<G: Signer> Actor<G> {
/// Create a new operation.
- pub fn op_with<T: Cob>(
+ pub fn op_with<T>(
&mut self,
actions: impl IntoIterator<Item = T::Action>,
identity: Option<Oid>,
timestamp: Timestamp,
) -> Op<T::Action>
where
+ T: Cob + CobWithType,
T::Action: Clone + Serialize,
{
let actions = actions.into_iter().collect::<Vec<_>>();
@@ -199,8 +202,9 @@ impl<G: Signer> Actor<G> {
}
/// Create a new operation.
- pub fn op<T: Cob>(&mut self, actions: impl IntoIterator<Item = T::Action>) -> Op<T::Action>
+ pub fn op<T>(&mut self, actions: impl IntoIterator<Item = T::Action>) -> Op<T::Action>
where
+ T: Cob + CobWithType,
T::Action: Clone + Serialize,
{
let identity = arbitrary::oid();
diff --git a/radicle/src/cob/thread.rs b/radicle/src/cob/thread.rs
index a0e814a45..08670dc6b 100644
--- a/radicle/src/cob/thread.rs
+++ b/radicle/src/cob/thread.rs
@@ -299,6 +299,12 @@ impl<T> Default for Thread<T> {
}
}
+impl<T> cob::store::CobWithType for Thread<T> {
+ fn type_name() -> &'static radicle_cob::TypeName {
+ &TYPENAME
+ }
+}
+
impl<T> Thread<T> {
pub fn new(id: CommentId, comment: T) -> Self {
Self {
@@ -403,10 +409,6 @@ impl cob::store::Cob for Thread {
type Action = Action;
type Error = Error;
- fn type_name() -> &'static radicle_cob::TypeName {
- &TYPENAME
- }
-
fn from_root<R: ReadRepository>(op: Op<Action>, repo: &R) -> Result<Self, Self::Error> {
let author = op.author;
let entry = op.id;
diff --git a/radicle/src/identity.rs b/radicle/src/identity.rs
index 620f16c7f..c149c83dc 100644
--- a/radicle/src/identity.rs
+++ b/radicle/src/identity.rs
@@ -8,4 +8,4 @@ pub use did::Did;
pub use doc::{Doc, DocAt, DocError, IdError, PayloadError, RawDoc, RepoId, Visibility};
pub use project::Project;
-pub use crate::cob::identity::{Error, Identity, IdentityMut};
+pub use crate::cob::identity::{Action, Error, Identity, IdentityMut, TYPENAME};
diff --git a/radicle/src/lib.rs b/radicle/src/lib.rs
index 1d38b4044..b1213ac59 100644
--- a/radicle/src/lib.rs
+++ b/radicle/src/lib.rs
@@ -31,7 +31,7 @@ pub mod test;
pub mod version;
pub mod web;
-pub use cob::{issue, patch};
+pub use cob::{external, issue, patch};
pub use node::Node;
pub use profile::Profile;
pub use storage::git::Storage;
commit 547614a5bbdca85e1cbd205b37cae7e25ba01273
Author: Lorenz Leutgeb <lorenz@leutgeb.xyz>
Date: Fri Oct 4 15:57:30 2024 +0200
cli-test: Add current working directory to PATH
diff --git a/radicle-cli-test/src/lib.rs b/radicle-cli-test/src/lib.rs
index 16c8e9a21..77f20c864 100644
--- a/radicle-cli-test/src/lib.rs
+++ b/radicle-cli-test/src/lib.rs
@@ -170,13 +170,19 @@ pub struct TestFormula {
impl TestFormula {
pub fn new(cwd: PathBuf) -> Self {
Self {
- cwd,
+ cwd: cwd.clone(),
env: HashMap::new(),
homes: HashMap::new(),
tests: Vec::new(),
subs: Substitutions::new(),
bins: env::var("PATH")
- .map(|p| p.split(':').map(PathBuf::from).collect())
+ .map(|env_path| {
+ let mut bins: Vec<PathBuf> = env_path.split(':').map(PathBuf::from).collect();
+ // Add current working directory to `$PATH`,
+ // this makes it more convenient to execute scripts during testing.
+ bins.push(cwd);
+ bins
+ })
.unwrap_or_default(),
}
}
@@ -534,7 +540,9 @@ $ rad sync
.as_bytes()
.to_owned();
- let mut actual = TestFormula::new(PathBuf::new());
+ let cwd = PathBuf::from("radicle-cli-test");
+
+ let mut actual = TestFormula::new(cwd.clone());
let path = Path::new("test.md").to_path_buf();
actual
.read(path.as_path(), io::BufReader::new(io::Cursor::new(input)))
@@ -542,14 +550,18 @@ $ rad sync
let expected = TestFormula {
homes: HashMap::new(),
- cwd: PathBuf::new(),
+ cwd: cwd.clone(),
env: HashMap::new(),
subs: Substitutions::new(),
- bins: env::var("PATH")
- .unwrap()
- .split(':')
- .map(PathBuf::from)
- .collect(),
+ bins: {
+ let mut bins: Vec<_> = env::var("PATH")
+ .unwrap_or_default()
+ .split(':')
+ .map(PathBuf::from)
+ .collect();
+ bins.push(cwd);
+ bins
+ },
tests: vec![
Test {
context: vec![String::from("Let's try to track @dave and @sean:")],
Exit code: 0
shell: 'cargo --version rustc --version cargo fmt --check cargo clippy --all-targets --workspace -- --deny warnings cargo build --all-targets --workspace cargo doc --workspace --no-deps cargo test --workspace --no-fail-fast '
Commands:
$ podman run --name 44992653-4652-4d7a-b3ea-3e50c517eabd -v /opt/radcis/ci.rad.levitte.org/cci/state/44992653-4652-4d7a-b3ea-3e50c517eabd/s:/44992653-4652-4d7a-b3ea-3e50c517eabd/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/44992653-4652-4d7a-b3ea-3e50c517eabd/w:/44992653-4652-4d7a-b3ea-3e50c517eabd/w -w /44992653-4652-4d7a-b3ea-3e50c517eabd/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:trixie bash /44992653-4652-4d7a-b3ea-3e50c517eabd/s/script.sh
+ cargo --version
info: syncing channel updates for '1.85-x86_64-unknown-linux-gnu'
info: latest update on 2025-03-18, rust version 1.85.1 (4eb161250 2025-03-15)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-src'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
info: installing component 'rust-src'
info: installing component 'rust-std'
info: installing component 'rustc'
info: installing component 'rustfmt'
cargo 1.85.1 (d73d2caf9 2024-12-31)
+ rustc --version
rustc 1.85.1 (4eb161250 2025-03-15)
+ cargo fmt --check
+ cargo clippy --all-targets --workspace -- --deny warnings
Updating crates.io index
Downloading crates ...
Downloaded aead v0.5.2
Downloaded aes-gcm v0.10.3
Downloaded amplify v4.6.0
Downloaded colorchoice v1.0.0
Downloaded amplify_derive v4.0.0
Downloaded sec1 v0.7.3
Downloaded aho-corasick v1.1.3
Downloaded shell-words v1.1.0
Downloaded gix-diff v0.49.0
Downloaded signals_receipts v0.2.0
Downloaded socks5-client v0.4.1
Downloaded bstr v1.9.1
Downloaded radicle-git-ext v0.8.0
Downloaded utf8_iter v1.0.4
Downloaded rand_core v0.6.4
Downloaded zerovec-derive v0.10.3
Downloaded gix-utils v0.1.14
Downloaded gix-tempfile v15.0.0
Downloaded group v0.13.0
Downloaded nonempty v0.9.0
Downloaded icu_provider_macros v1.5.0
Downloaded parking_lot v0.12.1
Downloaded p256 v0.13.2
Downloaded icu_provider v1.5.0
Downloaded zerofrom-derive v0.1.6
Downloaded dyn-clone v1.0.17
Downloaded p384 v0.13.0
Downloaded popol v3.0.0
Downloaded zeroize v1.7.0
Downloaded num-integer v0.1.46
Downloaded radicle-std-ext v0.1.0
Downloaded gix-revwalk v0.17.0
Downloaded gix-revision v0.31.1
Downloaded utf8parse v0.2.1
Downloaded noise-framework v0.4.0
Downloaded gix-shallow v0.1.0
Downloaded phf_shared v0.11.3
Downloaded normalize-line-endings v0.3.0
Downloaded home v0.5.9
Downloaded inout v0.1.3
Downloaded sqlite v0.32.0
Downloaded icu_locid v1.5.0
Downloaded io-reactor v0.5.2
Downloaded zerovec v0.10.4
Downloaded termion v2.0.3
Downloaded iana-time-zone v0.1.60
Downloaded icu_properties v1.5.1
Downloaded icu_collections v1.5.0
Downloaded itoa v1.0.11
Downloaded inquire v0.7.5
Downloaded walkdir v2.5.0
Downloaded idna v1.0.3
Downloaded radicle-surf v0.22.0
Downloaded gix-path v0.10.15
Downloaded thiserror-impl v2.0.12
Downloaded icu_properties_data v1.5.1
Downloaded tinyvec v1.6.0
Downloaded timeago v0.4.2
Downloaded synstructure v0.13.1
Downloaded qcheck v1.0.0
Downloaded quote v1.0.36
Downloaded unicode-width v0.1.11
Downloaded tempfile v3.10.1
Downloaded spin v0.9.8
Downloaded pem-rfc7468 v0.7.0
Downloaded snapbox v0.4.17
Downloaded linux-raw-sys v0.4.13
Downloaded hmac v0.12.1
Downloaded tree-sitter-css v0.23.1
Downloaded once_cell v1.19.0
Downloaded miniz_oxide v0.8.8
Downloaded url v2.5.4
Downloaded rand v0.8.5
Downloaded prodash v29.0.2
Downloaded num-traits v0.2.18
Downloaded unicode-segmentation v1.11.0
Downloaded tree-sitter-go v0.23.4
Downloaded jiff v0.2.1
Downloaded unicode-normalization v0.1.23
Downloaded winnow v0.6.26
Downloaded libc v0.2.155
Downloaded tree-sitter v0.24.4
Downloaded tree-sitter-python v0.23.4
Downloaded ssh-key v0.6.6
Downloaded vcpkg v0.2.15
Downloaded tree-sitter-c v0.23.2
Downloaded syn v2.0.89
Downloaded regex v1.11.1
Downloaded tree-sitter-rust v0.23.2
Downloaded tree-sitter-md v0.3.2
Downloaded regex-syntax v0.8.5
Downloaded rustix v0.38.34
Downloaded syn v1.0.109
Downloaded serde_json v1.0.116
Downloaded gix-ref v0.49.1
Downloaded tree-sitter-bash v0.23.3
Downloaded tar v0.4.40
Downloaded tree-sitter-ruby v0.23.1
Downloaded similar v2.5.0
Downloaded serde v1.0.219
Downloaded ryu v1.0.17
Downloaded regex-automata v0.4.9
Downloaded rsa v0.9.6
Downloaded gix-odb v0.66.0
Downloaded proc-macro2 v1.0.92
Downloaded yoke v0.7.5
Downloaded unicode-ident v1.0.12
Downloaded typenum v1.17.0
Downloaded socket2 v0.5.7
Downloaded gix-packetline v0.18.4
Downloaded yansi v0.5.1
Downloaded streaming-iterator v0.1.9
Downloaded tree-sitter-json v0.24.8
Downloaded tree-sitter-html v0.23.2
Downloaded tinyvec_macros v0.1.1
Downloaded sha3 v0.10.8
Downloaded tree-sitter-typescript v0.23.2
Downloaded tinystr v0.7.6
Downloaded thiserror v2.0.12
Downloaded spki v0.7.3
Downloaded num-bigint-dig v0.8.4
Downloaded indexmap v2.2.6
Downloaded qcheck-macros v1.0.0
Downloaded proc-macro-error-attr v1.0.4
Downloaded pbkdf2 v0.12.2
Downloaded numtoa v0.1.0
Downloaded num-iter v0.1.44
Downloaded writeable v0.5.5
Downloaded termion v3.0.0
Downloaded ssh-encoding v0.2.0
Downloaded opaque-debug v0.3.1
Downloaded gix-quote v0.4.15
Downloaded icu_normalizer_data v1.5.1
Downloaded libgit2-sys v0.17.0+1.8.1
Downloaded icu_locid_transform_data v1.5.1
Downloaded icu_locid_transform v1.5.0
Downloaded phf v0.11.3
Downloaded proc-macro-error v1.0.4
Downloaded polyval v0.6.2
Downloaded pkg-config v0.3.30
Downloaded lexopt v0.3.0
Downloaded pretty_assertions v1.4.0
Downloaded litemap v0.7.5
Downloaded tree-sitter-toml-ng v0.6.0
Downloaded idna_adapter v1.2.0
Downloaded icu_normalizer v1.5.0
Downloaded log v0.4.21
Downloaded universal-hash v0.5.1
Downloaded thiserror v1.0.69
Downloaded poly1305 v0.8.0
Downloaded percent-encoding v2.3.1
Downloaded same-file v1.0.6
Downloaded salsa20 v0.10.2
Downloaded gix-sec v0.10.12
Downloaded multibase v0.9.1
Downloaded memchr v2.7.2
Downloaded sqlite3-sys v0.15.2
Downloaded nonempty v0.5.0
Downloaded keccak v0.1.5
Downloaded scopeguard v1.2.0
Downloaded gix-refspec v0.27.0
Downloaded ppv-lite86 v0.2.17
Downloaded libm v0.2.8
Downloaded tree-sitter-highlight v0.24.4
Downloaded lazy_static v1.5.0
Downloaded unicode-display-width v0.3.0
Downloaded primeorder v0.13.6
Downloaded pkcs8 v0.10.2
Downloaded lock_api v0.4.11
Downloaded parking_lot_core v0.9.9
Downloaded jobserver v0.1.31
Downloaded yoke-derive v0.7.5
Downloaded utf16_iter v1.0.5
Downloaded subtle v2.5.0
Downloaded newline-converter v0.3.0
Downloaded netservices v0.8.0
Downloaded memmap2 v0.9.4
Downloaded localtime v1.3.1
Downloaded gix-trace v0.1.12
Downloaded pkcs1 v0.7.5
Downloaded maybe-async v0.2.10
Downloaded gix-validate v0.9.4
Downloaded version_check v0.9.4
Downloaded tree-sitter-language v0.1.2
Downloaded xattr v1.3.1
Downloaded stable_deref_trait v1.2.0
Downloaded siphasher v1.0.1
Downloaded git-ref-format v0.3.0
Downloaded getrandom v0.2.15
Downloaded gix-pack v0.56.0
Downloaded p521 v0.13.3
Downloaded gix-hash v0.15.1
Downloaded cypheraddr v0.4.0
Downloaded gix-date v0.9.4
Downloaded hashbrown v0.14.3
Downloaded base16ct v0.2.0
Downloaded gix-traverse v0.43.1
Downloaded gix-transport v0.44.0
Downloaded either v1.11.0
Downloaded digest v0.10.7
Downloaded bitflags v2.5.0
Downloaded gix-url v0.28.2
Downloaded zerofrom v0.1.6
Downloaded ssh-cipher v0.2.0
Downloaded crc32fast v1.4.0
Downloaded gix-protocol v0.47.0
Downloaded gix-prompt v0.9.1
Downloaded gix-object v0.46.1
Downloaded cpufeatures v0.2.12
Downloaded ghash v0.5.1
Downloaded cyphernet v0.5.2
Downloaded crossbeam-utils v0.8.19
Downloaded const-oid v0.9.6
Downloaded ascii v1.1.0
Downloaded amplify_syn v2.0.1
Downloaded write16 v1.0.0
Downloaded gix-negotiate v0.17.0
Downloaded gix-features v0.39.1
Downloaded cyphergraphy v0.3.0
Downloaded base64 v0.13.1
Downloaded base32 v0.4.0
Downloaded gix-fs v0.12.1
Downloaded sqlite3-src v0.5.1
Downloaded git-ref-format-macro v0.3.0
Downloaded form_urlencoded v1.2.1
Downloaded faster-hex v0.9.0
Downloaded errno v0.3.9
Downloaded ecdsa v0.16.9
Downloaded ctr v0.9.2
Downloaded ct-codecs v1.1.1
Downloaded chacha20 v0.9.1
Downloaded gix-hashtable v0.6.0
Downloaded generic-array v0.14.7
Downloaded flate2 v1.1.1
Downloaded displaydoc v0.2.5
Downloaded diff v0.1.13
Downloaded crypto-common v0.1.6
Downloaded crossbeam-channel v0.5.15
Downloaded cfg-if v1.0.0
Downloaded anstream v0.6.13
Downloaded thiserror-impl v1.0.69
Downloaded rfc6979 v0.4.0
Downloaded rand_chacha v0.3.1
Downloaded fxhash v0.2.1
Downloaded fastrand v2.1.0
Downloaded emojis v0.6.4
Downloaded ed25519 v1.5.3
Downloaded ec25519 v0.1.0
Downloaded cipher v0.4.4
Downloaded bytesize v2.0.1
Downloaded block-padding v0.3.3
Downloaded base-x v0.2.11
Downloaded autocfg v1.2.0
Downloaded arc-swap v1.7.1
Downloaded anstyle-query v1.0.2
Downloaded anstyle v1.0.6
Downloaded siphasher v0.3.11
Downloaded libz-sys v1.1.16
Downloaded signature v2.2.0
Downloaded shlex v1.3.0
Downloaded sha1_smol v1.0.0
Downloaded serde_derive v1.0.219
Downloaded sem_safe v0.2.0
Downloaded scrypt v0.11.0
Downloaded gix-lock v15.0.1
Downloaded gix-credentials v0.26.0
Downloaded gix-config-value v0.14.12
Downloaded gix-commitgraph v0.25.1
Downloaded gix-command v0.4.1
Downloaded gix-chunk v0.4.11
Downloaded gix-actor v0.33.2
Downloaded git2 v0.19.0
Downloaded git-ref-format-core v0.3.0
Downloaded filetime v0.2.23
Downloaded ff v0.13.0
Downloaded escargot v0.5.10
Downloaded equivalent v1.0.1
Downloaded elliptic-curve v0.13.8
Downloaded der v0.7.9
Downloaded data-encoding-macro-internal v0.1.12
Downloaded data-encoding v2.5.0
Downloaded crypto-bigint v0.5.5
Downloaded colored v2.1.0
Downloaded bloomy v1.2.0
Downloaded base64 v0.21.7
Downloaded anyhow v1.0.82
Downloaded snapbox-macros v0.3.8
Downloaded smallvec v1.13.2
Downloaded signature v1.6.4
Downloaded blowfish v0.9.1
Downloaded sha2 v0.10.8
Downloaded data-encoding-macro v0.1.14
Downloaded chrono v0.4.38
Downloaded cc v1.2.2
Downloaded bcrypt-pbkdf v0.10.0
Downloaded anstyle-parse v0.2.3
Downloaded chacha20poly1305 v0.10.1
Downloaded cbc v0.1.2
Downloaded byteorder v1.5.0
Downloaded block-buffer v0.10.4
Downloaded base64ct v1.6.0
Downloaded base64 v0.22.1
Downloaded amplify_num v0.5.2
Downloaded aes v0.8.4
Downloaded adler2 v2.0.0
Compiling libc v0.2.155
Compiling proc-macro2 v1.0.92
Compiling unicode-ident v1.0.12
Checking cfg-if v1.0.0
Compiling shlex v1.3.0
Compiling version_check v0.9.4
Compiling typenum v1.17.0
Checking memchr v2.7.2
Checking getrandom v0.2.15
Compiling quote v1.0.36
Compiling jobserver v0.1.31
Compiling generic-array v0.14.7
Compiling syn v2.0.89
Checking aho-corasick v1.1.3
Compiling cc v1.2.2
Checking regex-syntax v0.8.5
Compiling serde v1.0.219
Checking rand_core v0.6.4
Checking smallvec v1.13.2
Checking crypto-common v0.1.6
Checking fastrand v2.1.0
Checking log v0.4.21
Compiling autocfg v1.2.0
Checking bitflags v2.5.0
Checking subtle v2.5.0
Checking regex-automata v0.4.9
Checking once_cell v1.19.0
Compiling syn v1.0.109
Checking stable_deref_trait v1.2.0
Checking zeroize v1.7.0
Checking cpufeatures v0.2.12
Checking tinyvec_macros v0.1.1
Checking tinyvec v1.6.0
Compiling synstructure v0.13.1
Checking bstr v1.9.1
Compiling crc32fast v1.4.0
Checking unicode-normalization v0.1.23
Checking block-padding v0.3.3
Compiling icu_locid_transform_data v1.5.1
Checking litemap v0.7.5
Checking writeable v0.5.5
Checking inout v0.1.3
Checking block-buffer v0.10.4
Compiling icu_properties_data v1.5.1
Compiling serde_derive v1.0.219
Compiling zerofrom-derive v0.1.6
Compiling yoke-derive v0.7.5
Compiling zerovec-derive v0.10.3
Compiling displaydoc v0.2.5
Checking zerofrom v0.1.6
Checking yoke v0.7.5
Compiling icu_provider_macros v1.5.0
Checking zerovec v0.10.4
Compiling pkg-config v0.3.30
Checking digest v0.10.7
Checking cipher v0.4.4
Compiling icu_normalizer_data v1.5.1
Checking tinystr v0.7.6
Checking icu_collections v1.5.0
Compiling thiserror v1.0.69
Checking icu_locid v1.5.0
Compiling thiserror-impl v1.0.69
Checking icu_provider v1.5.0
Checking utf16_iter v1.0.5
Checking utf8_iter v1.0.4
Checking write16 v1.0.0
Checking icu_locid_transform v1.5.0
Compiling rustix v0.38.34
Checking sha2 v0.10.8
Checking itoa v1.0.11
Checking icu_properties v1.5.1
Checking percent-encoding v2.3.1
Checking linux-raw-sys v0.4.13
Checking hashbrown v0.14.3
Checking form_urlencoded v1.2.1
Checking universal-hash v0.5.1
Compiling vcpkg v0.2.15
Checking opaque-debug v0.3.1
Compiling amplify_syn v2.0.1
Checking icu_normalizer v1.5.0
Compiling libz-sys v1.1.16
Compiling data-encoding v2.5.0
Checking idna_adapter v1.2.0
Checking idna v1.0.3
Checking byteorder v1.5.0
Checking url v2.5.4
Compiling data-encoding-macro-internal v0.1.12
Compiling amplify_derive v4.0.0
Checking amplify_num v0.5.2
Checking ascii v1.1.0
Checking signature v1.6.4
Checking data-encoding-macro v0.1.14
Checking ed25519 v1.5.3
Checking tempfile v3.10.1
Compiling libgit2-sys v0.17.0+1.8.1
Checking aead v0.5.2
Compiling proc-macro-error-attr v1.0.4
Checking base-x v0.2.11
Checking ct-codecs v1.1.1
Checking multibase v0.9.1
Checking ec25519 v0.1.0
Checking poly1305 v0.8.0
Checking chacha20 v0.9.1
Compiling proc-macro-error v1.0.4
Checking keccak v0.1.5
Checking sha3 v0.10.8
Checking amplify v4.6.0
Compiling git-ref-format-core v0.3.0
Checking polyval v0.6.2
Compiling sqlite3-src v0.5.1
Checking cyphergraphy v0.3.0
Checking hmac v0.12.1
Checking base32 v0.4.0
Checking base64ct v1.6.0
Compiling thiserror v2.0.12
Checking ppv-lite86 v0.2.17
Checking pem-rfc7468 v0.7.0
Checking rand_chacha v0.3.1
Checking cypheraddr v0.4.0
Checking pbkdf2 v0.12.2
Compiling git-ref-format-macro v0.3.0
Checking ghash v0.5.1
Checking chacha20poly1305 v0.10.1
Checking aes v0.8.4
Checking ctr v0.9.2
Compiling thiserror-impl v2.0.12
Compiling crossbeam-utils v0.8.19
Checking aes-gcm v0.10.3
Checking git-ref-format v0.3.0
Checking noise-framework v0.4.0
Checking socks5-client v0.4.1
Checking rand v0.8.5
Checking ssh-encoding v0.2.0
Checking blowfish v0.9.1
Checking cbc v0.1.2
Checking radicle-std-ext v0.1.0
Checking bcrypt-pbkdf v0.10.0
Checking ssh-cipher v0.2.0
Checking cyphernet v0.5.2
Checking signature v2.2.0
Checking ssh-key v0.6.6
Checking crossbeam-channel v0.5.15
Checking qcheck v1.0.0
Checking radicle-ssh v0.9.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-ssh)
Compiling num-traits v0.2.18
Compiling serde_json v1.0.116
Checking equivalent v1.0.1
Checking indexmap v2.2.6
Checking ryu v1.0.17
Checking tree-sitter-language v0.1.2
Checking siphasher v1.0.1
Checking nonempty v0.9.0
Compiling lock_api v0.4.11
Checking radicle-dag v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-dag)
Compiling anyhow v1.0.82
Compiling parking_lot_core v0.9.9
Checking lazy_static v1.5.0
Checking gix-trace v0.1.12
Checking anstyle-query v1.0.2
Checking scopeguard v1.2.0
Checking iana-time-zone v0.1.60
Checking chrono v0.4.38
Checking colored v2.1.0
Checking localtime v1.3.1
Checking bytesize v2.0.1
Checking base64 v0.21.7
Checking utf8parse v0.2.1
Checking anstyle-parse v0.2.3
Checking parking_lot v0.12.1
Checking faster-hex v0.9.0
Checking sem_safe v0.2.0
Checking errno v0.3.9
Checking anstyle v1.0.6
Checking colorchoice v1.0.0
Checking signals_receipts v0.2.0
Checking anstream v0.6.13
Checking gix-utils v0.1.14
Checking adler2 v2.0.0
Checking snapbox-macros v0.3.8
Checking gix-hash v0.15.1
Checking miniz_oxide v0.8.8
Checking normalize-line-endings v0.3.0
Checking same-file v1.0.6
Checking similar v2.5.0
Checking snapbox v0.4.17
Checking flate2 v1.1.1
Checking walkdir v2.5.0
Checking radicle-signals v0.11.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-signals)
Checking prodash v29.0.2
Compiling tree-sitter v0.24.4
Checking sha1_smol v1.0.0
Checking gix-features v0.39.1
Checking diff v0.1.13
Checking unicode-segmentation v1.11.0
Checking yansi v0.5.1
Checking numtoa v0.1.0
Checking pretty_assertions v1.4.0
Compiling xattr v1.3.1
Compiling filetime v0.2.23
Compiling escargot v0.5.10
Checking lexopt v0.3.0
Checking home v0.5.9
Checking gix-path v0.10.15
Compiling tar v0.4.40
Checking termion v2.0.3
Checking newline-converter v0.3.0
Checking fxhash v0.2.1
Checking regex v1.11.1
Checking dyn-clone v1.0.17
Checking unicode-width v0.1.11
Checking streaming-iterator v0.1.9
Checking inquire v0.7.5
Compiling radicle-surf v0.22.0
Checking termion v3.0.0
Checking unicode-display-width v0.3.0
Compiling tree-sitter-json v0.24.8
Compiling tree-sitter-typescript v0.23.2
Compiling tree-sitter-go v0.23.4
Compiling tree-sitter-c v0.23.2
Compiling tree-sitter-css v0.23.1
Compiling tree-sitter-rust v0.23.2
Compiling tree-sitter-python v0.23.4
Compiling tree-sitter-bash v0.23.3
Compiling tree-sitter-html v0.23.2
Compiling tree-sitter-toml-ng v0.6.0
Compiling tree-sitter-ruby v0.23.1
Compiling tree-sitter-md v0.3.2
Checking sqlite3-sys v0.15.2
Checking sqlite v0.32.0
Checking nonempty v0.5.0
Compiling radicle-cli v0.12.1 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-cli)
Checking base64 v0.13.1
Checking tree-sitter-highlight v0.24.4
Checking timeago v0.4.2
Checking jiff v0.2.1
Checking winnow v0.6.26
Checking gix-hashtable v0.6.0
Checking gix-validate v0.9.4
Checking memmap2 v0.9.4
Checking gix-chunk v0.4.11
Checking gix-commitgraph v0.25.1
Checking gix-fs v0.12.1
Checking shell-words v1.1.0
Checking gix-command v0.4.1
Checking gix-tempfile v15.0.0
Checking gix-url v0.28.2
Checking gix-lock v15.0.1
Checking gix-config-value v0.14.12
Checking gix-quote v0.4.15
Checking gix-sec v0.10.12
Checking gix-packetline v0.18.4
Checking gix-transport v0.44.0
Checking gix-prompt v0.9.1
Checking gix-date v0.9.4
Checking gix-credentials v0.26.0
Checking gix-shallow v0.1.0
Checking gix-actor v0.33.2
Compiling maybe-async v0.2.10
Checking arc-swap v1.7.1
Checking gix-object v0.46.1
Checking popol v3.0.0
Checking either v1.11.0
Checking io-reactor v0.5.2
Checking salsa20 v0.10.2
Checking socket2 v0.5.7
Checking siphasher v0.3.11
Checking gix-revwalk v0.17.0
Checking gix-diff v0.49.0
Checking gix-traverse v0.43.1
Checking gix-revision v0.31.1
Checking gix-negotiate v0.17.0
Checking gix-refspec v0.27.0
Checking gix-pack v0.56.0
Checking gix-ref v0.49.1
Compiling radicle-node v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-node)
Checking gix-protocol v0.47.0
Checking bloomy v1.2.0
Checking netservices v0.8.0
Checking gix-odb v0.66.0
Checking scrypt v0.11.0
Checking radicle-systemd v0.9.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-systemd)
Compiling qcheck-macros v1.0.0
Compiling radicle-remote-helper v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-remote-helper)
Checking phf_shared v0.11.3
Checking phf v0.11.3
Checking emojis v0.6.4
Checking git2 v0.19.0
Checking radicle-git-ext v0.8.0
Checking radicle-term v0.12.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-term)
Checking radicle-crypto v0.11.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-crypto)
Checking radicle-cob v0.13.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-cob)
Checking radicle-crdt v0.1.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-crdt)
Checking radicle v0.14.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle)
Checking radicle-cli-test v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-cli-test)
Checking radicle-fetch v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-fetch)
Checking radicle-tools v0.9.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-tools)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 42.78s
+ cargo build --all-targets --workspace
Compiling cfg-if v1.0.0
Compiling libc v0.2.155
Compiling shlex v1.3.0
Compiling memchr v2.7.2
Compiling typenum v1.17.0
Compiling regex-syntax v0.8.5
Compiling jobserver v0.1.31
Compiling cc v1.2.2
Compiling getrandom v0.2.15
Compiling aho-corasick v1.1.3
Compiling rand_core v0.6.4
Compiling generic-array v0.14.7
Compiling smallvec v1.13.2
Compiling serde v1.0.219
Compiling regex-automata v0.4.9
Compiling crypto-common v0.1.6
Compiling log v0.4.21
Compiling fastrand v2.1.0
Compiling bitflags v2.5.0
Compiling subtle v2.5.0
Compiling zerofrom v0.1.6
Compiling once_cell v1.19.0
Compiling stable_deref_trait v1.2.0
Compiling zeroize v1.7.0
Compiling yoke v0.7.5
Compiling zerovec v0.10.4
Compiling tinyvec_macros v0.1.1
Compiling cpufeatures v0.2.12
Compiling tinyvec v1.6.0
Compiling block-padding v0.3.3
Compiling writeable v0.5.5
Compiling litemap v0.7.5
Compiling unicode-normalization v0.1.23
Compiling bstr v1.9.1
Compiling tinystr v0.7.6
Compiling icu_locid v1.5.0
Compiling inout v0.1.3
Compiling block-buffer v0.10.4
Compiling adler2 v2.0.0
Compiling miniz_oxide v0.8.8
Compiling digest v0.10.7
Compiling icu_provider v1.5.0
Compiling cipher v0.4.4
Compiling crc32fast v1.4.0
Compiling icu_locid_transform_data v1.5.1
Compiling icu_properties_data v1.5.1
Compiling icu_locid_transform v1.5.0
Compiling icu_collections v1.5.0
Compiling icu_normalizer_data v1.5.1
Compiling utf8_iter v1.0.4
Compiling write16 v1.0.0
Compiling utf16_iter v1.0.5
Compiling thiserror v1.0.69
Compiling sha2 v0.10.8
Compiling icu_properties v1.5.1
Compiling hashbrown v0.14.3
Compiling linux-raw-sys v0.4.13
Compiling percent-encoding v2.3.1
Compiling itoa v1.0.11
Compiling form_urlencoded v1.2.1
Compiling rustix v0.38.34
Compiling universal-hash v0.5.1
Compiling opaque-debug v0.3.1
Compiling libz-sys v1.1.16
Compiling byteorder v1.5.0
Compiling ascii v1.1.0
Compiling amplify_num v0.5.2
Compiling signature v1.6.4
Compiling data-encoding v2.5.0
Compiling icu_normalizer v1.5.0
Compiling idna_adapter v1.2.0
Compiling idna v1.0.3
Compiling tempfile v3.10.1
Compiling url v2.5.4
Compiling amplify v4.6.0
Compiling data-encoding-macro v0.1.14
Compiling ed25519 v1.5.3
Compiling libgit2-sys v0.17.0+1.8.1
Compiling aead v0.5.2
Compiling ct-codecs v1.1.1
Compiling base-x v0.2.11
Compiling ec25519 v0.1.0
Compiling multibase v0.9.1
Compiling poly1305 v0.8.0
Compiling chacha20 v0.9.1
Compiling cyphergraphy v0.3.0
Compiling keccak v0.1.5
Compiling polyval v0.6.2
Compiling sha3 v0.10.8
Compiling git-ref-format-core v0.3.0
Compiling hmac v0.12.1
Compiling sqlite3-src v0.5.1
Compiling base32 v0.4.0
Compiling ppv-lite86 v0.2.17
Compiling base64ct v1.6.0
Compiling rand_chacha v0.3.1
Compiling pem-rfc7468 v0.7.0
Compiling cypheraddr v0.4.0
Compiling git-ref-format-macro v0.3.0
Compiling pbkdf2 v0.12.2
Compiling ghash v0.5.1
Compiling chacha20poly1305 v0.10.1
Compiling ctr v0.9.2
Compiling aes v0.8.4
Compiling thiserror v2.0.12
Compiling git-ref-format v0.3.0
Compiling noise-framework v0.4.0
Compiling aes-gcm v0.10.3
Compiling socks5-client v0.4.1
Compiling ssh-encoding v0.2.0
Compiling rand v0.8.5
Compiling blowfish v0.9.1
Compiling cbc v0.1.2
Compiling radicle-std-ext v0.1.0
Compiling ssh-cipher v0.2.0
Compiling bcrypt-pbkdf v0.10.0
Compiling cyphernet v0.5.2
Compiling crossbeam-utils v0.8.19
Compiling signature v2.2.0
Compiling ssh-key v0.6.6
Compiling crossbeam-channel v0.5.15
Compiling qcheck v1.0.0
Compiling radicle-ssh v0.9.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-ssh)
Compiling equivalent v1.0.1
Compiling indexmap v2.2.6
Compiling tree-sitter-language v0.1.2
Compiling ryu v1.0.17
Compiling num-traits v0.2.18
Compiling serde_json v1.0.116
Compiling siphasher v1.0.1
Compiling nonempty v0.9.0
Compiling radicle-dag v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-dag)
Compiling lazy_static v1.5.0
Compiling gix-trace v0.1.12
Compiling scopeguard v1.2.0
Compiling iana-time-zone v0.1.60
Compiling anstyle-query v1.0.2
Compiling lock_api v0.4.11
Compiling chrono v0.4.38
Compiling parking_lot_core v0.9.9
Compiling colored v2.1.0
Compiling bytesize v2.0.1
Compiling localtime v1.3.1
Compiling base64 v0.21.7
Compiling utf8parse v0.2.1
Compiling anstyle-parse v0.2.3
Compiling parking_lot v0.12.1
Compiling anyhow v1.0.82
Compiling faster-hex v0.9.0
Compiling sem_safe v0.2.0
Compiling errno v0.3.9
Compiling colorchoice v1.0.0
Compiling anstyle v1.0.6
Compiling signals_receipts v0.2.0
Compiling anstream v0.6.13
Compiling gix-utils v0.1.14
Compiling snapbox-macros v0.3.8
Compiling gix-hash v0.15.1
Compiling normalize-line-endings v0.3.0
Compiling same-file v1.0.6
Compiling similar v2.5.0
Compiling walkdir v2.5.0
Compiling snapbox v0.4.17
Compiling radicle-signals v0.11.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-signals)
Compiling prodash v29.0.2
Compiling flate2 v1.1.1
Compiling tree-sitter v0.24.4
Compiling sha1_smol v1.0.0
Compiling gix-features v0.39.1
Compiling yansi v0.5.1
Compiling unicode-segmentation v1.11.0
Compiling diff v0.1.13
Compiling numtoa v0.1.0
Compiling pretty_assertions v1.4.0
Compiling xattr v1.3.1
Compiling filetime v0.2.23
Compiling home v0.5.9
Compiling lexopt v0.3.0
Compiling gix-path v0.10.15
Compiling tar v0.4.40
Compiling termion v2.0.3
Compiling newline-converter v0.3.0
Compiling fxhash v0.2.1
Compiling regex v1.11.1
Compiling dyn-clone v1.0.17
Compiling unicode-width v0.1.11
Compiling streaming-iterator v0.1.9
Compiling inquire v0.7.5
Compiling radicle-surf v0.22.0
Compiling escargot v0.5.10
Compiling termion v3.0.0
Compiling unicode-display-width v0.3.0
Compiling tree-sitter-ruby v0.23.1
Compiling tree-sitter-md v0.3.2
Compiling tree-sitter-json v0.24.8
Compiling tree-sitter-rust v0.23.2
Compiling tree-sitter-html v0.23.2
Compiling tree-sitter-typescript v0.23.2
Compiling tree-sitter-toml-ng v0.6.0
Compiling tree-sitter-css v0.23.1
Compiling tree-sitter-c v0.23.2
Compiling tree-sitter-go v0.23.4
Compiling tree-sitter-python v0.23.4
Compiling tree-sitter-bash v0.23.3
Compiling sqlite3-sys v0.15.2
Compiling sqlite v0.32.0
Compiling radicle-cli v0.12.1 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-cli)
Compiling nonempty v0.5.0
Compiling base64 v0.13.1
Compiling tree-sitter-highlight v0.24.4
Compiling timeago v0.4.2
Compiling jiff v0.2.1
Compiling winnow v0.6.26
Compiling gix-hashtable v0.6.0
Compiling gix-validate v0.9.4
Compiling memmap2 v0.9.4
Compiling gix-chunk v0.4.11
Compiling gix-commitgraph v0.25.1
Compiling gix-fs v0.12.1
Compiling shell-words v1.1.0
Compiling gix-tempfile v15.0.0
Compiling gix-command v0.4.1
Compiling gix-lock v15.0.1
Compiling gix-url v0.28.2
Compiling gix-config-value v0.14.12
Compiling gix-quote v0.4.15
Compiling gix-date v0.9.4
Compiling gix-sec v0.10.12
Compiling gix-actor v0.33.2
Compiling gix-object v0.46.1
Compiling gix-prompt v0.9.1
Compiling gix-packetline v0.18.4
Compiling gix-transport v0.44.0
Compiling gix-revwalk v0.17.0
Compiling gix-revision v0.31.1
Compiling gix-traverse v0.43.1
Compiling gix-diff v0.49.0
Compiling gix-pack v0.56.0
Compiling gix-refspec v0.27.0
Compiling gix-negotiate v0.17.0
Compiling gix-ref v0.49.1
Compiling gix-credentials v0.26.0
Compiling gix-shallow v0.1.0
Compiling arc-swap v1.7.1
Compiling gix-odb v0.66.0
Compiling gix-protocol v0.47.0
Compiling popol v3.0.0
Compiling either v1.11.0
Compiling io-reactor v0.5.2
Compiling salsa20 v0.10.2
Compiling socket2 v0.5.7
Compiling siphasher v0.3.11
Compiling radicle-node v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-node)
Compiling bloomy v1.2.0
Compiling netservices v0.8.0
Compiling scrypt v0.11.0
Compiling radicle-systemd v0.9.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-systemd)
Compiling radicle-remote-helper v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-remote-helper)
Compiling phf_shared v0.11.3
Compiling phf v0.11.3
Compiling emojis v0.6.4
Compiling git2 v0.19.0
Compiling radicle-git-ext v0.8.0
Compiling radicle-term v0.12.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-term)
Compiling radicle-crypto v0.11.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-crypto)
Compiling radicle-cob v0.13.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-cob)
Compiling radicle-crdt v0.1.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-crdt)
Compiling radicle v0.14.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle)
Compiling radicle-cli-test v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-cli-test)
Compiling radicle-fetch v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-fetch)
Compiling radicle-tools v0.9.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-tools)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 08s
+ cargo doc --workspace --no-deps
Compiling syn v1.0.109
Checking radicle-ssh v0.9.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-ssh)
Checking radicle-dag v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-dag)
Checking radicle-signals v0.11.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-signals)
Compiling radicle-cli v0.12.1 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-cli)
Checking radicle-term v0.12.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-term)
Compiling radicle-node v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-node)
Checking radicle-systemd v0.9.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-systemd)
Documenting radicle-term v0.12.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-term)
Documenting radicle-signals v0.11.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-signals)
Documenting radicle-ssh v0.9.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-ssh)
Documenting radicle-dag v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-dag)
Documenting radicle-systemd v0.9.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-systemd)
Compiling amplify_syn v2.0.1
Compiling proc-macro-error v1.0.4
Compiling data-encoding-macro-internal v0.1.12
Compiling git-ref-format-macro v0.3.0
Compiling amplify_derive v4.0.0
Checking data-encoding-macro v0.1.14
Checking git-ref-format v0.3.0
Checking multibase v0.9.1
Checking radicle-git-ext v0.8.0
Checking radicle-surf v0.22.0
Checking amplify v4.6.0
Checking cyphergraphy v0.3.0
Checking io-reactor v0.5.2
Checking cypheraddr v0.4.0
Checking noise-framework v0.4.0
Checking socks5-client v0.4.1
Checking cyphernet v0.5.2
Checking radicle-crypto v0.11.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-crypto)
Checking netservices v0.8.0
Documenting radicle-crypto v0.11.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-crypto)
Checking radicle-cob v0.13.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-cob)
Documenting radicle-cob v0.13.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-cob)
Documenting radicle-crdt v0.1.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-crdt)
Checking radicle v0.14.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle)
Documenting radicle v0.14.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle)
Checking radicle-cli-test v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-cli-test)
Checking radicle-fetch v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-fetch)
Documenting radicle-fetch v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-fetch)
Documenting radicle-cli-test v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-cli-test)
Documenting radicle-cli v0.12.1 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-cli)
Documenting radicle-node v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-node)
Documenting radicle-remote-helper v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-remote-helper)
Checking radicle-remote-helper v0.10.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-remote-helper)
Documenting radicle-tools v0.9.0 (/44992653-4652-4d7a-b3ea-3e50c517eabd/w/radicle-tools)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 10.20s
Generated /44992653-4652-4d7a-b3ea-3e50c517eabd/w/target/doc/radicle/index.html and 22 other files
+ cargo test --workspace --no-fail-fast
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.12s
Running unittests src/lib.rs (target/debug/deps/radicle-cbcedab996359b3f)
running 171 tests
test canonical::formatter::test::ascii_control_characters ... ok
test canonical::formatter::test::securesystemslib_asserts ... ok
test canonical::formatter::test::ordered_nested_object ... ok
test cob::cache::tests::test_check_version ... ok
test cob::cache::tests::test_migrate_to ... ok
test cob::cache::migrations::_2::tests::test_patch_json_deserialization ... ok
test cob::cache::migrations::_2::tests::test_migration_2 ... ok
test cob::common::test::test_color ... ok
test cob::common::test::test_emojis ... ok
test cob::identity::test::prop_json_eq_str ... ok
test cob::identity::test::test_identity_redact_revision ... ok
test cob::identity::test::test_identity_update_rejected ... ok
test cob::identity::test::test_identity_updates ... ok
test cob::identity::test::test_identity_remove_delegate_concurrent ... ok
test cob::identity::test::test_identity_reject_concurrent ... ok
test cob::issue::cache::tests::test_counts ... ok
test cob::issue::cache::tests::test_get ... ok
test cob::issue::cache::tests::test_is_empty ... ok
test cob::issue::cache::tests::test_list ... ok
test cob::issue::cache::tests::test_list_by_status ... ok
test cob::issue::cache::tests::test_remove ... ok
test cob::identity::test::test_valid_identity ... ok
test cob::identity::test::test_identity_updates_concurrent ... ok
test cob::issue::test::test_embeds_edit ... ok
test cob::issue::test::test_embeds ... ok
test cob::issue::test::test_invalid_actions ... ok
test cob::identity::test::test_identity_updates_concurrent_outdated ... ok
test cob::issue::test::test_invalid_tx ... ok
test cob::issue::test::test_invalid_cob ... ok
test cob::issue::test::test_invalid_tx_reference ... ok
test cob::issue::test::test_issue_comment ... ok
test cob::issue::test::test_concurrency ... ok
test cob::issue::test::test_issue_all ... ok
test cob::issue::test::test_issue_comment_redact ... ok
test cob::issue::test::test_issue_create_and_assign ... ok
test cob::issue::test::test_issue_create_and_get ... ok
test cob::issue::test::test_issue_create_and_change_state ... ok
test cob::issue::test::test_issue_create_and_unassign ... ok
test cob::issue::test::test_issue_create_and_reassign ... ok
test cob::issue::test::test_issue_edit ... ok
test cob::issue::test::test_issue_edit_description ... ok
test cob::issue::test::test_issue_multilines ... ok
test cob::issue::test::test_issue_state_serde ... ok
test cob::issue::test::test_ordering ... ok
test cob::issue::test::test_issue_react ... ok
test cob::issue::test::test_issue_label ... ok
test cob::issue::test::test_issue_reply ... ok
test cob::patch::cache::tests::test_is_empty ... ok
test cob::patch::cache::tests::test_get ... ok
test cob::patch::cache::tests::test_counts ... ok
test cob::patch::cache::tests::test_remove ... ok
test cob::patch::test::test_json ... ok
test cob::patch::test::test_json_serialization ... ok
test cob::patch::cache::tests::test_list ... ok
test cob::patch::cache::tests::test_list_by_status ... ok
test cob::patch::test::test_patch_create_and_get ... ok
test cob::patch::test::test_patch_discussion ... ok
test cob::patch::test::test_patch_merge ... ok
test cob::patch::test::test_patch_redact ... ok
test cob::patch::test::test_patch_review ... ok
test cob::patch::test::test_patch_review_comment ... ok
test cob::patch::test::test_patch_review_duplicate ... ok
test cob::patch::test::test_patch_review_edit ... ok
test cob::patch::test::test_patch_review_remove_summary ... ok
test cob::patch::test::test_patch_review_edit_comment ... ok
test cob::patch::test::test_reactions_json_serialization ... ok
test cob::patch::test::test_revision_edit_redact ... ok
test cob::patch::test::test_revision_reaction ... ok
test cob::patch::test::test_revision_review_merge_redacted ... ok
test cob::patch::test::test_patch_review_revision_redact ... ok
test cob::thread::tests::test_comment_edit_missing ... ok
test cob::thread::tests::test_comment_edit_redacted ... ok
test cob::thread::tests::test_comment_redact_missing ... ok
test cob::thread::tests::test_duplicate_comments ... ok
test cob::thread::tests::test_edit_comment ... ok
test cob::thread::tests::test_redact_comment ... ok
test cob::thread::tests::test_timeline ... ok
test cob::patch::test::test_patch_update ... ok
test git::canonical::tests::test_quorum_merges ... ok
test git::canonical::tests::test_quorum ... ok
test git::test::test_version_from_str ... ok
test git::test::test_version_ord ... ok
test identity::did::test::test_did_encode_decode ... ok
test identity::did::test::test_did_vectors ... ok
test identity::doc::id::test::prop_from_str ... ok
test cob::thread::tests::prop_ordering ... ok
test identity::doc::test::test_canonical_doc ... ok
test cob::patch::cache::tests::test_find_by_revision ... ok
test identity::doc::test::test_duplicate_dids ... ok
test identity::doc::test::test_future_version_error ... ok
test identity::doc::test::test_is_valid_version ... ok
test git::canonical::tests::test_quorum_properties ... ok
test identity::doc::test::test_not_found ... ok
test identity::doc::test::test_canonical_example ... ok
test identity::doc::test::test_parse_version ... ok
test identity::project::test::test_project_name ... ok
test node::address::store::test::test_alias ... ok
test identity::doc::test::test_visibility_json ... ok
test node::address::store::test::test_disconnected ... ok
test node::address::store::test::test_disconnected_ban ... ok
test node::address::store::test::test_entries ... ok
test node::address::store::test::test_get_none ... ok
test node::address::store::test::test_insert_and_get ... ok
test node::address::store::test::test_insert_and_remove ... ok
test node::address::store::test::test_insert_and_update ... ok
test node::address::store::test::test_insert_duplicate ... ok
test node::address::store::test::test_node_aliases ... ok
test node::address::store::test::test_remove_nothing ... ok
test node::db::test::test_version ... ok
test node::features::test::test_operations ... ok
test node::notifications::store::test::test_branch_notifications ... ok
test node::notifications::store::test::test_clear ... ok
test node::notifications::store::test::test_cob_notifications ... ok
test node::notifications::store::test::test_counts_by_repo ... ok
test node::notifications::store::test::test_duplicate_notifications ... ok
test node::notifications::store::test::test_notification_status ... ok
test node::policy::store::test::test_follow_and_unfollow_node ... ok
test node::policy::store::test::test_node_aliases ... ok
test node::policy::store::test::test_node_policies ... ok
test node::policy::store::test::test_node_policy ... ok
test node::policy::store::test::test_repo_policies ... ok
test node::address::store::test::test_empty ... ok
test node::policy::store::test::test_seed_and_unseed_repo ... ok
test node::policy::store::test::test_update_alias ... ok
test node::policy::store::test::test_update_scope ... ok
test node::refs::store::test::test_count ... ok
test node::refs::store::test::test_set_and_delete ... ok
test node::policy::store::test::test_repo_policy ... ok
test node::refs::store::test::test_set_and_get ... ok
test node::routing::test::test_count ... ok
test node::routing::test::test_insert_and_get ... ok
test node::routing::test::test_entries ... ok
test node::routing::test::test_insert_and_remove ... ok
test node::routing::test::test_insert_and_get_resources ... ok
test node::routing::test::test_insert_duplicate ... ok
test node::routing::test::test_insert_existing_updated_time ... ok
test node::routing::test::test_len ... ok
test node::routing::test::test_remove_many ... ok
test identity::doc::test::test_max_delegates ... ok
test node::routing::test::test_remove_redundant ... ok
test node::test::test_alias ... ok
test node::test::test_command_result ... ok
test node::test::test_user_agent ... ok
test node::timestamp::tests::test_timestamp_max ... ok
test profile::test::canonicalize_home ... ok
test profile::test::test_config ... ok
test node::routing::test::test_update_existing_multi ... ok
test node::routing::test::test_prune ... ok
test rad::tests::test_init ... ok
test serde_ext::test::test_localtime ... ok
test serde_ext::test::test_localtime_ext ... ok
test rad::tests::test_fork ... ok
test rad::tests::test_checkout ... ok
test storage::git::tests::test_sign_refs ... ok
test storage::git::tests::test_references_of ... ok
test storage::git::transport::local::url::test::test_url_to_string ... ok
test storage::git::transport::remote::url::test::test_url_parse ... ok
test storage::git::transport::local::url::test::test_url_parse ... ok
test storage::refs::tests::prop_canonical_roundtrip ... ok
test storage::tests::test_storage ... ok
test test::assert::test::assert_with_message ... ok
test test::assert::test::test_assert_no_move ... ok
test test::assert::test::test_assert_panic_0 - should panic ... ok
test test::assert::test::test_assert_panic_1 - should panic ... ok
test test::assert::test::test_assert_panic_2 - should panic ... ok
test test::assert::test::test_assert_succeed ... ok
test test::assert::test::test_panic_message ... ok
test version::test::test_version ... ok
test storage::git::tests::test_remote_refs ... ok
test storage::refs::tests::test_rid_verification ... ok
test identity::doc::test::prop_encode_decode ... ok
test result: ok. 171 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.49s
Running unittests src/lib.rs (target/debug/deps/radicle_cli-251748f5cc5248ac)
running 14 tests
test commands::rad_patch::review::builder::tests::test_review_comments_multiline ... ok
test commands::rad_patch::review::builder::tests::test_review_comments_basic ... ok
test commands::rad_inspect::test::test_tree ... ok
test commands::rad_patch::review::builder::tests::test_review_comments_before ... ok
test commands::rad_patch::review::builder::tests::test_review_comments_split_hunk ... ok
test git::pretty_diff::test::test_pretty ... ignored
test git::ddiff::tests::diff_encode_decode_ddiff_hunk ... ok
test git::unified_diff::test::test_diff_content_encode_decode_content ... ok
test git::unified_diff::test::test_diff_encode_decode_diff ... ok
test terminal::format::test::test_bytes ... ok
test terminal::format::test::test_strip_comments ... ok
test terminal::patch::test::test_edit_display_message ... ok
test terminal::patch::test::test_create_display_message ... ok
test terminal::patch::test::test_update_display_message ... ok
test result: ok. 13 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running unittests src/main.rs (target/debug/deps/rad-01072814f956d8cf)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/commands.rs (target/debug/deps/commands-e9a839806588f1b1)
running 96 tests
test framework_home ... ok
test git_push_amend ... ok
test git_push_and_fetch ... ok
test git_push_diverge ... ok
test rad_auth ... ok
test rad_auth_errors ... ok
test rad_block ... ok
test rad_checkout ... ok
test git_tag ... ok
test git_push_rollback ... ok
test rad_clone ... ok
test git_push_converge ... ok
test rad_clean ... ok
test rad_clone_all ... ok
test rad_clone_unknown ... ok
test rad_clone_directory ... ok
test rad_clone_connect ... ok
test rad_cob_multiset ... ok
test rad_cob_log ... ok
test rad_cob_migrate ... ok
test rad_clone_partial_fail ... ok
test rad_cob_update_identity ... ok
test rad_cob_show ... ok
test rad_config ... ok
test rad_cob_update ... ok
test rad_diff ... ok
test rad_id_collaboration ... ignored, slow
test rad_id ... ok
test rad_id_conflict ... ok
test rad_id_private ... ok
test rad_id_multi_delegate ... ok
test rad_id_threshold ... ok
test rad_id_unknown_field ... ok
test rad_id_update_delete_field ... ok
test rad_id_threshold_soft_fork ... ok
test rad_init ... ok
test rad_init_existing ... ok
test rad_init_no_git ... ok
test rad_init_no_seed ... ok
test rad_init_private ... ok
test rad_inbox ... ok
test rad_init_private_clone ... ok
test rad_init_private_no_seed ... ok
test rad_fork ... ok
test rad_fetch ... ok
test rad_init_private_clone_seed ... ok
test rad_init_private_seed ... ok
test rad_init_sync_not_connected ... ok
test rad_init_sync_preferred ... ok
test rad_init_with_existing_remote ... ok
test rad_inspect ... ok
test rad_issue ... ok
test rad_job ... ok
test rad_merge_after_update ... ok
test rad_merge_no_ff ... ok
test rad_merge_via_push ... ok
test rad_node_connect ... ok
test rad_patch ... ok
test rad_patch_ahead_behind ... ok
test rad_node ... ok
test rad_patch_change_base ... ok
test rad_patch_checkout ... ok
test rad_patch_checkout_revision ... ok
test rad_patch_checkout_force ... ok
test rad_patch_detached_head ... ok
test rad_patch_diff ... ok
test rad_init_sync_timeout ... ok
test rad_init_sync_and_clone ... ok
test rad_patch_draft ... ok
test rad_patch_edit ... ok
test rad_patch_fetch_2 ... ok
test rad_patch_merge_draft ... ok
test rad_patch_delete ... ok
test rad_patch_fetch_1 ... ok
test rad_patch_revert_merge ... ok
test rad_patch_update ... ok
test rad_patch_open_explore ... ok
test rad_patch_via_push ... ok
test rad_publish ... ok
test rad_review_by_hunk ... ok
test rad_seed_and_follow ... ok
test rad_seed_many ... ok
test rad_self ... ok
test rad_remote ... ok
test rad_sync_without_node ... ok
test rad_push_and_pull_patches ... ok
test rad_unseed ... ok
test rad_unseed_many ... ok
test rad_watch ... ok
test rad_sync ... ok
test test_clone_without_seeds ... ok
test test_cob_deletion ... ok
test test_cob_replication ... ok
test rad_workflow ... ok
test rad_patch_pull_update ... ok
test test_replication_via_seed ... ok
test result: ok. 95 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 72.77s
Running unittests src/lib.rs (target/debug/deps/radicle_cli_test-019416717c61773a)
running 3 tests
test tests::test_parse ... ok
test tests::test_run ... ok
test tests::test_example_spaced_brackets ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/radicle_cob-51e2cb672c60c662)
running 8 tests
test object::tests::test_serde ... ok
test tests::invalid_parse_refstr ... ok
test tests::parse_refstr ... ok
test tests::roundtrip ... ok
test type_name::test::valid_typenames ... ok
test tests::update_cob ... ok
test tests::traverse_cobs ... ok
test tests::list_cobs ... ok
test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
Running unittests src/lib.rs (target/debug/deps/radicle_crdt-7e149ccf4e56bbfc)
running 24 tests
test immutable::test::test_merge_equal ... ok
test immutable::test::test_merge_inequal - should panic ... ok
test lwwmap::tests::test_insert ... ok
test lwwmap::tests::test_insert_remove ... ok
test lwwmap::tests::test_is_empty ... ok
test lwwmap::tests::test_remove_insert ... ok
test lwwreg::tests::prop_semilattice ... ok
test lwwreg::tests::test_merge ... ok
test lwwreg::tests::test_set_get ... ok
test gmap::tests::prop_semilattice ... ok
test lwwset::tests::test_insert ... ok
test lwwset::tests::test_insert_remove ... ok
test lwwset::tests::test_remove_insert ... ok
test redactable::test::prop_invariants ... ok
test redactable::test::test_both_present ... ok
test redactable::test::test_redacted ... ok
test test::test_generator ... ok
test tests::prop_bool_laws ... ok
test tests::prop_option_laws ... ok
test tests::test_bool ... ok
test tests::test_option ... ok
test lwwset::tests::prop_semilattice ... ok
test gset::tests::prop_semilattice ... ok
test lwwmap::tests::prop_semilattice ... ok
test result: ok. 24 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s
Running unittests src/lib.rs (target/debug/deps/radicle_crypto-636ff488446c56b4)
running 12 tests
test ssh::fmt::test::test_key ... ok
test ssh::fmt::test::test_fingerprint ... ok
test ssh::keystore::tests::test_init_no_passphrase ... ok
test ssh::test::prop_encode_decode_sk ... ok
test ssh::test::test_agent_encoding_remove ... ok
test ssh::test::test_agent_encoding_sign ... ok
test tests::prop_encode_decode ... ok
test tests::test_e25519_dh ... ok
test tests::test_encode_decode ... ok
test tests::prop_key_equality ... ok
test ssh::keystore::tests::test_signer ... ok
test ssh::keystore::tests::test_init_passphrase ... ok
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.81s
Running unittests src/lib.rs (target/debug/deps/radicle_dag-569b0a6854e86d64)
running 20 tests
test tests::test_cycle ... ok
test tests::test_dependencies ... ok
test tests::test_contains ... ok
test tests::test_diamond ... ok
test tests::test_complex ... ok
test tests::test_fold_diamond ... ok
test tests::test_fold_multiple_roots ... ok
test tests::test_fold_reject ... ok
test tests::test_fold_sorting_1 ... ok
test tests::test_get ... ok
test tests::test_fold_sorting_2 ... ok
test tests::test_is_empty ... ok
test tests::test_len ... ok
test tests::test_merge_2 ... ok
test tests::test_merge_1 ... ok
test tests::test_prune_1 ... ok
test tests::test_prune_2 ... ok
test tests::test_prune_by_sorting ... ok
test tests::test_remove ... ok
test tests::test_siblings ... ok
test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/radicle_fetch-05fcd46c986cb4cd)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/radicle_node-a9151bc25a4f93b5)
running 113 tests
test control::tests::test_control_socket ... ok
test deserializer::test::prop_decode_next ... ok
test deserializer::test::test_decode_next ... ok
test deserializer::test::test_unparsed ... ok
test service::filter::test::test_parameters ... ok
test service::limiter::test::test_limitter_different_rates ... ok
test service::limiter::test::test_limitter_multi ... ok
test service::gossip::store::test::test_announced ... ok
test service::limiter::test::test_limitter_refill ... ok
test service::filter::test::test_sizes ... ok
test service::message::tests::test_inventory_limit ... ok
test control::tests::test_seed_unseed ... ok
test service::message::tests::test_ref_remote_limit ... ok
test service::message::tests::prop_refs_announcement_signing ... ok
test tests::e2e::missing_default_branch ... ok
test tests::e2e::missing_delegate_default_branch ... ok
test tests::e2e::test_background_foreground_fetch ... ok
test tests::e2e::test_channel_reader_limit ... ok
test tests::e2e::test_catchup_on_refs_announcements ... ok
test tests::e2e::test_clone ... ok
test service::message::tests::test_node_announcement_validate ... ok
test tests::e2e::test_dont_fetch_owned_refs ... ok
test tests::e2e::test_fetch_followed_remotes ... ok
test tests::e2e::test_connection_crossing ... ok
test tests::e2e::test_fetch_preserve_owned_refs ... ok
test tests::e2e::test_fetch_unseeded ... ok
test tests::e2e::test_concurrent_fetches ... ok
test tests::e2e::test_inventory_sync_basic ... ok
test tests::e2e::test_fetch_up_to_date ... ok
test tests::e2e::test_large_fetch ... ok
test tests::e2e::test_migrated_clone ... ok
test tests::e2e::test_missing_remote ... ok
test tests::e2e::test_multiple_offline_inits ... ok
test tests::e2e::test_non_fastforward_sigrefs ... ok
test tests::e2e::test_outdated_delegate_sigrefs ... ok
test tests::e2e::test_outdated_sigrefs ... ok
test tests::e2e::test_replication ... ok
test tests::e2e::test_replication_invalid ... ok
test tests::e2e::test_replication_ref_in_sigrefs ... ok
test tests::e2e::test_inventory_sync_bridge ... ok
test tests::e2e::test_inventory_sync_ring ... ok
test tests::test_announcement_rebroadcast ... ok
test tests::e2e::test_inventory_sync_star ... ok
test tests::test_announcement_rebroadcast_duplicates ... ok
test tests::test_announcement_rebroadcast_timestamp_filtered ... ok
test tests::test_connection_kept_alive ... ok
test tests::test_announcement_relay ... ok
test tests::test_disconnecting_unresponsive_peer ... ok
test tests::test_fetch_missing_inventory_on_gossip ... ok
test tests::test_fetch_missing_inventory_on_schedule ... ok
test tests::test_inbound_connection ... ok
test tests::test_inventory_decode ... ok
test tests::test_init_and_seed ... ok
test tests::test_inventory_relay ... ok
test tests::test_inventory_relay_bad_timestamp ... ok
test tests::test_inventory_sync ... ok
test tests::test_maintain_connections ... ok
test tests::test_maintain_connections_failed_attempt ... ok
test tests::test_maintain_connections_transient ... ok
test tests::test_outbound_connection ... ok
test tests::test_persistent_peer_connect ... ok
test tests::test_inventory_pruning ... ok
test tests::test_persistent_peer_reconnect_success ... ok
test tests::test_persistent_peer_reconnect_attempt ... ok
test tests::test_ping_response ... ok
test tests::test_queued_fetch_from_ann_same_rid ... ok
test tests::test_queued_fetch_from_command_same_rid ... ok
test tests::test_queued_fetch_max_capacity ... ok
test tests::test_redundant_connect ... ok
test tests::test_refs_announcement_followed ... ok
test tests::test_refs_announcement_fetch_trusted_no_inventory ... ok
test tests::test_refs_announcement_no_subscribe ... ok
test tests::test_refs_announcement_offline ... ok
test tests::test_refs_announcement_relay_private ... ok
test tests::test_refs_announcement_relay_public ... ok
test tests::test_refs_synced_event ... ok
test tests::test_seeding ... ok
test wire::frame::test::test_stream_id ... ok
test wire::message::tests::prop_addr ... ok
test tests::test_seed_repo_subscribe ... ok
test wire::message::tests::prop_message_encode_decode ... ok
test wire::message::tests::prop_zero_bytes_encode_decode ... ok
test wire::message::tests::test_inv_ann_max_size ... ok
test wire::message::tests::test_node_ann_max_size ... ok
test wire::message::tests::test_pingpong_encode_max_size ... ok
test wire::message::tests::test_pingpong_encode_size_overflow ... ok
test tests::prop_inventory_exchange_dense ... ok
test wire::protocol::test::test_inventory_ann_with_extension ... ok
test wire::protocol::test::test_pong_message_with_extension ... ok
test wire::tests::prop_filter ... ok
test wire::tests::prop_id ... ok
test wire::tests::prop_oid ... ok
test wire::message::tests::test_refs_ann_max_size ... ok
test wire::tests::prop_refs ... ok
test wire::tests::prop_signature ... ok
test wire::tests::prop_pubkey ... ok
test wire::tests::prop_string ... ok
test wire::tests::prop_tuple ... ok
test wire::tests::prop_u16 ... ok
test wire::tests::prop_u32 ... ok
test wire::tests::prop_u64 ... ok
test wire::tests::prop_u8 ... ok
test wire::tests::prop_signed_refs ... ok
test wire::tests::test_alias ... ok
test wire::tests::test_bounded_vec_limit ... ok
test wire::tests::test_filter_invalid ... ok
test wire::tests::test_string ... ok
test wire::varint::test::prop_encode_decode ... ok
test wire::varint::test::test_encode_overflow - should panic ... ok
test wire::varint::test::test_encoding ... ok
test wire::tests::prop_vec ... ok
test wire::message::tests::prop_message_decoder ... ok
test tests::test_announcement_message_amplification ... ok
test result: ok. 113 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 16.96s
Running unittests src/main.rs (target/debug/deps/radicle_node-4820984cdb5701fb)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/radicle_remote_helper-ec4b110573be437b)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/git-remote-rad.rs (target/debug/deps/git_remote_rad-6e1e6e7941f73781)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/radicle_signals-85fbd454a3bf0ac6)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/radicle_ssh-9b2e5dfee304f504)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/radicle_systemd-1b4159ebc8f1d2b1)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/radicle_term-96f7925768df62cb)
running 20 tests
test ansi::tests::colors_disabled ... ok
test cell::test::test_width ... ok
test ansi::tests::colors_enabled ... ok
test ansi::tests::wrapping ... ok
test element::test::test_width ... ok
test element::test::test_truncate ... ok
test table::test::test_table ... ok
test table::test::test_table_border_maximized ... ok
test table::test::test_table_border ... ok
test table::test::test_table_border_truncated ... ok
test table::test::test_table_truncate ... ok
test table::test::test_table_unicode ... ok
test table::test::test_truncate ... ok
test table::test::test_table_unicode_truncate ... ok
test textarea::test::test_wrapping ... ok
test textarea::test::test_wrapping_fenced_block ... ok
test textarea::test::test_wrapping_code_block ... ok
test textarea::test::test_wrapping_paragraphs ... ok
test vstack::test::test_vstack_maximize ... ok
test vstack::test::test_vstack ... ok
test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/rad-agent.rs (target/debug/deps/rad_agent-ebde29902276e4db)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/rad-cli-demo.rs (target/debug/deps/rad_cli_demo-b4888d9c25fd0e5a)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/rad-init.rs (target/debug/deps/rad_init-247a667ed33dcb00)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/rad-merge.rs (target/debug/deps/rad_merge-02c7555617034abf)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/rad-push.rs (target/debug/deps/rad_push-3ff35bc35af21c62)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/rad-self.rs (target/debug/deps/rad_self-b8e8c77c09a2ee71)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/rad-set-canonical-refs.rs (target/debug/deps/rad_set_canonical_refs-81969bf711bec567)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle_cli
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle_cli_test
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle_cob
running 1 test
test radicle-cob/src/backend/git/stable.rs - backend::git::stable::with_advanced_timestamp (line 56) ... ignored
test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle_crdt
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle_crypto
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle_dag
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle_fetch
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle_node
running 6 tests
test radicle-node/src/bounded.rs - bounded::BoundedVec<T,N>::max (line 96) ... ok
test radicle-node/src/bounded.rs - bounded::BoundedVec<T,N>::truncate (line 50) ... ok
test radicle-node/src/bounded.rs - bounded::BoundedVec<T,N>::collect_from (line 30) ... ok
test radicle-node/src/bounded.rs - bounded::BoundedVec<T,N>::unbound (line 149) ... ok
test radicle-node/src/bounded.rs - bounded::BoundedVec<T,N>::with_capacity (line 66) ... ok
test radicle-node/src/bounded.rs - bounded::BoundedVec<T,N>::push (line 122) ... ok
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.61s
Doc-tests radicle_remote_helper
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle_signals
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle_ssh
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle_systemd
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests radicle_term
running 1 test
test radicle-term/src/table.rs - table (line 4) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.40s
Exit code: 0
{
"response": "finished",
"result": "success"
}