rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 heartwood34014a67b0ddc859d95e17ffc71c1ae61aff5758
{
"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": "c54883e5ffb1f8a99f432e3ac79c0b728cd0dab3",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"title": "Canonical References",
"state": {
"status": "archived",
"conflicts": []
},
"before": "59a1021410a28b635d86cde34da75766204f2f7e",
"after": "34014a67b0ddc859d95e17ffc71c1ae61aff5758",
"commits": [
"34014a67b0ddc859d95e17ffc71c1ae61aff5758",
"0e0b77e4e2249fff3f5e3478ca0fd4677cd039ed",
"bbe019c478fc1f5c1f6417efe780a4b703284932",
"b3ad6f2d5ce0f68896aee1472acbc03ebb2c2b2f",
"04277b4b3012c7e7a3cb56db176bebe9cc898a25",
"312c6a4fb6de9caf7ec0e052ea8592a65607a425"
],
"target": "3fc92f83fe089263c470b328414d923227cf0e4a",
"labels": [],
"assignees": [
"did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz"
],
"revisions": [
{
"id": "c54883e5ffb1f8a99f432e3ac79c0b728cd0dab3",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "See RIP-0004[^0] for the specification.\n\nThis patch is an implementation of RIP-0004. It implements the rules mechanism\nwithin the `rules` module. This is interplays with the existing `canonical`\nmechanisms, already defined (but slightly refactored).\n\nThe `rules` are then used in pushing and fetching references. A test is added to\nillustrate the canonical references in action via tags.\n\nThere were some incidental changes that were made to ensure the tags use case is\neasy for users. The first change was to add a tags refspec to remotes in order\nto easily fetch tags from peers -- as well ensuring those tags do not pollute\nthe `refs/tags` namespace in the working copy.\n\nThis had a knock on change where there was a bug `libgit2` that didn't allow for\ndeleting `multivar` entries, which the new remote setup fell under. This was\nfixed and so we update to `git2-0.19`.\n\nAs well this, the `rad id update` command would error if the payload identifier\nwas not the project identifier. This would stop adding new payloads to extend\nthe identity -- which was needed for introducing canonical references.\n\n[^0]: https://app.radicle.xyz/nodes/seed.radicle.garden/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/patches/1d1ce874f7c39ecdcd8c318bbae46ffd02fe1ea8?tab=changes",
"base": "54551b117435a006acddefbf3850b02d6ee84ffa",
"oid": "3e978375aea43528b0ed72d94ca86458ae46d602",
"timestamp": 1721297970
},
{
"id": "c1a2cc5787f44c0a835c1deae375be04c399dd7e",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Major Update\n\nSee commits for all details, but basically it was the yak shave of a lifetime.",
"base": "c8fbcf2a7aeb0dc8b5c2f709532ae464fedc3677",
"oid": "58e932ce964e1bbe9eb148203f3e125bbab3cca1",
"timestamp": 1725375337
},
{
"id": "c55494efc2e780cd6c91a1f90efdae8a3eb1c7ef",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Remove use of `--tags` flag in `git-tag` test\n- Use `EntryKey` instead of `ObjectId` in `ChangeGraph` to simplify\n getting the root entry. This means that `timestamp_of` is only used\n in the `GraphBuilder`.",
"base": "c8fbcf2a7aeb0dc8b5c2f709532ae464fedc3677",
"oid": "1b07774def93c135e5f43367de672a36aafab4dd",
"timestamp": 1727169095
},
{
"id": "583e6b3366c36cc7e67910c29a66750397a60484",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Introduce `VersionGroupId`\n- Update test SHAs\n- Handle ordering case in identity test, where we expect the commit to\n happen later (uses unsafe and manipulating env var)\n- Introduce migration code for `Doc`s\n- Introduce `rad id migrate`",
"base": "c8fbcf2a7aeb0dc8b5c2f709532ae464fedc3677",
"oid": "fdd52771765ae0b19a057da61b69034b188f84e3",
"timestamp": 1728573242
},
{
"id": "d54ddef216909bdd3e54e33e4f82c45df79c00d3",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Rebased on [Patch 52f287f50c5d62c815bf3878b398b708f60c9b46](https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/patches/52f287f50c5d62c815bf3878b398b708f60c9b46?tab=activity)\n- Removed `VersionGroupId``",
"base": "0c9a7419dcfd265b6c1efe85c915d126feaef649",
"oid": "f24f9d89018bf3b074032e68bc77a0dc6aa5c1e1",
"timestamp": 1729873668
},
{
"id": "ac48ae6e75d4eaa13daed657eed24dfeabb9be94",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Rebased on c7f00b1c\n- Missing commit for incorporating rules into identity doc",
"base": "c7f00b1c6cebd2adeb1738f061990bd131804ca7",
"oid": "7d8e4616ebe40839401562a2ad8f98ec8ddcef9e",
"timestamp": 1730283252
},
{
"id": "2b31e460db7451376dc3e346ee02b5fd597fa5c6",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Fixups in documentation and tests",
"base": "c7f00b1c6cebd2adeb1738f061990bd131804ca7",
"oid": "040cfb712250461a1878293e02f47e2cb9694233",
"timestamp": 1730286464
},
{
"id": "e1c360a1311a0a215bed6eb42e4b0c8c5c44e611",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Disallow `rad/sigrefs` rule\n- Add test for failing validation with `rad/id` and `rad/sigrefs`",
"base": "c7f00b1c6cebd2adeb1738f061990bd131804ca7",
"oid": "f0dec88a89d65d0bdae8c422e9f3211554b7206a",
"timestamp": 1731322110
},
{
"id": "492cfbafd31e4bac85ee73af519ddc6254b47f82",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Rename delegates field and type to `allow` and `Allowed`\n- Rename token from `$identity` to `delegates`\n- Use `BTreeMap`s instead of `Vec`.\n * This required rethinking using `QualifiedPattern`, and instead using a\n newtype, `Pattern`, that implemented `Ord` by specificity.\n * `Pattern` also disqualifies any use of `refs/rad`\n- Add `From<Did> for doc::Delegates` to infallibly construct the set with a\n single `Did`.\n- Changes `extensions` to a JSON map\n- Derive `Deserialize` for `Rule`, but only via the private `Sealed` trait\n- Changed the `FnMut` resolve callback to be `Fn`\n- Required updating all the `radicle-cli` tests\n- Added other QoL API improvements",
"base": "0c9a7419dcfd265b6c1efe85c915d126feaef649",
"oid": "f9cb27f65430abbbc549521240054e3e0297cb53",
"timestamp": 1738339341
},
{
"id": "fbdf18d7683bdac7a76149777eed5cf9bbbf5bd5",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Rebased on master @ 3b5fac178eaf9bca639fbd0c1df0c68619a7f51f",
"base": "3b5fac178eaf9bca639fbd0c1df0c68619a7f51f",
"oid": "2a6475510a438726189500457d33887872785366",
"timestamp": 1738750291
},
{
"id": "4baf32afd65f2c4b374d8f21fed6877aa804a003",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Document `RawCanonicalRefs` and `CanonicalRefs`",
"base": "3b5fac178eaf9bca639fbd0c1df0c68619a7f51f",
"oid": "0cecae660273ab0c31f481d9078b2d93f0a78965",
"timestamp": 1738750973
},
{
"id": "d2ebc70caca54a8ba508d72862c1e1c79d718129",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Rebased\n- Rename `canonical-ref` to `cref`\n- Order enum variant in `canonical-ref.rs`\n- Fix wording in example tests\n- Avoid removing `rad-config` tests\n- Use `saturating_sub` for `threshold` calculation in `radicle-fetch`",
"base": "7eb07e1b4d57ddb4660d0363691877fe893af044",
"oid": "4515d452ab9ca812825572e38700b5be5eec21ae",
"timestamp": 1744810962
},
{
"id": "13e9ba641c624db26b6bfe85870daf064f90e9ab",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Rebased",
"base": "f30760d6bb86d2978a5ed4df8ee45b9aa97778b4",
"oid": "045e4654b281379a968b6b8a8f50639c3d29f2fe",
"timestamp": 1745855085
},
{
"id": "47495c408ccf5eec49b61c7bdb339e5f2d695a30",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Stricter version parsing\n- Using `VersionedRawDoc` to enumerate each version of the `RawDoc`s\n- Singleton approach for version numbers in the docs\n- Better renaming of CLI cref module\n- Fix docs for `rad id migrate`",
"base": "f30760d6bb86d2978a5ed4df8ee45b9aa97778b4",
"oid": "a6be35592e8c78100b76da9832d19ffff035ed21",
"timestamp": 1746037637
},
{
"id": "e3bdb65d3adb94360dd3449744792f6ecb1f451f",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Remove `MatchedRule`\n- Refactoring of `Canonical`\n- General fixes",
"base": "f30760d6bb86d2978a5ed4df8ee45b9aa97778b4",
"oid": "8d0821554a3ce0f26e36dd4c85206d987e1ff2e6",
"timestamp": 1746548066
},
{
"id": "9f779028704b4c022cbe25c0e4a9bb46dc8463ba",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Addressed erik's feedback",
"base": "f30760d6bb86d2978a5ed4df8ee45b9aa97778b4",
"oid": "49fcea75c04936fe3cdaf7e81c7577833fa66bfc",
"timestamp": 1746617713
},
{
"id": "86ebfcaaf986edba5e77ede1be4d3c4ce33bd27c",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Fixed `cargo doc` errors\n- Fixed cli test errors",
"base": "f30760d6bb86d2978a5ed4df8ee45b9aa97778b4",
"oid": "2df7cd9af4ffd1e929a9c3e33c682c7e59ee3950",
"timestamp": 1746627996
},
{
"id": "fa9bdff35d76903f72cf24f1cccca812ae26e98c",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Rebased",
"base": "59a1021410a28b635d86cde34da75766204f2f7e",
"oid": "34014a67b0ddc859d95e17ffc71c1ae61aff5758",
"timestamp": 1746628627
}
]
}
}
{
"response": "triggered",
"run_id": {
"id": "26e38854-c259-4494-8c47-30a5dd6aea1a"
},
"info_url": "https://cci.rad.levitte.org//26e38854-c259-4494-8c47-30a5dd6aea1a.html"
}
Started at: 2026-09-08 03:33:48.107716+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/26e38854-c259-4494-8c47-30a5dd6aea1a/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 189 issues · 38 patches │
╰────────────────────────────────────╯
Run `cd ./.` to go to the repository directory.
Exit code: 0
$ rad patch checkout c54883e5ffb1f8a99f432e3ac79c0b728cd0dab3
✓ Switched to branch patch/c54883e at revision fa9bdff
✓ Branch patch/c54883e setup to track rad/patches/c54883e5ffb1f8a99f432e3ac79c0b728cd0dab3
Exit code: 0
$ git config advice.detachedHead false
Exit code: 0
$ git checkout 34014a67b0ddc859d95e17ffc71c1ae61aff5758
HEAD is now at 34014a67 radicle: refactor rule matching
Exit code: 0
$ rad patch show c54883e5ffb1f8a99f432e3ac79c0b728cd0dab3 -p
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Title Canonical References │
│ Patch c54883e5ffb1f8a99f432e3ac79c0b728cd0dab3 │
│ Author fintohaps z6Mkire…SQZ3voM │
│ Head 34014a67b0ddc859d95e17ffc71c1ae61aff5758 │
│ Base 59a1021410a28b635d86cde34da75766204f2f7e │
│ Target master │
│ Branches patch/c54883e │
│ Commits ahead 6, behind 1076 │
│ Status archived │
│ │
│ See RIP-0004[^0] for the specification. │
│ │
│ This patch is an implementation of RIP-0004. It implements the rules mechanism │
│ within the `rules` module. This is interplays with the existing `canonical` │
│ mechanisms, already defined (but slightly refactored). │
│ │
│ The `rules` are then used in pushing and fetching references. A test is added to │
│ illustrate the canonical references in action via tags. │
│ │
│ There were some incidental changes that were made to ensure the tags use case is │
│ easy for users. The first change was to add a tags refspec to remotes in order │
│ to easily fetch tags from peers -- as well ensuring those tags do not pollute │
│ the `refs/tags` namespace in the working copy. │
│ │
│ This had a knock on change where there was a bug `libgit2` that didn't allow for │
│ deleting `multivar` entries, which the new remote setup fell under. This was │
│ fixed and so we update to `git2-0.19`. │
│ │
│ As well this, the `rad id update` command would error if the payload identifier │
│ was not the project identifier. This would stop adding new payloads to extend │
│ the identity -- which was needed for introducing canonical references. │
│ │
│ [^0]: │
│ https://app.radicle.xyz/nodes/seed.radicle.garden/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/patches/1d1ce874f7c39ecdcd8c318bbae46ffd02fe1ea8?tab=changes │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ 34014a6 radicle: refactor rule matching │
│ 0e0b77e radicle: add canonical refs to identity │
│ bbe019c radicle: canonical reference rules │
│ b3ad6f2 radicle: refactor Canonical │
│ 04277b4 radicle: store threshold in Canonical │
│ 312c6a4 meta: relax radicle-git dependencies │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ● Revision c54883e @ 54551b1..3e97837 by fintohaps z6Mkire…SQZ3voM 2 years ago │
│ ↑ Revision c1a2cc5 @ c8fbcf2..58e932c by fintohaps z6Mkire…SQZ3voM 2 years ago │
│ ↑ Revision c55494e @ c8fbcf2..1b07774 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision 583e6b3 @ c8fbcf2..fdd5277 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision d54ddef @ 0c9a741..f24f9d8 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision ac48ae6 @ c7f00b1..7d8e461 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision 2b31e46 @ c7f00b1..040cfb7 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision e1c360a @ c7f00b1..f0dec88 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision 492cfba @ 0c9a741..f9cb27f by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision fbdf18d @ 3b5fac1..2a64755 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision 4baf32a @ 3b5fac1..0cecae6 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ └─ ⋄ reviewed by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ ↑ Revision d2ebc70 @ 7eb07e1..4515d45 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision 13e9ba6 @ f30760d..045e465 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision 47495c4 @ f30760d..a6be355 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision e3bdb65 @ f30760d..8d08215 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ └─ ⋄ reviewed by erikli z6MkgFq…FGAnBGz 1 year ago │
│ ↑ Revision 9f77902 @ f30760d..49fcea7 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision 86ebfca @ f30760d..2df7cd9 by fintohaps z6Mkire…SQZ3voM 1 year ago │
│ ↑ Revision fa9bdff @ 59a1021..34014a6 by fintohaps z6Mkire…SQZ3voM 1 year ago │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
commit 34014a67b0ddc859d95e17ffc71c1ae61aff5758
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Tue May 6 17:07:15 2025 +0100
radicle: refactor rule matching
This change improves the API for rule matching by providing a method that
produces a `Canonical` directly from the set of rules – as opposed to going
through the previous `MatchedRule` struct. `Canonical` now carries its
`ValidRule`, which means that invalid states regarding threshold and the
allowed set of DIDs are unrepresentable.
It also provides a method `matches` that returns all matching `Pattern`s. This
allows us to provide the CLI subcommand, `rad cref match` that shows which
rules match for a given reference.
diff --git a/radicle-cli/examples/git/git-push-canonical-tags.md b/radicle-cli/examples/git/git-push-canonical-tags.md
index d01a563ff..b073d611e 100644
--- a/radicle-cli/examples/git/git-push-canonical-tags.md
+++ b/radicle-cli/examples/git/git-push-canonical-tags.md
@@ -125,8 +125,7 @@ no quorum was found for the new tag:
``` ~bob (stderr)
$ git tag v2.0
$ git push rad --tags
-warn: could not determine canonical tip for `refs/tags/v2.0`
-warn: no commit found with at least 2 vote(s) (threshold not met)
+warn: could not determine tip for canonical reference 'refs/tags/v2.0', no commit with at least 2 vote(s) found (threshold not met)
warn: it is recommended to find a commit to agree upon
✓ Synced with 1 node(s)
To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
diff --git a/radicle-cli/examples/git/git-push-converge.md b/radicle-cli/examples/git/git-push-converge.md
index b7088382e..49920a0fc 100644
--- a/radicle-cli/examples/git/git-push-converge.md
+++ b/radicle-cli/examples/git/git-push-converge.md
@@ -93,8 +93,7 @@ commit:
``` ~alice (stderr)
$ git push rad -f
-warn: could not determine canonical tip for `refs/heads/master`
-warn: no commit found with at least 3 vote(s) (threshold not met)
+warn: could not determine tip for canonical reference 'refs/heads/master', no commit with at least 3 vote(s) found (threshold not met)
warn: it is recommended to find a commit to agree upon
✓ Synced with 2 node(s)
To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
diff --git a/radicle-cli/examples/rad-cref.md b/radicle-cli/examples/rad-cref.md
index 5c93bbc39..444487d6e 100644
--- a/radicle-cli/examples/rad-cref.md
+++ b/radicle-cli/examples/rad-cref.md
@@ -73,6 +73,19 @@ $ rad cref add refs/tags/releases/* --title "Add canonical reference rule for re
✓ Identity revision a880b05441f00cc90bc7bae76e0f1ef16b73daf1 created
```
+We can test that our rule matches against the reference name `refs/tags/releases/v1.2`
+as expected:
+
+```
+$ rad cref match refs/tags/releases/v1.2
+{
+ "refs/tags/releases/*": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+}
+```
+
Here, we didn't specify the `--allow` or `--threshold` options. This means
the defaults of `delegates` and `1` will be used. So let's check that this is
true:
@@ -123,6 +136,13 @@ $ rad cref
}
```
+Let's also check that the refname `refs/heads/weird` does not match any rules:
+
+```
+$ rad cref match refs/heads/weird
+{}
+```
+
The `refspec` value must be a fully-qualified reference name. That is, it must
start with `refs`, followed by the category (e.g. `refs/tags`), and the suffix.
Here we show that it will fail if this is not the case:
diff --git a/radicle-cli/src/commands/cref.rs b/radicle-cli/src/commands/cref.rs
index 6466520fd..91df219e4 100644
--- a/radicle-cli/src/commands/cref.rs
+++ b/radicle-cli/src/commands/cref.rs
@@ -9,6 +9,8 @@ use radicle::cob::identity::Identity;
use radicle::git::canonical::rules;
use radicle::git::canonical::rules::Rule;
use radicle::git::canonical::RawRule;
+use radicle::git::canonical::Rules;
+use radicle::git::Qualified;
use radicle::identity::Did;
use radicle::identity::IdentityMut;
use radicle::identity::RawDoc;
@@ -34,11 +36,13 @@ Usage
rad cref add <refspec> [--allow <did>..] [--threshold <num>] [<option>...]
rad cref edit <refspec> [--allow <did>..] [--threshold <num>] [<option>...]
rad cref remove <refspec> [<option>...]
+ rad cref match <refname>
The *rad cref* command is used to manage the rules for setting
canonical references in the Radicle repository.
- The *list* command lists all the rules associated with the repository.
+ The *list* command lists all the rules associated with the repository that
+ match the given *refname*.
The *add* command will add the rule for the given *refspec*.
@@ -46,6 +50,10 @@ Usage
The *remove* command will remove the rule for the given *refspec*.
+ The *match* command evaluates all rules against given reference name,
+ it prints the set of matched rules, in order of precedence, i.e. the
+ first rule printed is the one that applies to the given reference name.
+
Add/Edit options
--allow <did> DID to be used for the canonical reference rule.
@@ -77,6 +85,9 @@ pub enum Operation {
threshold: usize,
},
List,
+ Match {
+ refname: Qualified<'static>,
+ },
Remove {
title: Option<String>,
description: Option<String>,
@@ -90,6 +101,7 @@ pub enum OperationName {
Edit,
#[default]
List,
+ Match,
Remove,
}
@@ -106,6 +118,7 @@ impl Args for Options {
let mut parser = lexopt::Parser::from_args(args);
let mut op: Option<OperationName> = None;
let mut pattern: Option<rules::Pattern> = None;
+ let mut refname: Option<Qualified<'static>> = None;
let mut allow: Vec<Did> = Vec::new();
let mut threshold: Option<usize> = None;
let mut rid: Option<RepoId> = None;
@@ -120,10 +133,15 @@ impl Args for Options {
"e" | "edit" => op = Some(OperationName::Edit),
"r" | "remove" => op = Some(OperationName::Remove),
"l" | "list" => op = Some(OperationName::List),
+ "m" | "match" => op = Some(OperationName::Match),
unknown => anyhow::bail!("unknown operation '{}'", unknown),
},
+ Value(val) if matches!(op, Some(OperationName::Match)) => {
+ refname = Some(term::args::qualified_refstring(val)?);
+ }
+
Value(val)
if matches!(
op,
@@ -203,6 +221,9 @@ impl Args for Options {
pattern: pattern.ok_or_else(|| anyhow!("a refspec must be provided"))?,
},
OperationName::List => Operation::List,
+ OperationName::Match => Operation::Match {
+ refname: refname.ok_or_else(|| anyhow!("a refname must be provided"))?,
+ },
};
Ok((Options { op, rid, quiet }, vec![]))
@@ -283,7 +304,19 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
)?;
}
Operation::List => {
- print_rules(current.rules())?;
+ if !options.quiet {
+ print_rules(current.rules())?;
+ }
+ }
+ Operation::Match { refname } => {
+ if !options.quiet {
+ print_rules(&Rules::from_iter(
+ current
+ .rules()
+ .matches(&refname)
+ .map(|(pattern, rule)| (pattern.clone(), rule.clone())),
+ ))?;
+ }
}
}
Ok(())
diff --git a/radicle-cli/src/commands/id.rs b/radicle-cli/src/commands/id.rs
index 8cf36d525..f60024d6d 100644
--- a/radicle-cli/src/commands/id.rs
+++ b/radicle-cli/src/commands/id.rs
@@ -735,12 +735,11 @@ fn verify_project_delegates<S>(proposal: &RawDoc, current: &Doc, repo: &S) -> an
where
S: ReadRepository,
{
- let rule = current.default_branch_rule()?;
- let threshold = rule.map(|rule| *rule.threshold()).ok_or(anyhow::anyhow!(
- "failed to find canonical ref rule for default branch"
- ))?;
+ let Some(threshold) = current.default_branch_threshold()? else {
+ anyhow::bail!("failed to find threshold of canonical ref rule for default branch");
+ };
- if let Some(errs) = verify_delegates(&proposal.delegates, threshold.into(), repo)? {
+ if let Some(errs) = verify_delegates(&proposal.delegates, threshold, repo)? {
term::error(format!("failed to verify delegates for {}", repo.rid()));
term::error(format!(
"the threshold of {} delegates cannot be met..",
diff --git a/radicle-cli/src/terminal/args.rs b/radicle-cli/src/terminal/args.rs
index b3012df98..398a3daea 100644
--- a/radicle-cli/src/terminal/args.rs
+++ b/radicle-cli/src/terminal/args.rs
@@ -108,6 +108,17 @@ pub fn refstring(flag: &str, value: OsString) -> anyhow::Result<RefString> {
})
}
+pub fn qualified_refstring(value: OsString) -> anyhow::Result<radicle::git::Qualified<'static>> {
+ let refstring = RefString::try_from(
+ value
+ .into_string()
+ .map_err(|_| anyhow!("the value specified is not valid UTF-8"))?,
+ )
+ .map_err(|_| anyhow!("the value specified is not a valid ref string",))?;
+ radicle::git::Qualified::from_refstr(refstring)
+ .ok_or_else(|| anyhow!("the value specified is not a valid qualified ref string",))
+}
+
pub fn qualified_pattern_string(value: OsString) -> anyhow::Result<QualifiedPattern<'static>> {
let value = value
.into_string()
diff --git a/radicle-fetch/src/state.rs b/radicle-fetch/src/state.rs
index a899c7428..fdf623534 100644
--- a/radicle-fetch/src/state.rs
+++ b/radicle-fetch/src/state.rs
@@ -405,10 +405,7 @@ impl FetchState {
// The threshold is only relevant to the default branch, if there is
// none we set the threshold to 0 so that validation succeeds.
- let threshold: usize = anchor
- .default_branch_rule()?
- .map(|rule| (*rule.threshold()).into())
- .unwrap_or(0);
+ let threshold: usize = anchor.default_branch_threshold()?.unwrap_or(0);
// The local peer does not need to count towards the threshold
// since they must be valid already.
let threshold = if is_delegate {
diff --git a/radicle-node/src/worker/fetch.rs b/radicle-node/src/worker/fetch.rs
index 9c2ebb990..eeb0ae178 100644
--- a/radicle-node/src/worker/fetch.rs
+++ b/radicle-node/src/worker/fetch.rs
@@ -90,8 +90,6 @@ impl Handle {
remote: PublicKey,
refs_at: Option<Vec<RefsAt>>,
) -> Result<FetchResult, error::Fetch> {
- use git::canonical::QuorumError::{Diverging, NoCandidates};
-
let (result, clone, notifs) = match self {
Self::Clone { mut handle, tmp } => {
log::debug!(target: "worker", "{} cloning from {remote}", handle.local());
@@ -146,10 +144,10 @@ impl Handle {
log::trace!(target: "worker", "Set HEAD to {}", head.new);
}
}
- Err(RepositoryError::Quorum(Diverging(e))) => {
- log::warn!(target: "worker", "Fetch could not set HEAD: {e}")
+ Err(RepositoryError::Quorum(radicle::git::canonical::QuorumError::Git(e))) => {
+ return Err(e.into())
}
- Err(RepositoryError::Quorum(NoCandidates(e))) => {
+ Err(RepositoryError::Quorum(e)) => {
log::warn!(target: "worker", "Fetch could not set HEAD: {e}")
}
Err(e) => return Err(e.into()),
@@ -389,39 +387,59 @@ fn set_canonical_refs(repo: &Repository, applied: &Applied) -> Result<(), error:
if rules.is_empty() {
return Ok(());
}
- let matches = applied.updated.iter().filter_map(|r| match r {
- RefUpdate::Updated { name, .. } | RefUpdate::Created { name, .. } => {
- let name = name.clone().into_qualified()?;
- let name = name.to_namespaced()?.strip_namespace();
- rules.matches(name)
- }
- RefUpdate::Deleted { .. } | RefUpdate::Skipped { .. } => None,
- });
- for matched in matches {
- let Ok(canonical) = matched.canonical(repo) else {
- log::warn!(target: "worker", "Failed to get canonical tips for {}", matched.refname());
+
+ for update in applied.updated.iter() {
+ let name = match update {
+ RefUpdate::Updated { name, .. } | RefUpdate::Created { name, .. } => name,
+ _ => {
+ log::trace!(target: "worker", "Skipping update {update}");
+ continue;
+ }
+ };
+ let Some(name) = name.clone().into_qualified() else {
+ log::warn!(target: "worker", "Skipping update for canonical reference '{name}' because it is not qualified.");
continue;
};
- let Ok(oid) = canonical.quorum(&repo.backend) else {
- log::warn!(
- target: "worker",
- "Failed to calculate canonical tip for {}",
- matched.refname()
- );
+ let Some(name) = name.to_namespaced() else {
+ log::warn!(target: "worker", "Skipping update for canonical reference '{name}' because it is not namespaced.");
continue;
};
- if let Err(e) = repo.backend.reference(
- matched.refname().as_str(),
- *oid,
- true,
- "set-canonical-reference from fetch (radicle)",
- ) {
- log::warn!(
- target: "worker",
- "Failed to set canonical tip for {}->{}: {e}",
- matched.refname(),
- oid
- );
+
+ let name = name.strip_namespace();
+
+ let canonical = match identity.rules().canonical(name.clone(), repo) {
+ Ok(Some(canonical)) => canonical,
+ Ok(None) => continue,
+ Err(e) => {
+ log::warn!(target: "worker", "Failed to get canonical tips for {name}: {e}");
+ continue;
+ }
+ };
+
+ match canonical.quorum(&repo.backend) {
+ Err(err) => {
+ log::warn!(
+ target: "worker",
+ "Failed to calculate canonical tip: {}",
+ err,
+ );
+ continue;
+ }
+ Ok((refname, oid)) => {
+ if let Err(e) = repo.backend.reference(
+ refname.clone().as_str(),
+ *oid,
+ true,
+ "set-canonical-reference from fetch (radicle)",
+ ) {
+ log::warn!(
+ target: "worker",
+ "Failed to set canonical tip for {}->{}: {e}",
+ refname,
+ oid
+ );
+ }
+ }
}
}
Ok(())
diff --git a/radicle-remote-helper/src/push.rs b/radicle-remote-helper/src/push.rs
index fb61aed0c..a56c8d945 100644
--- a/radicle-remote-helper/src/push.rs
+++ b/radicle-remote-helper/src/push.rs
@@ -273,28 +273,21 @@ pub fn run(
// Note that we *do* allow rolling back to a previous commit on the
// canonical branch.
- if let Some(rule) = rules.matches(dst.clone()) {
- if rule.allowed().contains(&me) {
- let mut canonical = rule.canonical(stored)?;
+ if let Some(mut canonical) = rules.canonical(dst.clone(), stored)? {
+ if canonical.is_allowed(&me) {
let head = working.find_reference(src.as_str())?;
let head = head.peel_to_commit()?.id();
- let converges = canonical::converges(
- canonical
- .tips()
- .filter_map(|(did, tip)| (*did != me).then_some(tip)),
- head.into(),
- &working,
- )?;
+ let converges =
+ canonical.converges(&working, (&me, &head.into()))?;
// If `canonical` is empty then we're creating a new reference.
// If we're the only delegate then we need to modify our vote.
- if converges || canonical.is_empty() || rule.allowed().is_only(&me)
- {
+ if converges || canonical.has_no_tips() || canonical.is_only(&me) {
canonical.modify_vote(me, head.into());
}
match canonical.quorum(&working) {
- Ok(canonical_oid) => {
+ Ok((dst, canonical_oid)) => {
// Canonical head is an ancestor of head.
let is_ff = head == *canonical_oid
|| working.graph_descendant_of(head, *canonical_oid)?;
@@ -302,13 +295,13 @@ pub fn run(
if !is_ff && !converges {
if hints {
hint(
- format!("you are attempting to push a commit that would cause \
- your upstream to diverge from the canonical reference {dst}"),
- );
+ format!("you are attempting to push a commit that would cause \
+ your upstream to diverge from the canonical reference {dst}"),
+ );
hint(
- "to integrate the remote changes, run `git pull --rebase` \
- and try again",
- );
+ "to integrate the remote changes, run `git pull --rebase` \
+ and try again",
+ );
}
return Err(Error::HeadsDiverge(
head.into(),
@@ -318,21 +311,11 @@ pub fn run(
set_canonical_refs
.push((dst.clone().to_owned(), canonical_oid));
}
- Err(canonical::QuorumError::Diverging(e)) => {
- warn(format!(
- "could not determine canonical tip for `{dst}`"
- ));
+ Err(canonical::QuorumError::Git(e)) => return Err(e.into()),
+ Err(e) => {
warn(e.to_string());
warn("it is recommended to find a commit to agree upon");
}
- Err(canonical::QuorumError::NoCandidates(e)) => {
- warn(format!(
- "could not determine canonical tip for `{dst}`"
- ));
- warn(e.to_string());
- warn("it is recommended to find a commit to agree upon");
- }
- Err(e) => return Err(e.into()),
}
}
}
diff --git a/radicle/src/cob/patch.rs b/radicle/src/cob/patch.rs
index ff1b6c503..a55fd4f80 100644
--- a/radicle/src/cob/patch.rs
+++ b/radicle/src/cob/patch.rs
@@ -1080,8 +1080,7 @@ impl Patch {
);
{
- if let Some(rule) = identity.default_branch_rule()? {
- let threshold = (*rule.rule().threshold()).into();
+ if let Some(threshold) = identity.default_branch_threshold()? {
// Discard revisions that weren't merged by a threshold of delegates.
merges.retain(|_, count| *count >= threshold);
}
diff --git a/radicle/src/git/canonical.rs b/radicle/src/git/canonical.rs
index 2b40b5052..dc0a2aeb0 100644
--- a/radicle/src/git/canonical.rs
+++ b/radicle/src/git/canonical.rs
@@ -1,127 +1,65 @@
pub mod rules;
-pub use rules::{MatchedRule, RawRule, Rules, ValidRule};
+pub use rules::{RawRule, Rules, ValidRule};
use std::collections::BTreeMap;
-use std::fmt;
-use nonempty::NonEmpty;
use raw::Repository;
use thiserror::Error;
use crate::prelude::Did;
-use crate::prelude::Project;
use crate::storage::ReadRepository;
use super::raw;
-use super::{lit, Oid, Qualified};
+use super::{Oid, Qualified};
/// A collection of [`Did`]s and their [`Oid`]s that is the tip for a given
/// reference for that [`Did`].
///
/// The general construction of `Canonical` is by using the
-/// [`Canonical::reference`] constructor. For the default branch of a
-/// [`Project`], use [`Canonical::default_branch`].
+/// [`Canonical::new`] constructor.
///
/// `Canonical` can then be used for performing calculations about the
/// canonicity of the reference, most importantly the [`Canonical::quorum`].
+///
+/// References to the refname and the matched rule are kept, as they
+/// are very handy for generating error messages.
#[derive(Debug)]
-pub struct Canonical {
+pub struct Canonical<'a, 'b> {
+ refname: Qualified<'a>,
+ rule: &'b ValidRule,
tips: BTreeMap<Did, Oid>,
- threshold: usize,
}
/// Error that can occur when calculation the [`Canonical::quorum`].
#[derive(Debug, Error)]
pub enum QuorumError {
/// Could not determine a quorum [`Oid`], due to diverging tips.
- #[error("could not determine canonical reference tip, {0}")]
- Diverging(Diverging),
+ #[error("could not determine tip for canonical reference '{refname}', found diverging commits {longest} and {head}, with base commit {base} and threshold {threshold}")]
+ Diverging {
+ refname: String,
+ threshold: usize,
+ base: Oid,
+ longest: Oid,
+ head: Oid,
+ },
/// Could not determine a base candidate from the given set of delegates.
- #[error("could not determine canonical reference tip, {0}")]
- NoCandidates(NoCandidates),
+ #[error("could not determine tip for canonical reference '{refname}', no commit with at least {threshold} vote(s) found (threshold not met)")]
+ NoCandidates { refname: String, threshold: usize },
/// An error occurred from [`git2`].
#[error(transparent)]
Git(#[from] git2::Error),
}
-/// No candidates were found for the [`Canonical::quorum`] calculation.
-///
-/// The [`fmt::Display`] is used in [`QuorumError`], to provide information on
-/// the threshold and delegates in the calculation.
-#[derive(Debug)]
-pub struct NoCandidates {
- threshold: usize,
-}
-
-impl fmt::Display for NoCandidates {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- let NoCandidates { threshold } = self;
- write!(
- f,
- "no commit found with at least {threshold} vote(s) (threshold not met)"
- )
- }
-}
-
-/// Diverging commits were found during the [`Canonical::quorum`] calculation.
-///
-/// The [`fmt::Display`] is used in [`QuorumError`], to provide information on
-/// the threshold, base commit, and the two diverging commits, in the
-/// calculation.
-#[derive(Debug)]
-pub struct Diverging {
- threshold: usize,
- base: Oid,
- longest: Oid,
- head: Oid,
-}
-
-impl fmt::Display for Diverging {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- let Diverging {
- threshold,
- base,
- longest,
- head,
- } = self;
- write!(f, "found diverging commits {longest} and {head}, with base commit {base} and threshold {threshold}")
- }
-}
-
-impl Canonical {
- /// Construct the set of canonical tips of the `Project::default_branch` for
- /// the given `delegates`.
- pub fn default_branch<S>(
- repo: &S,
- project: &Project,
- delegates: &NonEmpty<Did>,
- threshold: usize,
- ) -> Result<Self, raw::Error>
- where
- S: ReadRepository,
- {
- Self::reference(
- repo,
- &lit::refs_heads(project.default_branch()).into(),
- delegates,
- threshold,
- )
- }
-
- /// Construct the set of canonical tips given for the given `delegates` and
- /// the reference `name`.
- pub fn reference<S>(
- repo: &S,
- refname: &Qualified,
- delegates: &NonEmpty<Did>,
- threshold: usize,
- ) -> Result<Self, raw::Error>
+impl<'a, 'b> Canonical<'a, 'b> {
+ /// Construct the set of canonical tips given for the given `rule` and
+ /// the reference `refname`.
+ pub fn new<S>(repo: &S, refname: Qualified<'a>, rule: &'b ValidRule) -> Result<Self, raw::Error>
where
S: ReadRepository,
{
let mut tips = BTreeMap::new();
- for delegate in delegates.iter() {
- match repo.reference_oid(delegate, refname) {
+ for delegate in rule.allowed().iter() {
+ match repo.reference_oid(delegate, &refname) {
Ok(tip) => {
tips.insert(*delegate, tip);
}
@@ -134,7 +72,11 @@ impl Canonical {
Err(e) => return Err(e),
}
}
- Ok(Canonical { tips, threshold })
+ Ok(Canonical {
+ refname,
+ tips,
+ rule,
+ })
}
/// Return the set of [`Did`]s and their [`Oid`] tip.
@@ -142,36 +84,18 @@ impl Canonical {
self.tips.iter()
}
- /// Returns `true` is there were no tips found for any of the delegates for
+ /// Returns `true` if there were no tips found for any of the DIDs for
/// the given reference.
///
/// N.b. this may be the case when a new reference is being created.
- pub fn is_empty(&self) -> bool {
+ pub fn has_no_tips(&self) -> bool {
self.tips.is_empty()
}
-}
-/// Check that a given `target` converges with any of the provided `tips`.
-///
-/// It converges if the `target` is either equal to, ahead of, or behind any of
-/// the tips.
-pub fn converges<'a>(
- tips: impl Iterator<Item = &'a Oid>,
- target: Oid,
- repo: &Repository,
-) -> Result<bool, raw::Error> {
- for tip in tips {
- match repo.graph_ahead_behind(*target, **tip)? {
- (0, 0) => return Ok(true),
- (ahead, behind) if ahead > 0 && behind == 0 => return Ok(true),
- (ahead, behind) if behind > 0 && ahead == 0 => return Ok(true),
- (_, _) => {}
- }
+ pub fn refname(&self) -> &Qualified {
+ &self.refname
}
- Ok(false)
-}
-impl Canonical {
/// In some cases, we allow the vote to be modified. For example, when the
/// `did` is pushing a new commit, we may want to see if the new commit will
/// reach a quorum.
@@ -179,6 +103,41 @@ impl Canonical {
self.tips.insert(did, new);
}
+ /// Check that the provided `did` is part of the set of allowed
+ /// DIDs of the matching rule.
+ pub fn is_allowed(&self, did: &Did) -> bool {
+ self.rule.allowed().contains(did)
+ }
+
+ /// Check that the provided `did` is the only DID in the set of allowed
+ /// DIDs of the matching rule.
+ pub fn is_only(&self, did: &Did) -> bool {
+ self.rule.allowed().is_only(did)
+ }
+
+ /// Checks that setting the given candidate tip would converge with at least
+ /// one other known tip.
+ ///
+ /// It converges if the candidate Oid is either equal to, ahead of, or behind any of
+ /// the tips.
+ pub fn converges(
+ &self,
+ repo: &Repository,
+ candidate: (&Did, &Oid),
+ ) -> Result<bool, raw::Error> {
+ for tip in self
+ .tips
+ .iter()
+ .filter_map(|(did, tip)| (did != candidate.0).then_some(tip))
+ {
+ let (ahead, behind) = repo.graph_ahead_behind(**candidate.1, **tip)?;
+ if ahead * behind == 0 {
+ return Ok(true);
+ }
+ }
+ Ok(false)
+ }
+
/// Computes the quorum or "canonical" tip based on the tips, of `Canonical`,
/// and the threshold. This can be described as the latest commit that is
/// included in at least `threshold` histories. In case there are multiple tips
@@ -186,7 +145,7 @@ impl Canonical {
///
/// Also returns an error if `heads` is empty or `threshold` cannot be
/// satisified with the number of heads given.
- pub fn quorum(self, repo: &raw::Repository) -> Result<Oid, QuorumError> {
+ pub fn quorum(self, repo: &raw::Repository) -> Result<(Qualified<'a>, Oid), QuorumError> {
let mut candidates = BTreeMap::<_, usize>::new();
// Build a list of candidate commits and count how many "votes" each of them has.
@@ -211,14 +170,12 @@ impl Canonical {
}
}
// Keep commits which pass the threshold.
- candidates.retain(|_, votes| *votes >= self.threshold);
+ candidates.retain(|_, votes| *votes >= self.threshold());
- let (mut longest, _) =
- candidates
- .pop_first()
- .ok_or(QuorumError::NoCandidates(NoCandidates {
- threshold: self.threshold,
- }))?;
+ let (mut longest, _) = candidates.pop_first().ok_or(QuorumError::NoCandidates {
+ refname: self.refname.to_string(),
+ threshold: self.threshold(),
+ })?;
// Now that all scores are calculated, figure out what is the longest branch
// that passes the threshold. In case of divergence, return an error.
@@ -252,15 +209,20 @@ impl Canonical {
// o (base)
// |
//
- return Err(QuorumError::Diverging(Diverging {
- threshold: self.threshold,
+ return Err(QuorumError::Diverging {
+ refname: self.refname.to_string(),
+ threshold: self.threshold(),
base: base.into(),
longest,
head: *head,
- }));
+ });
}
}
- Ok((*longest).into())
+ Ok((self.refname, (*longest).into()))
+ }
+
+ fn threshold(&self) -> usize {
+ (*self.rule.threshold()).into()
}
}
@@ -281,7 +243,7 @@ mod tests {
threshold: usize,
repo: &git::raw::Repository,
) -> Result<Oid, QuorumError> {
- let tips = heads
+ let tips: BTreeMap<Did, Oid> = heads
.iter()
.enumerate()
.map(|(i, head)| {
@@ -290,7 +252,25 @@ mod tests {
(did, (*head).into())
})
.collect();
- Canonical { tips, threshold }.quorum(repo)
+
+ let refname =
+ git::refs::branch(git_ext::ref_format::RefStr::try_from_str("master").unwrap());
+
+ let rule: RawRule = crate::git::canonical::rules::Rule::new(
+ crate::git::canonical::rules::Allowed::Delegates,
+ threshold,
+ );
+ let rule = rule
+ .validate(&mut |_| Ok(crate::identity::doc::Delegates::new(tips.keys().cloned())?))
+ .unwrap();
+
+ Canonical {
+ refname,
+ tips,
+ rule: &rule,
+ }
+ .quorum(repo)
+ .map(|(_, oid)| oid)
}
#[test]
@@ -352,9 +332,6 @@ mod tests {
assert_eq!(quorum(&[*c0], 1, &repo).unwrap(), c0);
assert_eq!(quorum(&[*c1], 1, &repo).unwrap(), c1);
assert_eq!(quorum(&[*c2], 1, &repo).unwrap(), c2);
- assert_eq!(quorum(&[*c0], 0, &repo).unwrap(), c0);
- assert_matches!(quorum(&[], 0, &repo), Err(QuorumError::NoCandidates(_)));
- assert_matches!(quorum(&[*c0], 2, &repo), Err(QuorumError::NoCandidates(_)));
// C1
// |
@@ -380,23 +357,23 @@ mod tests {
// C0
assert_matches!(
quorum(&[*c1, *c2, *b2], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*c2, *b2], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*b2, *c2], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*c2, *b2], 2, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
assert_matches!(
quorum(&[*b2, *c2], 2, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
assert_eq!(quorum(&[*c1, *c2, *b2], 2, &repo).unwrap(), c1);
assert_eq!(quorum(&[*c1, *c2, *b2], 3, &repo).unwrap(), c1);
@@ -404,7 +381,7 @@ mod tests {
assert_eq!(quorum(&[*b2, *c2, *c2], 2, &repo).unwrap(), c2);
assert_matches!(
quorum(&[*b2, *b2, *c2, *c2], 2, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
// B2 C2 C3
@@ -415,15 +392,15 @@ mod tests {
assert_eq!(quorum(&[*b2, *c2, *c2], 2, &repo).unwrap(), c2);
assert_matches!(
quorum(&[*b2, *c2, *c2], 3, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
assert_matches!(
quorum(&[*b2, *c2, *b2, *c2], 3, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
assert_matches!(
quorum(&[*c3, *b2, *c2, *b2, *c2, *c3], 3, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
// B2 C2
@@ -433,19 +410,19 @@ mod tests {
// C0
assert_matches!(
quorum(&[*c2, *b2, *a1], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*c2, *b2, *a1], 2, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
assert_matches!(
quorum(&[*c2, *b2, *a1], 3, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
assert_matches!(
quorum(&[*c1, *c2, *b2, *a1], 4, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
assert_eq!(quorum(&[*c0, *c1, *c2, *b2, *a1], 2, &repo).unwrap(), c1,);
assert_eq!(quorum(&[*c0, *c1, *c2, *b2, *a1], 3, &repo).unwrap(), c1,);
@@ -453,23 +430,23 @@ mod tests {
assert_eq!(quorum(&[*c0, *c1, *c2, *b2, *a1], 4, &repo).unwrap(), c0,);
assert_matches!(
quorum(&[*a1, *a1, *c2, *c2, *c1], 2, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*a1, *a1, *c2, *c2, *c1], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*a1, *a1, *c2], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*b2, *b2, *c2, *c2], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*b2, *b2, *c2, *c2, *a1], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
// M2 M1
@@ -482,27 +459,27 @@ mod tests {
assert_eq!(quorum(&[*m1], 1, &repo).unwrap(), m1);
assert_matches!(
quorum(&[*m1, *m2], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*m2, *m1], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*m1, *m2], 2, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
assert_matches!(
quorum(&[*m1, *m2, *c2], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*m1, *a1], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*m1, *a1], 2, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
assert_eq!(quorum(&[*m1, *m2, *b2, *c1], 4, &repo).unwrap(), c1);
assert_eq!(quorum(&[*m1, *m1, *b2], 2, &repo).unwrap(), m1);
@@ -541,11 +518,11 @@ mod tests {
// C0
assert_matches!(
quorum(&[*m1, *m2], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*m1, *m2], 2, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
let m3 = fixtures::commit("M3", &[*c2, *c1], &repo);
@@ -557,27 +534,27 @@ mod tests {
// C0
assert_matches!(
quorum(&[*m1, *m3], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*m1, *m3], 2, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
assert_matches!(
quorum(&[*m3, *m1], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*m3, *m1], 2, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
assert_matches!(
quorum(&[*m3, *m2], 1, &repo),
- Err(QuorumError::Diverging(_))
+ Err(QuorumError::Diverging { .. })
);
assert_matches!(
quorum(&[*m3, *m2], 2, &repo),
- Err(QuorumError::NoCandidates(_))
+ Err(QuorumError::NoCandidates { .. })
);
}
}
diff --git a/radicle/src/git/canonical/rules.rs b/radicle/src/git/canonical/rules.rs
index 4cd612de6..f8593e398 100644
--- a/radicle/src/git/canonical/rules.rs
+++ b/radicle/src/git/canonical/rules.rs
@@ -486,47 +486,6 @@ impl std::ops::Deref for ResolvedDelegates {
}
}
-/// A reference that has been matched against a [`ValidRule`].
-///
-/// Can be constructed by using [`Rules::matches`].
-#[derive(Debug)]
-pub struct MatchedRule<'a> {
- refname: Qualified<'a>,
- rule: ValidRule,
-}
-
-impl MatchedRule<'_> {
- /// Return the reference name that was used for checking if it was a match.
- pub fn refname(&self) -> &Qualified {
- &self.refname
- }
-
- /// Return the rule that was matched.
- pub fn rule(&self) -> &ValidRule {
- &self.rule
- }
-
- /// Return the allowed DIDs for the matched rule.
- pub fn allowed(&self) -> &doc::Delegates {
- self.rule().allowed()
- }
-
- /// Return the [`doc::Threshold`] for the matched rule.
- pub fn threshold(&self) -> &doc::Threshold {
- self.rule().threshold()
- }
-
- /// Return the [`Canonical`] representation for the matched rule.
- pub fn canonical(&self, repo: &Repository) -> Result<Canonical, git::raw::Error> {
- Canonical::reference(
- repo,
- &self.refname,
- self.rule.allow.as_ref(),
- self.rule.threshold.into(),
- )
- }
-}
-
/// A set of valid [`Rule`]s, where the set of DIDs and threshold are fully
/// resolved and valid. Since the rules are constructed via a `BTreeMap`, they
/// cannot be duplicated.
@@ -608,18 +567,32 @@ impl Rules {
Ok(Self { rules: valid })
}
- /// Return the first matching rule for the given `refname`, if there is any.
+ /// Return the matching rules for the given `refname`.
+ pub fn matches<'a>(
+ &self,
+ refname: &Qualified<'a>,
+ ) -> impl Iterator<Item = (&Pattern, &ValidRule)> + use<'a, '_> {
+ let refname_cloned = refname.clone();
+ self.rules
+ .iter()
+ .filter(move |(pattern, _)| pattern.matches(&refname_cloned))
+ }
+
+ /// Match given refname, take the most specific rule, and prepare evaluation
+ /// as [`Canonical`]
///
/// N.b. it will find the first rule that is most specific for the given
/// `refname`.
- pub fn matches<'a>(&self, refname: Qualified<'a>) -> Option<MatchedRule<'a>> {
- self.rules
- .iter()
- .find(|(pattern, _)| pattern.matches(&refname))
- .map(|(_, rule)| MatchedRule {
- refname,
- rule: rule.clone(),
- })
+ pub fn canonical<'a, 'b>(
+ &'a self,
+ refname: Qualified<'b>,
+ repo: &Repository,
+ ) -> Result<Option<Canonical<'b, 'a>>, git::raw::Error> {
+ if let Some((_, rule)) = self.matches(&refname).next() {
+ Ok(Some(Canonical::new(repo, refname, rule)?))
+ } else {
+ Ok(None)
+ }
}
}
@@ -1184,19 +1157,21 @@ mod tests {
// candidates tag.
let stored = storage.repository(rid).unwrap();
let failing = git::Qualified::from(git::lit::refs_tags(failing_tag));
- for (refname, oid) in tags.iter() {
- let matched = rules.matches(refname.clone()).unwrap_or_else(|| {
- panic!("there should be a matching rule for {refname}, rules: {rules:#?}")
- });
- let canonical = matched.canonical(&stored).unwrap();
- if *refname == failing {
+ for (refname, oid) in tags.into_iter() {
+ let canonical = rules
+ .canonical(refname.clone(), &stored)
+ .unwrap()
+ .unwrap_or_else(|| {
+ panic!("there should be a matching rule for {refname}, rules: {rules:#?}")
+ });
+ if refname == failing {
assert!(canonical.quorum(&repo).is_err());
} else {
assert_eq!(
canonical
.quorum(&repo)
.unwrap_or_else(|e| panic!("quorum error for {refname}: {e}")),
- *oid,
+ (refname, oid),
)
}
}
diff --git a/radicle/src/identity/doc.rs b/radicle/src/identity/doc.rs
index ce444ddd5..1b7f08011 100644
--- a/radicle/src/identity/doc.rs
+++ b/radicle/src/identity/doc.rs
@@ -25,7 +25,7 @@ use crate::crypto;
use crate::crypto::Signature;
use crate::git;
use crate::git::canonical::rules;
-use crate::git::canonical::rules::{MatchedRule, RawRules, Rule};
+use crate::git::canonical::rules::{RawRules, Rule};
use crate::git::canonical::Rules;
use crate::identity::{project::Project, Did};
use crate::storage;
@@ -795,20 +795,21 @@ impl Doc {
&self.canonical_refs.rules
}
- /// Return the canonical reference rule for the identity's default branch,
- /// if the repository has a [`Doc::project`] associated with it.
- pub fn default_branch_rule(&self) -> Result<Option<MatchedRule>, PayloadError> {
- match self.project() {
- Ok(project) => Ok(self.rule_of(git::refs::branch(project.default_branch()).clone())),
+ pub fn default_branch_threshold(&self) -> Result<Option<usize>, PayloadError> {
+ let Some(refname) = match self.project() {
+ Ok(project) => Ok(Some(git::refs::branch(project.default_branch()))),
Err(e) if e.is_not_found() => Ok(None),
Err(e) => Err(e),
- }
- }
-
- /// Get the matching rule, if present, from the identity's canonical
- /// reference rules.
- pub fn rule_of<'a>(&self, refname: git::Qualified<'a>) -> Option<MatchedRule<'a>> {
- self.rules().matches(refname)
+ }?
+ else {
+ return Ok(None);
+ };
+
+ Ok(self
+ .rules()
+ .matches(&refname)
+ .next()
+ .map(|(_, rule)| (*rule.threshold()).into()))
}
/// Check whether this document and the associated repository is visible to
diff --git a/radicle/src/storage/git.rs b/radicle/src/storage/git.rs
index bab494712..9f4418f37 100644
--- a/radicle/src/storage/git.rs
+++ b/radicle/src/storage/git.rs
@@ -743,11 +743,12 @@ impl ReadRepository for Repository {
fn canonical_head(&self) -> Result<(Qualified, Oid), RepositoryError> {
let doc = self.identity_doc()?;
- let rule = doc
- .default_branch_rule()?
- .ok_or(RepositoryError::MissingBranchRule)?;
- let oid = rule.canonical(self)?.quorum(self.raw())?;
- Ok((rule.refname().clone().to_owned(), oid))
+ let refname = git::refs::branch(doc.project()?.default_branch());
+ Ok(doc
+ .rules()
+ .canonical(refname, self)?
+ .ok_or(RepositoryError::MissingBranchRule)?
+ .quorum(self.raw())?)
}
fn identity_head(&self) -> Result<Oid, RepositoryError> {
commit 0e0b77e4e2249fff3f5e3478ca0fd4677cd039ed
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Tue Aug 20 12:43:12 2024 +0100
radicle: add canonical refs to identity
The main change of this patch is adding canonical reference rules to the identity
document. Since this is a core piece to the stack it does come with a lot of
consequential changes.
A large majority of changes come from the fact that changing the contents of the
identity document changes the resulting SHAs of the identity. This has a knock
on effect on RIDs in the tests, and SHAs of COBs since they store the identity
SHA alongside their data. Other changes in the CLI example tests include:
- Having the `canonicalRefs` field included in the identity output
- Removal of `threshold` field in the identity output
- Some copy changes for canonical references when pushing
The change to the `RawDoc` and `Doc` types follows from the Canonical References
RIP. The `RawRules` and `Rules` types are added to each, respectively. Since the
`threshold` is not used anymore, this is dropped from `Doc` and `RawDoc` – in
the case of the version 2 document.
For compatibility, if the document is version 1 and the repository is a project,
the verified `Doc` will use the existing `threshold` to ensure that there is a
rule for the default branch – which consists of the delegates and the threshold.
A test is introduced to try to ensure that migration from the old document
format to the latest document format is compatible. It does this by hardcoding
the old document value and seeing that it can deserialize into the latest
`RawDoc` and `Doc` types. Note that this should future-proof any further
changes, since this test will fail if any non-backwards-compatible changes are
made.
By removing the threshold from the identity, the threshold for a project had to
be obtained from the canonical reference rules. This is done by using a helper,
`default_branch_rule`, for getting the threshold if it exists. For
`canonical_head` this means that it will fail it could not find the rule.
Within `radicle-fetch` it means that verification using the threshold only
occurs when the threshold is available. Similarly, in `rad id`, the threshold
for verification only applies for project repositories and is skipped otherwise.
Following from removing `threshold`, this meant that some tests that used `rad
id update --threshold` needed to be changed. To do this a new command `rad
canonical-ref` was added to manage canonical reference rules. Some code was
extracted from `commands/id.rs` into a common module that could be reused across
both `commands/id.rs` and `commands/canonical-ref.rs`. Finally, the
`--threshold` option was removed since it was no longer relevant.
The more functional part of the work for using the canonical reference rules
happens in `radicle-node/src/worker/fetch.rs` and
`radicle-remote-helper/src/push.rs`. In the former, the updated references are
iterated over to check if they match any of the given rules in an identity. If a
match is found, the canonical reference is calculated and set. In the latter,
when a reference is pushed, the canonical references are also checked for a
match and the canonical reference is similarly calculated and set. Note that
this code is very similar to original default branch code, while augmenting it
for the rule matching.
An example test, `git-push-canonical-tags`, is added to demonstrate how
canonical reference rules can be used for tags -- which is deemed as one of the
important use cases of this feature.
One change of note is that the `rad-id-threshold` test no longer made any sense
to test with how the CLI works -- see the FIXME comment for more details. It is
ignored for the time being.
diff --git a/rad-id.1.adoc b/rad-id.1.adoc
index 91ab86430..daecde571 100644
--- a/rad-id.1.adoc
+++ b/rad-id.1.adoc
@@ -18,7 +18,8 @@ rad-id - Manage changes to a Radicle repository's identity.
*rad id* _edit_ <revision-id> [--title <string>] [--description <string>] [<option>...] +
*rad id* _show_ <revision-id> [<option>...] +
*rad id* _accept_ | _reject_ <revision-id> [<option>...] +
-*rad id* _redact_ <revision-id> [<option>...]
+*rad id* _redact_ <revision-id> [<option>...] +
+*rad id* _migrate_ [--from <version>] [<option>...]
== Description
@@ -89,6 +90,27 @@ automatically accepted and included into the identity document.
*--edit*::
Opens your $EDITOR to edit the JSON contents directly.
+=== migrate
+
+Propose a migration revision to the identity document. The migration will
+automatically convert the current identity document into the latest version,
+if possible.
+
+If a title and description are not provided on the command line, you will be
+prompted to enter one via your text editor.
+
+Note that if you are the repository's only delegate, the migration changes will
+be automatically accepted and included into the identity document.
+
+*--title* _<string>_::
+ Set the title for the new revision.
+
+*--description* _<string>_::
+ Set the description for the new revision.
+
+*--no-confirm*::
+ Don't ask for confirmation before creating the revision.
+
=== edit
Edit an existing revision to the identity document. The revision must still be
diff --git a/rad-patch.1.adoc b/rad-patch.1.adoc
index 269ae129a..bbce15cc0 100644
--- a/rad-patch.1.adoc
+++ b/rad-patch.1.adoc
@@ -278,7 +278,7 @@ example, if some patch *26e3e56* is ready to merge, the steps would be:
$ git merge patch/26e3e56
$ git push rad
✓ Patch 26e3e563ddc7df8dd0c9f81274c0b3cb1b764568 merged
- To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ To rad://z3oVNYXUagaa4L4HqKzTDppHBA1Jo/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..d6399c7 master -> master
In the above, we created a checkout for the patch, and merged that branch into
diff --git a/radicle-cli/examples/git/git-fetch.md b/radicle-cli/examples/git/git-fetch.md
index 652254b7f..bf62b32c9 100644
--- a/radicle-cli/examples/git/git-fetch.md
+++ b/radicle-cli/examples/git/git-fetch.md
@@ -13,7 +13,7 @@ f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
``` (stderr)
$ git fetch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
-From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+From rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new branch] alice/1 -> alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/alice/1
```
diff --git a/radicle-cli/examples/git/git-push-amend.md b/radicle-cli/examples/git/git-push-amend.md
index a547341d6..7a6b9182f 100644
--- a/radicle-cli/examples/git/git-push-amend.md
+++ b/radicle-cli/examples/git/git-push-amend.md
@@ -1,11 +1,11 @@
``` ~alice
-$ rad id update --title "Add Bob" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji -q
-c036c0d89ce26aef3ad7da402157dba16b5163b4
+$ rad id update --title "Add Bob" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 -q
+f48a2c516aceccde576d9ba8845b21eca1f7902c
```
``` ~bob
$ rad sync --fetch
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
```
@@ -20,8 +20,8 @@ $ git commit --amend -m "Neue Änderungen" --allow-empty -q
``` ~alice (stderr)
$ git push rad master -f
-✓ Canonical head updated to 9170c8795d3a78f0381a0ffafb20ea69fb0f5b6b
+✓ Canonical head for refs/heads/master updated to 9170c8795d3a78f0381a0ffafb20ea69fb0f5b6b
✓ Synced with 1 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ fb25886...9170c87 master -> master (forced update)
```
diff --git a/radicle-cli/examples/git/git-push-canonical-tags.md b/radicle-cli/examples/git/git-push-canonical-tags.md
new file mode 100644
index 000000000..d01a563ff
--- /dev/null
+++ b/radicle-cli/examples/git/git-push-canonical-tags.md
@@ -0,0 +1,175 @@
+In this example, we will show how we can make other references become canonical.
+To illustrate, we will use Git tags as an example. The storage of the repository
+should look something like this by the end of the example:
+
+~~~
+storage/z6cFWeWpnZNHh9rUW8phgA3b5yGt/refs
+├── heads
+│ └── main
+├── namespaces
+│ ├── z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+│ │ └── refs
+│ │ ├── cobs
+│ │ │ └── xyz.radicle.id
+│ │ │ └── 865c48204bd7bb7f088b8db90ffdccb48cfa0a50
+│ │ ├── heads
+│ │ │ └── master
+│ │ ├── tags
+│ │ │ ├── v1.0-hotfix
+│ │ │ └── v1.0
+│ │ └── rad
+│ │ ├── id
+│ │ └── sigrefs
+│ └── z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+│ └── refs
+│ ├── heads
+│ │ └── master
+│ ├── tags
+│ │ ├── v1.0-hotfix
+│ │ └── v1.0
+│ └── rad
+│ ├── id
+│ └── sigrefs
+├── rad
+│ └── id
+└── tags
+ ├── v1.0-hotfix
+ └── v1.0
+~~~
+
+Noting that there are tags under `refs/tags` now.
+
+To start, Alice will add a new payload to the repository identity. The identifier
+for this payload is `xyz.radicle.project.canonicalReferences`. It contains a
+single field with the key `rules`, and the value for this key is an array of
+rules. In this case, we will have two rules: one for `refs/tags/*` and one for
+`refs/tags/qa/*` (see RIP-0004 for more information on the rules).
+
+``` ~alice
+$ rad cref add refs/tags/* --threshold 1 --title "Add rule for refs/tags/*"
+✓ Rule for refs/tags/* has been added
+✓ Identity revision f4eda597611ec04ccf8bb3f18ddede4801a8441a created
+$ rad cref add refs/tags/qa/* --threshold 1 --title "Add rule for refs/tags/qa/*"
+✓ Rule for refs/tags/qa/* has been added
+✓ Identity revision 4b02f0d2040de5970eab9b1889321387f379ef5c created
+```
+
+Now, Alice will create a tag and push it:
+
+``` ~alice
+$ git tag v1.0-hotfix
+```
+
+``` ~alice (stderr)
+$ git push rad --tags
+✓ Canonical head for refs/tags/v1.0-hotfix updated to f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
+✓ Synced with 1 node(s)
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ * [new tag] v1.0-hotfix -> v1.0-hotfix
+```
+
+Notice that the output included a message about a canonical reference being
+updated:
+
+~~~
+✓ Canonical reference refs/tags/v1.0-hotfix updated to f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
+~~~
+
+On the other side, Bob performs a fetch and now has the tags locally:
+
+``` ~bob (stderr)
+$ cd heartwood
+$ git fetch rad
+From rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+ * [new tag] v1.0-hotfix -> rad/tags/v1.0-hotfix
+ * [new tag] v1.0-hotfix -> v1.0-hotfix
+```
+
+In the next portion of this example, we want to show that using a `threshold` of
+`2` requires both delegates. To do this, Bob creates a `master` reference, Alice
+adds him as a remote, and adds him to the identity delegates, as well as setting
+the `threshold` to `2` for the `refs/tags/*` rule:
+
+``` ~bob
+$ rad remote add z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --name alice
+✓ Follow policy updated for z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
+✓ Remote alice added
+✓ Remote-tracking branch alice/master created for z6MknSL…StBU8Vi
+$ git push rad master
+```
+
+``` ~alice
+$ rad remote add z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob
+✓ Follow policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
+✓ Remote bob added
+✓ Remote-tracking branch bob/master created for z6Mkt67…v4N1tRk
+$ rad id update --title "Add Bob" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm -q
+f0fd3b0c65ec38e52d578df94f96a0f57ac27d65
+$ rad cref edit refs/tags/* --threshold 2 --title "Change threshold for refs/tags to 2"
+✓ Rule for refs/tags/* has been modified
+✓ Identity revision ef105be657f3f112d0a3cfaafdf7362bc2df786a created
+```
+
+``` ~bob
+$ rad sync -f
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
+✓ Fetched repository from 1 seed(s)
+$ rad id accept ef105be657f3f112d0a3cfaafdf7362bc2df786a -q
+```
+
+When Bob creates a new tag and pushes it, we see that there's a warning that
+no quorum was found for the new tag:
+
+``` ~bob (stderr)
+$ git tag v2.0
+$ git push rad --tags
+warn: could not determine canonical tip for `refs/tags/v2.0`
+warn: no commit found with at least 2 vote(s) (threshold not met)
+warn: it is recommended to find a commit to agree upon
+✓ Synced with 1 node(s)
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+ * [new tag] v1.0-hotfix -> v1.0-hotfix
+ * [new tag] v2.0 -> v2.0
+```
+
+Alice can then fetch and checkout the new tag, create one on her side, and push
+it:
+
+``` ~alice (stderr)
+$ git fetch bob
+From rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+ * [new tag] v1.0-hotfix -> bob/tags/v1.0-hotfix
+ * [new tag] v2.0 -> bob/tags/v2.0
+```
+
+``` ~alice
+$ git checkout bob/tags/v2.0
+$ git tag v2.0
+```
+
+``` ~alice (stderr)
+$ git push rad --tags
+✓ Canonical head for refs/tags/v2.0 updated to f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
+✓ Synced with 1 node(s)
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ * [new tag] v2.0 -> v2.0
+```
+
+Now that Bob has also pushed this tag, we can see that the tag was made
+canonical.
+
+For the final portion of the example, we will show that both delegates aren't
+required for pushing tags that match the rule `refs/tags/qa/*`. To show this,
+Bob will create a tag and push it, and we should see that the canonical
+reference is created:
+
+``` ~bob (stderr)
+$ git tag qa/v2.1
+$ git push rad --tags
+✓ Canonical head for refs/tags/qa/v2.1 updated to f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
+✓ Synced with 1 node(s)
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+ * [new tag] qa/v2.1 -> qa/v2.1
+```
diff --git a/radicle-cli/examples/git/git-push-converge.md b/radicle-cli/examples/git/git-push-converge.md
index 62d90aeef..b7088382e 100644
--- a/radicle-cli/examples/git/git-push-converge.md
+++ b/radicle-cli/examples/git/git-push-converge.md
@@ -5,8 +5,11 @@ First we add our new delegates, Bob & Eve, to our repo, while also setting the
`threshold` to `3`:
``` ~alice
-$ rad id update --title "Add Bob & Eve" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --delegate did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --threshold 3 --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji -q
-3143236b2e40338f5574ec04e935a5ab80a6868a
+$ rad id update --title "Add Bob & Eve" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --delegate did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 -q
+7dab9524869f5f25abc3d9f2e7a7f975872a24c5
+$ rad cref edit refs/heads/master --threshold 3 --title "Set threshold for refs/heads/master to 3"
+✓ Rule for refs/heads/master has been modified
+✓ Identity revision 3b03a364f20c36c8e5d35599fc53e44fb1950cbf created
```
Bob and Eve will fetch the changes to ensure they hear about their delegate
@@ -14,15 +17,16 @@ responsibilities:
``` ~bob
$ rad sync --fetch
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkux1…nVhib7Z@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 2 seed(s)
+$ rad id accept 3b03a364f20c36c8e5d35599fc53e44fb1950cbf -q
```
``` ~eve
$ rad sync --fetch
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 2 seed(s)
```
@@ -58,14 +62,14 @@ found` error is showing up:
``` ~alice
$ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob
✓ Follow policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkux1…nVhib7Z@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
✓ Remote bob added
✓ Remote-tracking branch bob/master created for z6Mkt67…v4N1tRk
$ rad remote add did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --name eve
✓ Follow policy updated for z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z (eve)
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkux1…nVhib7Z@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
✓ Remote eve added
✓ Remote-tracking branch eve/master created for z6Mkux1…nVhib7Z
```
@@ -93,15 +97,15 @@ warn: could not determine canonical tip for `refs/heads/master`
warn: no commit found with at least 3 vote(s) (threshold not met)
warn: it is recommended to find a commit to agree upon
✓ Synced with 2 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
d09e634..0f9bd80 master -> master
```
``` ~bob
$ rad remote add did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --name alice
✓ Follow policy updated for z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkux1…nVhib7Z@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Remote alice added
✓ Remote-tracking branch alice/master created for z6MknSL…StBU8Vi
$ git reset --hard alice/master
@@ -115,9 +119,9 @@ become the canonical `master`.
``` ~bob (stderr)
$ git push rad
-✓ Canonical head updated to 3a75f66dd0020c9a0355cc6ec21f15de989e2001
+✓ Canonical head for refs/heads/master updated to 3a75f66dd0020c9a0355cc6ec21f15de989e2001
✓ Synced with 2 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
2a37862..0f9bd80 master -> master
```
@@ -126,8 +130,8 @@ Once Eve also resets to the merge commits, the canonical `master` is set to this
``` ~eve
$ rad remote add did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --name alice
✓ Follow policy updated for z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Remote alice added
✓ Remote-tracking branch alice/master created for z6MknSL…StBU8Vi
$ git reset --hard alice/master
@@ -136,8 +140,8 @@ HEAD is now at 0f9bd80 Merge remote-tracking branch 'eve/master'
``` ~eve (stderr)
$ git push rad
-✓ Canonical head updated to 0f9bd8035c04b3f73f5408e73e8454879b20800b
+✓ Canonical head for refs/heads/master updated to 0f9bd8035c04b3f73f5408e73e8454879b20800b
✓ Synced with 2 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
3a75f66..0f9bd80 master -> master
```
diff --git a/radicle-cli/examples/git/git-push-delete.md b/radicle-cli/examples/git/git-push-delete.md
index 0d4edb5e5..f46539971 100644
--- a/radicle-cli/examples/git/git-push-delete.md
+++ b/radicle-cli/examples/git/git-push-delete.md
@@ -1,7 +1,7 @@
Finally, we can also delete branches with `git push`:
```
-$ git ls-remote rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi refs/heads/*
+$ git ls-remote rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi refs/heads/*
145e1e69bef3ad93d14946ea212249c2fa9b9828 refs/heads/alice/1
ddcc1f164eacfd7dba41da9bff3261da3ee79fd3 refs/heads/alice/2
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
@@ -9,12 +9,12 @@ f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
``` (stderr) RAD_SOCKET=/dev/null
$ git push rad :alice/1
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- [deleted] alice/1
```
```
-$ git ls-remote rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi refs/heads/*
+$ git ls-remote rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi refs/heads/*
ddcc1f164eacfd7dba41da9bff3261da3ee79fd3 refs/heads/alice/2
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
```
@@ -23,13 +23,13 @@ f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
$ git checkout alice/2
Switched to a new branch 'alice/2'
$ git push rad HEAD:refs/patches
-✓ Patch bb9b0d5b8de8d5e2a4cba45f02bd35b3e2678fbe opened
+✓ Patch 799833562f9fff5ba32cb699141ca8a162e6bdf7 opened
To [..]
* [new reference] HEAD -> refs/patches
```
``` (stderr) RAD_SOCKET=/dev/null
$ git push rad alice/2 -d
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- [deleted] alice/2
```
diff --git a/radicle-cli/examples/git/git-push-diverge.md b/radicle-cli/examples/git/git-push-diverge.md
index a9b86e54e..6bb96c85c 100644
--- a/radicle-cli/examples/git/git-push-diverge.md
+++ b/radicle-cli/examples/git/git-push-diverge.md
@@ -5,15 +5,15 @@ canonical head.
First we add a second delegate, Bob, to our repo:
``` ~alice
-$ rad id update --title "Add Bob" --description "" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji -q
-c036c0d89ce26aef3ad7da402157dba16b5163b4
+$ rad id update --title "Add Bob" --description "" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 -q
+f48a2c516aceccde576d9ba8845b21eca1f7902c
```
Then, as Bob, we commit some code on top of the canonical head:
``` ~bob
$ rad sync --fetch
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
$ rad inspect --delegates
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
@@ -43,10 +43,10 @@ integrate Bob's changes before pushing ours:
``` ~alice (stderr) (fail) RAD_HINT=1
$ git push rad
-hint: you are attempting to push a commit that would cause your upstream to diverge from the canonical head
+hint: you are attempting to push a commit that would cause your upstream to diverge from the canonical reference refs/heads/master
hint: to integrate the remote changes, run `git pull --rebase` and try again
error: refusing to update branch to commit that is not a descendant of canonical head
-error: failed to push some refs to 'rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
+error: failed to push some refs to 'rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
```
We do that, and notice that we're now able to push our code:
@@ -61,8 +61,8 @@ f2de534 Second commit
```
``` ~alice RAD_SOCKET=/dev/null (stderr)
$ git push rad
-✓ Canonical head updated to f6cff86594495e9beccfeda7c20173e55c1dd9fc
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Canonical head for refs/heads/master updated to f6cff86594495e9beccfeda7c20173e55c1dd9fc
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..f6cff86 master -> master
```
@@ -74,7 +74,7 @@ $ git reset --hard HEAD^ -q
```
``` ~alice RAD_SOCKET=/dev/null (stderr)
$ git push -f
-✓ Canonical head updated to 319a7dc3b195368ded4b099f8c90bbb80addccd3
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Canonical head for refs/heads/master updated to 319a7dc3b195368ded4b099f8c90bbb80addccd3
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ f6cff86...319a7dc master -> master (forced update)
```
diff --git a/radicle-cli/examples/git/git-push-rollback.md b/radicle-cli/examples/git/git-push-rollback.md
index b0c478455..0568e4db4 100644
--- a/radicle-cli/examples/git/git-push-rollback.md
+++ b/radicle-cli/examples/git/git-push-rollback.md
@@ -4,16 +4,20 @@ First we add a second delegate, Bob, to our repo. We also change the threshold
to 2:
``` ~alice
-$ rad id update --title "Add Bob" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --threshold 2 -q
-069e7d58faa9a7473d27f5510d676af33282796f
+$ rad id update --title "Add Bob" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 -q
+f48a2c516aceccde576d9ba8845b21eca1f7902c
+$ rad cref edit refs/heads/master --threshold 2 --title "Set default branch threshold to 2"
+✓ Rule for refs/heads/master has been modified
+✓ Identity revision 33a0e4502bd6325b69d65a831e14bd12dddd9358 created
```
Bob then syncs these changes and adds a new commit:
``` ~bob
$ rad sync --fetch
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
+$ rad id accept 33a0e4502bd6325b69d65a831e14bd12dddd9358 -q
$ git commit -m "Third commit" --allow-empty -q
$ git push rad
$ git branch -arv
@@ -34,9 +38,9 @@ Fast-forward
``` ~alice (stderr)
$ git push rad
-✓ Canonical head updated to 319a7dc3b195368ded4b099f8c90bbb80addccd3
+✓ Canonical head for refs/heads/master updated to 319a7dc3b195368ded4b099f8c90bbb80addccd3
✓ Synced with 1 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..319a7dc master -> master
```
@@ -53,8 +57,8 @@ push and the new canonical head becomes the previous commit again:
``` ~alice (stderr)
$ git push rad -f
-✓ Canonical head updated to f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
+✓ Canonical head for refs/heads/master updated to f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
✓ Synced with 1 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 319a7dc...f2de534 master -> master (forced update)
```
diff --git a/radicle-cli/examples/git/git-push.md b/radicle-cli/examples/git/git-push.md
index 632639825..24ad82474 100644
--- a/radicle-cli/examples/git/git-push.md
+++ b/radicle-cli/examples/git/git-push.md
@@ -6,7 +6,7 @@ $ git commit -m "Alice's commit" --allow-empty -s
``` (stderr) RAD_SOCKET=/dev/null
$ git push rad HEAD:alice/1
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new branch] HEAD -> alice/1
```
@@ -17,9 +17,9 @@ $ git commit --amend -m "Alice's amended commit" --allow-empty -s
```
``` (stderr) (fail)
$ git push rad HEAD:alice/1
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
! [rejected] HEAD -> alice/1 (non-fast-forward)
-error: failed to push some refs to 'rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
+error: failed to push some refs to 'rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
hint: [..]
hint: [..]
hint: [..]
@@ -30,7 +30,7 @@ And that we can with `+`:
``` (stderr)
$ git push -o no-sync rad +HEAD:alice/1
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 87fa120...145e1e6 HEAD -> alice/1 (forced update)
```
@@ -45,7 +45,7 @@ $ git branch -r -vv
List our namespaced refs:
```
-$ git ls-remote rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 'refs/heads/*'
+$ git ls-remote rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 'refs/heads/*'
145e1e69bef3ad93d14946ea212249c2fa9b9828 refs/heads/alice/1
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
```
@@ -67,7 +67,7 @@ Note that it is forbidden to delete the default/canonical branch:
``` (fail) (stderr)
$ git push rad :master
error: refusing to delete default branch ref 'refs/heads/master'
-error: failed to push some refs to 'rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
+error: failed to push some refs to 'rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
```
If you pass an unsupported push option, you get an error:
@@ -85,6 +85,6 @@ $ git commit -m "Something good" --allow-empty -s
```
``` (stderr)
$ git push -o no-sync rad ddcc1f164eacfd7dba41da9bff3261da3ee79fd3:refs/heads/alice/2
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new branch] ddcc1f164eacfd7dba41da9bff3261da3ee79fd3 -> alice/2
```
diff --git a/radicle-cli/examples/git/git-tag.md b/radicle-cli/examples/git/git-tag.md
index 96b74e9f7..54cd75f12 100644
--- a/radicle-cli/examples/git/git-tag.md
+++ b/radicle-cli/examples/git/git-tag.md
@@ -13,7 +13,7 @@ $ git tag v1.0 -a -m "Release v1.0"
``` ~alice (stderr)
$ git push rad v1.0
✓ Synced with 1 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new tag] v1.0 -> v1.0
```
@@ -37,7 +37,7 @@ Bob fetches the tag from Alice, by adding her as a remote:
$ cd heartwood
$ rad remote add z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --name alice
✓ Follow policy updated for z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Remote alice added
✓ Remote-tracking branch alice/master created for z6MknSL…StBU8Vi
```
@@ -47,7 +47,7 @@ under the `alice` remote:
``` ~bob (stderr)
$ git fetch alice
-From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+From rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new tag] v1.0 -> alice/tags/v1.0
```
@@ -64,7 +64,7 @@ Updated tag 'v1.0' (was be18ed6)
``` ~alice (stderr)
$ git push rad v1.0 -f
✓ Synced with 1 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ be18ed6...9dbdebc v1.0 -> v1.0 (forced update)
```
@@ -73,12 +73,12 @@ update of the tag:
``` ~bob
$ rad sync -f
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
```
``` ~bob (stderr)
$ git fetch alice -f
-From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+From rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
62d19fd..9dbdebc v1.0 -> alice/tags/v1.0
```
diff --git a/radicle-cli/examples/rad-block.md b/radicle-cli/examples/rad-block.md
index 413350273..4adec001a 100644
--- a/radicle-cli/examples/rad-block.md
+++ b/radicle-cli/examples/rad-block.md
@@ -4,8 +4,8 @@ repositories from being seeded.
For instance, if our default policy is to seed, any unknown repository will
have its policy set to allow seeding:
```
-$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --policy
-Repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji is being seeded with scope `all`
+$ rad inspect rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --policy
+Repository rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 is being seeded with scope `all`
```
Since there is no policy specific to this repository, there's nothing to be
@@ -20,15 +20,15 @@ But if we wanted to prevent this repository from being seeded, while
allowing all other repositories, we could use `rad block`:
```
-$ rad block rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Policy for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji set to 'block'
+$ rad block rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Policy for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 set to 'block'
```
We can see that it is now no longer seeded:
```
-$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --policy
-Repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji is not being seeded
+$ rad inspect rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --policy
+Repository rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 is not being seeded
```
And a 'block' policy was added:
@@ -38,15 +38,15 @@ $ rad seed
╭───────────────────────────────────────────────────────────╮
│ Repository Name Policy Scope │
├───────────────────────────────────────────────────────────┤
-│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji block all │
+│ rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 block all │
╰───────────────────────────────────────────────────────────╯
```
If we want to reverse the blocking of the RID we can use `rad unblock`:
```
-$ rad unblock rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ The 'block' policy for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji is removed
+$ rad unblock rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ The 'block' policy for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 is removed
```
```
diff --git a/radicle-cli/examples/rad-checkout-repo-config-linux.md b/radicle-cli/examples/rad-checkout-repo-config-linux.md
index ace75c511..78c1ba2c4 100644
--- a/radicle-cli/examples/rad-checkout-repo-config-linux.md
+++ b/radicle-cli/examples/rad-checkout-repo-config-linux.md
@@ -14,10 +14,10 @@ $ cat .git/config
[push]
default = upstream
[remote "rad"]
- url = rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+ url = rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
fetch = +refs/heads/*:refs/remotes/rad/*
fetch = +refs/tags/*:refs/remotes/rad/tags/*
- pushurl = rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ pushurl = rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
[branch "master"]
remote = rad
merge = refs/heads/master
diff --git a/radicle-cli/examples/rad-checkout-repo-config-macos.md b/radicle-cli/examples/rad-checkout-repo-config-macos.md
index ff7bc1f3c..61f6c9298 100644
--- a/radicle-cli/examples/rad-checkout-repo-config-macos.md
+++ b/radicle-cli/examples/rad-checkout-repo-config-macos.md
@@ -16,10 +16,10 @@ $ cat .git/config
[push]
default = upstream
[remote "rad"]
- url = rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+ url = rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
fetch = +refs/heads/*:refs/remotes/rad/*
fetch = +refs/tags/*:refs/remotes/rad/tags/*
- pushurl = rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ pushurl = rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
[branch "master"]
remote = rad
merge = refs/heads/master
diff --git a/radicle-cli/examples/rad-checkout.md b/radicle-cli/examples/rad-checkout.md
index e51b4199c..98d1f2fc8 100644
--- a/radicle-cli/examples/rad-checkout.md
+++ b/radicle-cli/examples/rad-checkout.md
@@ -2,7 +2,7 @@ With the `rad checkout` command, you can create a new working copy from an
existing project.
```
-$ rad checkout rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+$ rad checkout rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
✓ Repository checkout successful under ./heartwood
```
@@ -32,8 +32,8 @@ Check the remote configuration:
```
$ git remote --verbose
-rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji (fetch)
-rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
+rad rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 (fetch)
+rad rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
```
List the branches:
diff --git a/radicle-cli/examples/rad-clean.md b/radicle-cli/examples/rad-clean.md
index 66f93b169..632e11ae3 100644
--- a/radicle-cli/examples/rad-clean.md
+++ b/radicle-cli/examples/rad-clean.md
@@ -10,7 +10,7 @@ $ rad ls
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
-│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public f2de534 Radicle Heartwood Protocol & Stack │
+│ heartwood rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 public f2de534 Radicle Heartwood Protocol & Stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -18,23 +18,23 @@ Let's also inspect what remotes are in the repository:
``` ~alice
$ rad inspect --sigrefs
-z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 99c549702e2bcfe02b0e68d4a2224fb7a1524529
-z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk e9f48ef90fe8592e1b1c95f96c21a59ca1495300
+z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 7c1445cd018b1b0f51e0d815c3c03b289140eafa
+z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk 2694db11d1ce3fb21f4cee6840f7daa6846366b2
```
Now let's clean the `heartwood` project:
``` ~alice
-$ rad clean rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
+$ rad clean rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --no-confirm
Removed z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
-✓ Successfully cleaned rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+✓ Successfully cleaned rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
```
Inspecting the remotes again, we see that Bob is now gone:
``` ~alice
$ rad inspect --sigrefs
-z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 99c549702e2bcfe02b0e68d4a2224fb7a1524529
+z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 7c1445cd018b1b0f51e0d815c3c03b289140eafa
```
Note that Bob will be fetched again if we do not untrack his
@@ -45,23 +45,23 @@ Cleaning a repository again will remove no remotes, since we're
already at the minimal set of remotes:
``` ~alice
-$ rad clean rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
-✓ Successfully cleaned rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+$ rad clean rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --no-confirm
+✓ Successfully cleaned rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
```
Since Eve did not fork the repository, and has no refs of their own,
when they run `rad clean` it will remove the project entirely:
``` ~eve
-$ rad clean rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
+$ rad clean rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --no-confirm
Removed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
-✓ Successfully cleaned rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+✓ Successfully cleaned rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
```
And attempting to clean the repository again, or any non-existent
repository, has no effect on the storage at all:
``` ~eve (fail)
-$ rad clean rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
-✗ Error: repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji was not found
+$ rad clean rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --no-confirm
+✗ Error: repository rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 was not found
```
diff --git a/radicle-cli/examples/rad-clone-all.md b/radicle-cli/examples/rad-clone-all.md
index 9ee1980b8..2ca1972e3 100644
--- a/radicle-cli/examples/rad-clone-all.md
+++ b/radicle-cli/examples/rad-clone-all.md
@@ -1,7 +1,7 @@
```
-$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope all
-✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+$ rad clone rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --scope all
+✓ Seeding policy updated for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 with scope 'all'
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
@@ -63,7 +63,7 @@ We can also create our own fork just by pushing:
``` (stderr)
$ git push -o no-sync rad master
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
* [new branch] master -> master
```
```
diff --git a/radicle-cli/examples/rad-clone-connect.md b/radicle-cli/examples/rad-clone-connect.md
index 852906ec5..f97b3be17 100644
--- a/radicle-cli/examples/rad-clone-connect.md
+++ b/radicle-cli/examples/rad-clone-connect.md
@@ -2,12 +2,12 @@ If we're not connecting to seed nodes when cloning, the `clone` command will
automatically connect to the necessary seeds.
```
-$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
+$ rad clone rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Seeding policy updated for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 with scope 'all'
✓ Connecting to z6Mkt67…v4N1tRk@[..]
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
✓ Connecting to z6MknSL…StBU8Vi@[..]
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
diff --git a/radicle-cli/examples/rad-clone-directory.md b/radicle-cli/examples/rad-clone-directory.md
index f499fac5d..418b5f429 100644
--- a/radicle-cli/examples/rad-clone-directory.md
+++ b/radicle-cli/examples/rad-clone-directory.md
@@ -2,9 +2,9 @@ We can specify where a repository gets cloned into on our filesystem
by specifying the directory in the `rad clone` command:
```
-$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed Developer/Radicle
-✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'followed'
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+$ rad clone rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --scope followed Developer/Radicle
+✓ Seeding policy updated for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 with scope 'followed'
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Creating checkout in ./Developer/Radicle..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
@@ -21,7 +21,7 @@ Note that attempting to clone into a directory that already exists,
and is not empty, will fail:
``` (fail)
-$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed Developer/Radicle
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+$ rad clone rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --scope followed Developer/Radicle
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✗ Error: the directory path "Developer/Radicle" already exists
```
diff --git a/radicle-cli/examples/rad-clone-partial-fail.md b/radicle-cli/examples/rad-clone-partial-fail.md
index c554fbb55..3c74e6254 100644
--- a/radicle-cli/examples/rad-clone-partial-fail.md
+++ b/radicle-cli/examples/rad-clone-partial-fail.md
@@ -5,19 +5,19 @@ $ rad node routing
╭─────────────────────────────────────────────────────╮
│ RID NID │
├─────────────────────────────────────────────────────┤
-│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji z6MknSL…StBU8Vi │
-│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji z6MksFq…bS9wzpT │
-│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji z6Mkt67…v4N1tRk │
+│ rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 z6MknSL…StBU8Vi │
+│ rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 z6MksFq…bS9wzpT │
+│ rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 z6Mkt67…v4N1tRk │
╰─────────────────────────────────────────────────────╯
```
When she tries to clone, one of those will fail to fetch. But the clone command
still returns successfully.
```
-$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --timeout 3
-✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
-✗ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..].. error: failed to perform fetch handshake
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+$ rad clone rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --timeout 3
+✓ Seeding policy updated for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 with scope 'all'
+✗ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..].. error: failed to perform fetch handshake
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✗ Connecting to z6MksFq…bS9wzpT@[..].. error: [..]
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
diff --git a/radicle-cli/examples/rad-clone.md b/radicle-cli/examples/rad-clone.md
index 4dc68cd81..86febad8c 100644
--- a/radicle-cli/examples/rad-clone.md
+++ b/radicle-cli/examples/rad-clone.md
@@ -2,9 +2,9 @@ To create a local copy of a repository on the radicle network, we use the
`clone` command, followed by the identifier or *RID* of the repository:
```
-$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed
-✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'followed'
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+$ rad clone rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --scope followed
+✓ Seeding policy updated for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 with scope 'followed'
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
@@ -46,10 +46,10 @@ We can also take a look at the remotes:
```
$ git remote -v
-alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (fetch)
-alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
-rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji (fetch)
-rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (push)
+alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (fetch)
+alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
+rad rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 (fetch)
+rad rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (push)
```
Let's check the last commit!
@@ -69,6 +69,6 @@ $ rad ls --seeded
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
-│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public f2de534 Radicle Heartwood Protocol & Stack │
+│ heartwood rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 public f2de534 Radicle Heartwood Protocol & Stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-cob-log.md b/radicle-cli/examples/rad-cob-log.md
index 2fa0c0562..e7aeb09ba 100644
--- a/radicle-cli/examples/rad-cob-log.md
+++ b/radicle-cli/examples/rad-cob-log.md
@@ -6,7 +6,7 @@ First create an issue.
$ rad issue open --title "flux capacitor underpowered" --description "Flux capacitor power requirements exceed current supply" --no-announce
╭─────────────────────────────────────────────────────────╮
│ Title flux capacitor underpowered │
-│ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │
+│ Issue 0d18c610be2fbb4f47d45434c581f3bf0b0ff071 │
│ Author alice (you) │
│ Status open │
│ │
@@ -21,7 +21,7 @@ $ rad issue list
╭──────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Labels Assignees Opened │
├──────────────────────────────────────────────────────────────────────────────────────────┤
-│ ● d87dcfe flux capacitor underpowered alice (you) now │
+│ ● 0d18c61 flux capacitor underpowered alice (you) now │
╰──────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -45,25 +45,25 @@ $ rad patch
╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │
├─────────────────────────────────────────────────────────────────────────────────────────┤
-│ ● aa45913 Define power requirements alice (you) - 3e674d1 +0 -0 now │
+│ ● c90967c Define power requirements alice (you) - 3e674d1 +0 -0 now │
╰─────────────────────────────────────────────────────────────────────────────────────────╯
```
Both issue and patch COBs can be listed.
```
-$ rad cob list --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue
-d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
-$ rad cob list --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch
-aa45913e757cacd46972733bddee5472c78fa32a
+$ rad cob list --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.issue
+0d18c610be2fbb4f47d45434c581f3bf0b0ff071
+$ rad cob list --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.patch
+c90967c43719b916e0b5a8b5dafe353608f8a08a
```
We can look at the issue COB.
```
-$ rad cob log --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
-commit d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
-resource 0656c217f917c3e06234771e9ecae53aba5e173e
+$ rad cob log --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.issue --object 0d18c610be2fbb4f47d45434c581f3bf0b0ff071
+commit 0d18c610be2fbb4f47d45434c581f3bf0b0ff071
+resource eeb8b44890570ccf85db7f3cb2a475100a27408a
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date Thu, 15 Dec 2022 17:28:04 +0000
@@ -82,9 +82,9 @@ date Thu, 15 Dec 2022 17:28:04 +0000
We can look at the patch COB too.
```
-$ rad cob log --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch --object aa45913e757cacd46972733bddee5472c78fa32a
-commit aa45913e757cacd46972733bddee5472c78fa32a
-resource 0656c217f917c3e06234771e9ecae53aba5e173e
+$ rad cob log --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.patch --object c90967c43719b916e0b5a8b5dafe353608f8a08a
+commit c90967c43719b916e0b5a8b5dafe353608f8a08a
+resource eeb8b44890570ccf85db7f3cb2a475100a27408a
rel 3e674d1a1df90807e934f9ae5da2591dd6848a33
rel f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
@@ -108,11 +108,11 @@ date Thu, 15 Dec 2022 17:28:04 +0000
Finally let's updated the issue and see the `parent` header:
```
-$ rad issue label d87dcfe --add bug --no-announce
-$ rad cob log --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
-commit abec0a9f3c945594c4e78d24d8ec679e56b22b79
-resource 0656c217f917c3e06234771e9ecae53aba5e173e
-parent d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
+$ rad issue label 0d18c61 --add bug --no-announce
+$ rad cob log --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.issue --object 0d18c610be2fbb4f47d45434c581f3bf0b0ff071
+commit c1cde09b836c4b1dc25acbcf73105b3794df84d8
+resource eeb8b44890570ccf85db7f3cb2a475100a27408a
+parent 0d18c610be2fbb4f47d45434c581f3bf0b0ff071
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date Thu, 15 Dec 2022 17:28:04 +0000
@@ -123,8 +123,8 @@ date Thu, 15 Dec 2022 17:28:04 +0000
"type": "label"
}
-commit d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
-resource 0656c217f917c3e06234771e9ecae53aba5e173e
+commit 0d18c610be2fbb4f47d45434c581f3bf0b0ff071
+resource eeb8b44890570ccf85db7f3cb2a475100a27408a
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date Thu, 15 Dec 2022 17:28:04 +0000
diff --git a/radicle-cli/examples/rad-cob-multiset.md b/radicle-cli/examples/rad-cob-multiset.md
index bd52803a1..f8c8528b2 100644
--- a/radicle-cli/examples/rad-cob-multiset.md
+++ b/radicle-cli/examples/rad-cob-multiset.md
@@ -41,28 +41,28 @@ It reads the current state of the grocery list and operations containing actions
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
+$ rad cob create --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --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
+$ rad cob show --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --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
+$ rad cob update --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --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
+$ rad cob show --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type com.example.multiset --object 9bba8e6f83ef56b11151ef6ad02cc4595f982aab
{"jelly":0,"peanut butter":2,"salad":4}
```
diff --git a/radicle-cli/examples/rad-cob-show.md b/radicle-cli/examples/rad-cob-show.md
index 673f88788..a90cd53bf 100644
--- a/radicle-cli/examples/rad-cob-show.md
+++ b/radicle-cli/examples/rad-cob-show.md
@@ -9,7 +9,7 @@ First create an issue.
$ rad issue open --title "spice harvester broken" --description "Fremen have attacked, maybe we went too far?" --no-announce
╭──────────────────────────────────────────────────╮
│ Title spice harvester broken │
-│ Issue 9de644864342d7a505eb8d58d1ef20e5bb05de2e │
+│ Issue fa09289336f9317e0d2573372f7965cf8861d04e │
│ Author alice (you) │
│ Status open │
│ │
@@ -24,7 +24,7 @@ $ rad issue list
╭─────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Labels Assignees Opened │
├─────────────────────────────────────────────────────────────────────────────────────┤
-│ ● 9de6448 spice harvester broken alice (you) now │
+│ ● fa09289 spice harvester broken alice (you) now │
╰─────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -48,37 +48,37 @@ $ rad patch
╭───────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │
├───────────────────────────────────────────────────────────────────────────────────────────┤
-│ ● d1f7f86 Start drafting peace treaty alice (you) - 575ed68 +0 -0 now │
+│ ● 07f94cd Start drafting peace treaty alice (you) - 575ed68 +0 -0 now │
╰───────────────────────────────────────────────────────────────────────────────────────────╯
```
Both issue and patch COBs can be listed.
```
-$ rad cob list --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue
-9de644864342d7a505eb8d58d1ef20e5bb05de2e
-$ rad cob list --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch
-d1f7f869fde9fac19c1779c4c2e77e8361333f91
+$ rad cob list --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.issue
+fa09289336f9317e0d2573372f7965cf8861d04e
+$ rad cob list --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.patch
+07f94cddadbf87eca62a4b175c47b03db3015427
```
We can show the issue COB.
```
-$ rad cob show --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object 9de644864342d7a505eb8d58d1ef20e5bb05de2e
-{"assignees":[],"title":"spice harvester broken","state":{"status":"open"},"labels":[],"thread":{"comments":{"9de644864342d7a505eb8d58d1ef20e5bb05de2e":{"author":"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi","reactions":[],"resolved":false,"body":"Fremen have attacked, maybe we went too far?","edits":[{"author":"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi","timestamp":1671125284000,"body":"Fremen have attacked, maybe we went too far?","embeds":[]}]}},"timeline":["9de644864342d7a505eb8d58d1ef20e5bb05de2e"]}}
+$ rad cob show --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.issue --object fa09289336f9317e0d2573372f7965cf8861d04e
+{"assignees":[],"title":"spice harvester broken","state":{"status":"open"},"labels":[],"thread":{"comments":{"fa09289336f9317e0d2573372f7965cf8861d04e":{"author":"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi","reactions":[],"resolved":false,"body":"Fremen have attacked, maybe we went too far?","edits":[{"author":"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi","timestamp":1671125284000,"body":"Fremen have attacked, maybe we went too far?","embeds":[]}]}},"timeline":["fa09289336f9317e0d2573372f7965cf8861d04e"]}}
```
We can show the patch COB too.
```
-$ rad cob show --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch --object d1f7f869fde9fac19c1779c4c2e77e8361333f91
-{"title":"Start drafting peace treaty","author":{"id":"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"},"state":{"status":"open"},"target":"delegates","labels":[],"merges":{},"revisions":{"d1f7f869fde9fac19c1779c4c2e77e8361333f91":{"id":"d1f7f869fde9fac19c1779c4c2e77e8361333f91","author":{"id":"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"},"description":[{"author":"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi","timestamp":1671125284000,"body":"See details.","embeds":[]}],"base":"f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354","oid":"575ed68c716d6aae81ea6b718fd9ac66a8eae532","discussion":{"comments":{},"timeline":[]},"reviews":{},"timestamp":1671125284000,"resolves":[],"reactions":[]}},"assignees":[],"timeline":["d1f7f869fde9fac19c1779c4c2e77e8361333f91"],"reviews":{}}
+$ rad cob show --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.patch --object 07f94cddadbf87eca62a4b175c47b03db3015427
+{"title":"Start drafting peace treaty","author":{"id":"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"},"state":{"status":"open"},"target":"delegates","labels":[],"merges":{},"revisions":{"07f94cddadbf87eca62a4b175c47b03db3015427":{"id":"07f94cddadbf87eca62a4b175c47b03db3015427","author":{"id":"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"},"description":[{"author":"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi","timestamp":1671125284000,"body":"See details.","embeds":[]}],"base":"f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354","oid":"575ed68c716d6aae81ea6b718fd9ac66a8eae532","discussion":{"comments":{},"timeline":[]},"reviews":{},"timestamp":1671125284000,"resolves":[],"reactions":[]}},"assignees":[],"timeline":["07f94cddadbf87eca62a4b175c47b03db3015427"],"reviews":{}}
```
Finally let's update the issue and see the output of `rad cob show` also changes.
```
-$ rad issue label 9de6448 --add bug --no-announce
-$ rad cob show --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object 9de644864342d7a505eb8d58d1ef20e5bb05de2e
-{"assignees":[],"title":"spice harvester broken","state":{"status":"open"},"labels":["bug"],"thread":{"comments":{"9de644864342d7a505eb8d58d1ef20e5bb05de2e":{"author":"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi","reactions":[],"resolved":false,"body":"Fremen have attacked, maybe we went too far?","edits":[{"author":"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi","timestamp":1671125284000,"body":"Fremen have attacked, maybe we went too far?","embeds":[]}]}},"timeline":["9de644864342d7a505eb8d58d1ef20e5bb05de2e"]}}
+$ rad issue label fa09289 --add bug --no-announce
+$ rad cob show --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.issue --object fa09289336f9317e0d2573372f7965cf8861d04e
+{"assignees":[],"title":"spice harvester broken","state":{"status":"open"},"labels":["bug"],"thread":{"comments":{"fa09289336f9317e0d2573372f7965cf8861d04e":{"author":"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi","reactions":[],"resolved":false,"body":"Fremen have attacked, maybe we went too far?","edits":[{"author":"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi","timestamp":1671125284000,"body":"Fremen have attacked, maybe we went too far?","embeds":[]}]}},"timeline":["fa09289336f9317e0d2573372f7965cf8861d04e"]}}
```
diff --git a/radicle-cli/examples/rad-cob-update-identity.md b/radicle-cli/examples/rad-cob-update-identity.md
index 40083e8c2..dcb862a18 100644
--- a/radicle-cli/examples/rad-cob-update-identity.md
+++ b/radicle-cli/examples/rad-cob-update-identity.md
@@ -1,6 +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
+$ rad cob update --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.id --object eeb8b44890570ccf85db7f3cb2a475100a27408a --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
index 86516b8bf..83c329a67 100644
--- a/radicle-cli/examples/rad-cob-update.md
+++ b/radicle-cli/examples/rad-cob-update.md
@@ -12,8 +12,8 @@ $ git commit --message "Add README, just for the fun"
``` (stderr)
$ git push rad -o patch.message="Add README, just for the fun" HEAD:refs/patches
-✓ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch f699e2299e9ee734758626924df7e15fd9a68553 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -28,22 +28,22 @@ $ git commit -v -m "Define the LICENSE"
``` (stderr)
$ git push -f -o patch.message="Add License"
-✓ Patch 89f7afb updated to revision 5d78dd5376453e25df5988ec86951c99cb73742c
-To compare against your previous revision 89f7afb, run:
+✓ Patch f699e22 updated to revision 13b5240e3f7ecb60fea4f66eb1a09fa3ffc1de7f
+To compare against your previous revision f699e22, run:
git range-diff f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 03c02af4b12a593d17a06d38fae50a57fc3c339a 8945f6189adf027892c85ac57f7e9341049c2537
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- 03c02af..8945f61 changes -> patches/89f7afb1511b976482b21f6b2f39aef7f4fb88a2
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 03c02af..8945f61 changes -> patches/f699e2299e9ee734758626924df7e15fd9a68553
```
Let's look at the patch, to see what it looks like before editing it:
```
-$ rad patch show 89f7afb
+$ rad patch show f699e22
╭─────────────────────────────────────────────────────────────────────╮
│ Title Add README, just for the fun │
-│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
+│ Patch f699e2299e9ee734758626924df7e15fd9a68553 │
│ Author alice (you) │
│ Head 8945f6189adf027892c85ac57f7e9341049c2537 │
│ Branches changes │
@@ -54,7 +54,7 @@ $ rad patch show 89f7afb
│ 03c02af Add README, just for the fun │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (03c02af) now │
-│ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
+│ ↑ updated to 13b5240e3f7ecb60fea4f66eb1a09fa3ffc1de7f (8945f61) now │
╰─────────────────────────────────────────────────────────────────────╯
```
@@ -62,11 +62,11 @@ 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
+$ rad patch edit f699e22 --message "Add Metadata" --message "Add README & LICENSE" --no-announce
+$ rad patch show f699e22
╭─────────────────────────────────────────────────────────────────────╮
│ Title Add Metadata │
-│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
+│ Patch f699e2299e9ee734758626924df7e15fd9a68553 │
│ Author alice (you) │
│ Head 8945f6189adf027892c85ac57f7e9341049c2537 │
│ Branches changes │
@@ -79,14 +79,14 @@ $ rad patch show 89f7afb
│ 03c02af Add README, just for the fun │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (03c02af) now │
-│ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
+│ ↑ updated to 13b5240e3f7ecb60fea4f66eb1a09fa3ffc1de7f (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"}
+{"type": "revision.edit", "description": "Add README and LICENSE", "revision": "f699e2299e9ee734758626924df7e15fd9a68553"}
```
We now use `rad cob update` to edit the patch another time, rewriting the description.
@@ -95,12 +95,12 @@ specifying the revision for which the description should be changed, and `descri
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
+$ rad cob update --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.patch --object f699e2299e9ee734758626924df7e15fd9a68553 --message "Edit patch" revision-edit.jsonl
+8f2a1a4c55f976288574e463dff1e85981e7a427
+$ rad patch show --verbose f699e22
╭─────────────────────────────────────────────────────────────────────╮
│ Title Add Metadata │
-│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
+│ Patch f699e2299e9ee734758626924df7e15fd9a68553 │
│ Author alice (you) │
│ Head 8945f6189adf027892c85ac57f7e9341049c2537 │
│ Base f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 │
@@ -114,7 +114,7 @@ $ rad patch show --verbose 89f7afb
│ 03c02af Add README, just for the fun │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (03c02af) now │
-│ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
+│ ↑ updated to 13b5240e3f7ecb60fea4f66eb1a09fa3ffc1de7f (8945f61) now │
╰─────────────────────────────────────────────────────────────────────╯
```
@@ -137,7 +137,7 @@ We prepare the file `revision-create.jsonl` which contains one action.
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
+$ rad cob update --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.patch --object f699e2299e9ee734758626924df7e15fd9a68553 --message "Create new revision" revision.jsonl
✗ Error: store: update error: failed to read 'f1339dd109e538c6b3a7fed3e72403e1b4db08c9' from git odb
```
@@ -153,12 +153,12 @@ $ 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
+$ rad cob update --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --type xyz.radicle.patch --object f699e2299e9ee734758626924df7e15fd9a68553 --message "Create new revision" revision.jsonl
+c60de85722655bfd01867c180307a9064c7acabd
+$ rad patch show f699e22
╭─────────────────────────────────────────────────────────────────────╮
│ Title Add Metadata │
-│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
+│ Patch f699e2299e9ee734758626924df7e15fd9a68553 │
│ Author alice (you) │
│ Head f1339dd109e538c6b3a7fed3e72403e1b4db08c9 │
│ Branches changes │
@@ -172,7 +172,7 @@ $ rad patch show 89f7afb
│ 03c02af Add README, just for the fun │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (03c02af) now │
-│ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
-│ ↑ updated to 2da9c025a1d14d93c4f2cec60a7878afbc5e2a3c (f1339dd) now │
+│ ↑ updated to 13b5240e3f7ecb60fea4f66eb1a09fa3ffc1de7f (8945f61) now │
+│ ↑ updated to c60de85722655bfd01867c180307a9064c7acabd (f1339dd) now │
╰─────────────────────────────────────────────────────────────────────╯
-```
\ No newline at end of file
+```
diff --git a/radicle-cli/examples/rad-cref.md b/radicle-cli/examples/rad-cref.md
index d7eab7691..5c93bbc39 100644
--- a/radicle-cli/examples/rad-cref.md
+++ b/radicle-cli/examples/rad-cref.md
@@ -23,23 +23,26 @@ divergence.
So, let's add another rule through the `add` subcommand:
```
-$ rad cref add refs/heads/dev --delegate did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2 --title "Add canonical reference rule for refs/heads/dev"
+$ rad cref add refs/heads/dev --allow did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2 --title "Add canonical reference rule for refs/heads/dev"
✓ Rule for refs/heads/dev has been added
✓ Identity revision a92caeb23f9df6f2136c583ef6f01cb81fea853f created
```
-Notice the output for the `Identity revision`. This is because when we update
-the canonical reference rules, the identity is updated since that's where they
-are stored. In this case, there is a single delegate so these updates will be
-accepted automatically. If there were more delegates then they would require a
-majority to agree to the changes.
+Notice the last line of the output:
-We can also note that we added the delegate
+ ✓ Identity revision a92caeb23f9df6f2136c583ef6f01cb81fea853f created
+
+Since canonical reference rules are stored in the identity document, it needs to
+be updated whenever the rules change. In the case above, there is a single
+delegate so these updates will be accepted automatically. If there were more
+delegates then they would require a majority to agree to the changes.
+
+We can also note that we added the DID
`did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk` to the rule. This
peer is not a delegate of the repository, but their commit state for
`refs/heads/dev` is taken into account when computing the canonical reference.
This means that we can collaborate with peers that we trust on certain
-references but may not want to trust them on all changes made to the project.
+references, but may not want to trust them on all changes made to the project.
Let's check that our rule was successfully added:
@@ -70,7 +73,7 @@ $ rad cref add refs/tags/releases/* --title "Add canonical reference rule for re
✓ Identity revision a880b05441f00cc90bc7bae76e0f1ef16b73daf1 created
```
-Here, we didn't specify the `--delegate` or `--threshold` options. This means
+Here, we didn't specify the `--allow` or `--threshold` options. This means
the defaults of `delegates` and `1` will be used. So let's check that this is
true:
@@ -137,11 +140,11 @@ $ rad cref remove refs/heads/main --title "Remove refs/heads/main"
Nothing to do. The rules are up to date. See `rad cref list`.
```
-Finally, rules are verified according to the RIP. In this case we'll add a rule
+Finally, rules are verified according to [RIP-0004]. In this case we'll add a rule
with a single delegate but a threshold of 2:
``` (fails)
-$ rad cref add refs/tags/* --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2 --title "Add canonical reference rule for refs/tags/*"
+$ rad cref add refs/tags/* --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2 --title "Add canonical reference rule for refs/tags/*"
✗ Error: invalid threshold `2`: threshold cannot exceed number of delegates
```
@@ -150,7 +153,9 @@ reference:
``` (fails)
$ rad cref add refs/rad/id --title "Try add refs/rad/id"
-✗ Error: rad cref: cannot create rule for 'refs/rad/id' since references under 'refs/rad' are reserved
+✗ Error: rad cref: cannot create rule for 'refs/rad/id' since references under 'refs/rad' are protected
```
+# FIXME: update the link to post directly to the RIP when merged
+[RIP-0004]: https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/patches/1d1ce874f7c39ecdcd8c318bbae46ffd02fe1ea8
[git-check-ref-format]: https://git-scm.com/docs/git-check-ref-format
diff --git a/radicle-cli/examples/rad-fetch.md b/radicle-cli/examples/rad-fetch.md
index 8e03ce158..9ad4a205a 100644
--- a/radicle-cli/examples/rad-fetch.md
+++ b/radicle-cli/examples/rad-fetch.md
@@ -5,12 +5,12 @@ necessary.
Instead, we want to fetch the project from the network into our local
storage. In this scenario, we know that the project is
-`rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji`. In order to fetch it, we first
+`rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2`. In order to fetch it, we first
have to update our seeding policy for the project.
```
-$ rad seed rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-fetch
-✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
+$ rad seed rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --no-fetch
+✓ Seeding policy updated for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 with scope 'all'
```
Now that the project is seeding we can fetch it and we will have it in
@@ -18,8 +18,8 @@ our local storage. Note that the `seed` command can also be told to fetch
by passing the `--fetch` option.
```
-$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+$ rad sync --fetch rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
```
diff --git a/radicle-cli/examples/rad-fork.md b/radicle-cli/examples/rad-fork.md
index 6ef727ac3..8e89b5d43 100644
--- a/radicle-cli/examples/rad-fork.md
+++ b/radicle-cli/examples/rad-fork.md
@@ -4,7 +4,7 @@ NID. This is demonstrated below where our NID is
`z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk`:
```
-$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --refs
+$ rad inspect rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --refs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
└── refs
├── cobs
@@ -22,15 +22,15 @@ To remedy this, we can use the `rad fork` command for the project we
wish to fork:
```
-$ rad fork rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Forked repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+$ rad fork rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Forked repository rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
```
Now, if we `rad inspect` the project's refs again we will see that we
have a copy of the main set of refs:
```
-$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --refs
+$ rad inspect rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --refs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
└── refs
├── cobs
diff --git a/radicle-cli/examples/rad-id-collaboration.md b/radicle-cli/examples/rad-id-collaboration.md
index d36892e2a..d75c6826d 100644
--- a/radicle-cli/examples/rad-id-collaboration.md
+++ b/radicle-cli/examples/rad-id-collaboration.md
@@ -7,10 +7,10 @@ First, we'll start off with Alice adding Bob. It's necessary for Bob
to have a fork of the project and Alice must be aware of the fork:
``` ~bob
-$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed
-✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'followed'
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD@[..]..
+$ rad clone rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --scope followed
+✓ Seeding policy updated for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 with scope 'followed'
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkuPZ…xEuaPUp@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkvVv…Z1Ct4tD@[..]..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
@@ -30,8 +30,8 @@ to the default branch, she must set the `threshold` to `2` when adding
Bob as a delegate:
``` ~alice (fails)
-$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Bob" --description "" --threshold 2 --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm -q
-✗ Error: failed to verify delegates for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+$ rad id update --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --title "Add Bob" --description "" --threshold 2 --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm -q
+✗ Error: failed to verify delegates for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
✗ Error: a threshold of 2 delegates cannot be met, found 1 delegate(s) and the following delegates are missing [did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk]
✗ Hint: run `rad follow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk` to follow this missing peer
✗ Hint: run `rad sync -f` to attempt to fetch the newly followed peers
@@ -50,15 +50,15 @@ So, instead Alice needs to first follow Bob and fetch his references:
$ rad follow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --alias bob
✓ Follow policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
$ rad sync
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkuPZ…xEuaPUp@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkvVv…Z1Ct4tD@[..]..
✓ Fetched repository from 2 seed(s)
✓ Synced with 1 node(s)
-$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Bob" --description "" --threshold 2 --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm -q
+$ rad id update --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --title "Add Bob" --description "" --threshold 2 --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm -q
069e7d58faa9a7473d27f5510d676af33282796f
$ rad sync
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkuPZ…xEuaPUp@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkvVv…Z1Ct4tD@[..]..
✓ Fetched repository from 2 seed(s)
✓ Synced with 3 node(s)
```
@@ -67,8 +67,8 @@ Bob can confirm that he was made a delegate by fetching the update:
``` ~bob
$ rad sync
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkuPZ…xEuaPUp@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkvVv…Z1Ct4tD@[..]..
✓ Fetched repository from 2 seed(s)
✓ Synced with 1 node(s)
$ rad inspect --delegates
@@ -81,10 +81,10 @@ project. For Bob to propose Eve, similar steps need to happen as
between Alice and Bob. Eve first needs to setup a fork:
``` ~eve
-$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed
-✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'followed'
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD@[..]..
+$ rad clone rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --scope followed
+✓ Seeding policy updated for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 with scope 'followed'
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkuPZ…xEuaPUp@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkvVv…Z1Ct4tD@[..]..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
@@ -104,11 +104,11 @@ $ git push rad master
Bob then adds Eve as a delegate:
``` ~bob
-$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Eve" --description "" --delegate did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --no-confirm -q
+$ rad id update --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --title "Add Eve" --description "" --delegate did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --no-confirm -q
3cd3c7f9900de0fcb19705856a7cc339a38fb0b3
$ rad sync
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkvVv…Z1Ct4tD@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkuPZ…xEuaPUp@[..]..
✓ Fetched repository from 2 seed(s)
✓ Synced with 3 node(s)
```
@@ -122,8 +122,8 @@ the change to meet a quorum of votes (`votes >= (delegates / 2) + 1`):
``` ~alice
$ rad sync
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkvVv…Z1Ct4tD@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkuPZ…xEuaPUp@[..]..
✓ Fetched repository from 2 seed(s)
✓ Nothing to announce, already in sync with 3 node(s) (see `rad sync status`)
$ rad id list
@@ -134,7 +134,7 @@ $ rad id list
│ ● 069e7d5 Add Bob alice (you) accepted now │
│ ● 0656c21 Initial revision alice (you) accepted now │
╰────────────────────────────────────────────────────────────────────────────────╯
-$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --sigrefs
+$ rad inspect rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --sigrefs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 6acd3b370839318d96dbfff43948bab2bcdd3681
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk c40018821dc1b41cad75e91e0c9d00827e815324
$ rad id accept 3cd3c7f
@@ -157,11 +157,11 @@ since she has become a delegate:
``` ~alice
$ rad sync --timeout 3
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkvVv…Z1Ct4tD@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkuPZ…xEuaPUp@[..]..
✓ Fetched repository from 2 seed(s)
✓ Synced with 3 node(s)
-$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --sigrefs
+$ rad inspect rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --sigrefs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 1f716870f890be0c13fdd0af9f527af849fec792
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk c40018821dc1b41cad75e91e0c9d00827e815324
z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z 95cd447c57de8d232c6154f5dba0451aa593520e
@@ -173,8 +173,8 @@ see that both seeds are `synced`:
``` ~eve
$ rad sync
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkvVv…Z1Ct4tD@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MkuPZ…xEuaPUp@[..]..
✓ Fetched repository from 2 seed(s)
✓ Synced with 3 node(s)
$ rad sync status
diff --git a/radicle-cli/examples/rad-id-conflict.md b/radicle-cli/examples/rad-id-conflict.md
index 49d65306b..d44ecf115 100644
--- a/radicle-cli/examples/rad-id-conflict.md
+++ b/radicle-cli/examples/rad-id-conflict.md
@@ -1,13 +1,13 @@
First let's add Bob as a delegate, and sync the changes to Bob:
``` ~alice
-$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2 -q
-0ca42d376bd566631083c8913cf86bec722da392
+$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -q
+ba5c358894e0a58dd0772fd3eb6d070282dffc26
```
``` ~bob
$ cd heartwood
-$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+$ rad sync --fetch rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
```
@@ -16,28 +16,28 @@ time:
``` ~alice
$ rad id update --title "Edit project name" --description "" --payload "xyz.radicle.project" "name" '"heart"' -q
-12d7300d1bbba84e4e5760c8c61999bf5fefb81a
+46b0a1a441cd1646395e3cf893b99aa258ed7c63
```
``` ~bob
$ rad id update --title "Edit project name" --description "" --payload "xyz.radicle.project" "name" '"wood"' -q
-89b2623e7f2ddf5748661b15b9975ab0b4ee17ab
+8118b11afaf5e43d4446788e0a223ed85e060a56
```
When Alice syncs with Bob, she notices the problem: there are two active
revisions.
``` ~alice
-$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
+$ rad sync --fetch rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
✓ Fetched repository from 1 seed(s)
$ rad id list
╭─────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Status Created │
├─────────────────────────────────────────────────────────────────────────────────┤
-│ ● 89b2623 Edit project name bob z6Mkt67…v4N1tRk active now │
-│ ● 12d7300 Edit project name alice (you) active now │
-│ ● 0ca42d3 Add Bob alice (you) accepted now │
-│ ● 0656c21 Initial revision alice (you) accepted now │
+│ ● 8118b11 Edit project name bob z6Mkt67…v4N1tRk active now │
+│ ● 46b0a1a Edit project name alice (you) active now │
+│ ● ba5c358 Add Bob alice (you) accepted now │
+│ ● eeb8b44 Initial revision alice (you) accepted now │
╰─────────────────────────────────────────────────────────────────────────────────╯
```
@@ -45,15 +45,15 @@ This isn't a problem as long as we don't try to accept both. So let's accept
Bob's:
``` ~alice
-$ rad id accept 89b2623 -q
+$ rad id accept 8118b11 -q
$ rad id list
╭─────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Status Created │
├─────────────────────────────────────────────────────────────────────────────────┤
-│ ● 89b2623 Edit project name bob z6Mkt67…v4N1tRk accepted now │
-│ ● 12d7300 Edit project name alice (you) stale now │
-│ ● 0ca42d3 Add Bob alice (you) accepted now │
-│ ● 0656c21 Initial revision alice (you) accepted now │
+│ ● 8118b11 Edit project name bob z6Mkt67…v4N1tRk accepted now │
+│ ● 46b0a1a Edit project name alice (you) stale now │
+│ ● ba5c358 Add Bob alice (you) accepted now │
+│ ● eeb8b44 Initial revision alice (you) accepted now │
╰─────────────────────────────────────────────────────────────────────────────────╯
```
@@ -61,22 +61,22 @@ Doing so voided the other conflicting revision, and it can no longer be
accepted now.
``` ~bob
-$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+$ rad sync --fetch rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
```
``` ~bob (fail)
-$ rad id accept 12d7300 -q
+$ rad id accept 46b0a1a -q
✗ Error: cannot vote on revision that is stale
-$ rad id reject 12d7300 -q
+$ rad id reject 46b0a1a -q
✗ Error: cannot vote on revision that is stale
```
``` ~bob
-$ rad id show 12d7300
+$ rad id show 46b0a1a
╭────────────────────────────────────────────────────────────────────────╮
│ Title Edit project name │
-│ Revision 12d7300d1bbba84e4e5760c8c61999bf5fefb81a │
-│ Blob e93aa3e3c5c448bacd3537a81daf1437eccd046a │
+│ Revision 46b0a1a441cd1646395e3cf893b99aa258ed7c63 │
+│ Blob 9ae843924509f3f617b044923772171287dab49b │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State stale │
│ Quorum no │
@@ -85,8 +85,9 @@ $ rad id show 12d7300
│ ? did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk bob (you) │
╰────────────────────────────────────────────────────────────────────────╯
-@@ -1,14 +1,14 @@
+@@ -1,22 +1,22 @@
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -99,6 +100,13 @@ $ rad id show 12d7300
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
"did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
],
- "threshold": 2
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ }
}
```
diff --git a/radicle-cli/examples/rad-id-multi-delegate.md b/radicle-cli/examples/rad-id-multi-delegate.md
index d2d057cf7..f278b2562 100644
--- a/radicle-cli/examples/rad-id-multi-delegate.md
+++ b/radicle-cli/examples/rad-id-multi-delegate.md
@@ -1,33 +1,33 @@
``` ~alice
-$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Bob" --description "" --threshold 2 --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm -q
-069e7d58faa9a7473d27f5510d676af33282796f
+$ rad id update --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --title "Add Bob" --description "" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm -q
+f48a2c516aceccde576d9ba8845b21eca1f7902c
```
``` ~bob
-$ rad watch --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --node z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z -r 'refs/rad/sigrefs' -t c9a828fc2fb01f893d6e6e9e17b9092dea2b3aba -i 500 --timeout 5000
-$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+$ rad watch --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --node z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z -r 'refs/rad/sigrefs' -t 6001fa5f08133dcf91029b4fc0b78a59bfd7883a -i 500 --timeout 5000
+$ rad sync --fetch rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
-$ rad id --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+$ rad id --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
╭────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Status Created │
├────────────────────────────────────────────────────────────────────────────────┤
-│ ● 069e7d5 Add Bob alice z6MknSL…StBU8Vi accepted now │
-│ ● 0656c21 Initial revision alice z6MknSL…StBU8Vi accepted now │
+│ ● f48a2c5 Add Bob alice z6MknSL…StBU8Vi accepted now │
+│ ● eeb8b44 Initial revision alice z6MknSL…StBU8Vi accepted now │
╰────────────────────────────────────────────────────────────────────────────────╯
-$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --sigrefs
+$ rad inspect rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --sigrefs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi [..]
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [..]
z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z [..]
-$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --delegates
+$ rad inspect rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --delegates
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
-$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Eve" --description "" --delegate did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --no-confirm
-✓ Identity revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 created
+$ rad id update --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --title "Add Eve" --description "" --delegate did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --no-confirm
+✓ Identity revision 4e7e2aca58c18add67cf117ad414e61645cc39c0 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add Eve │
-│ Revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 │
-│ Blob 74581605d1f75396c331487a10ca61c4815ed685 │
+│ Revision 4e7e2aca58c18add67cf117ad414e61645cc39c0 │
+│ Blob bad2d965c9022797a711cb2031c041ab2e2d729f │
│ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │
│ State active │
│ Quorum no │
@@ -36,8 +36,9 @@ $ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Eve" --des
│ ? did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice │
╰────────────────────────────────────────────────────────────────────────╯
-@@ -1,14 +1,15 @@
+@@ -1,22 +1,23 @@
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -51,24 +52,31 @@ $ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Eve" --des
+ "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk",
+ "did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z"
],
- "threshold": 2
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ }
}
```
``` ~alice
-$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
+$ rad sync --fetch rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkux1…nVhib7Z@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
✓ Fetched repository from 2 seed(s)
-$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --delegates
+$ rad inspect rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --delegates
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
-$ rad id accept 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
-✓ Revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 accepted
+$ rad id accept 4e7e2aca58c18add67cf117ad414e61645cc39c0 --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --no-confirm
+✓ Revision 4e7e2aca58c18add67cf117ad414e61645cc39c0 accepted
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add Eve │
-│ Revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 │
-│ Blob 74581605d1f75396c331487a10ca61c4815ed685 │
+│ Revision 4e7e2aca58c18add67cf117ad414e61645cc39c0 │
+│ Blob bad2d965c9022797a711cb2031c041ab2e2d729f │
│ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │
│ State accepted │
│ Quorum yes │
@@ -76,15 +84,15 @@ $ rad id accept 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 --repo rad:z42hL2jL4XNk
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
│ ✓ did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk bob │
╰────────────────────────────────────────────────────────────────────────╯
-$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --delegates
+$ rad inspect rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --delegates
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z (eve)
```
``` ~alice
-$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Make private" --description "" --visibility private --no-confirm -q
-e6bf10593b78384eb2b281cbb18a605668a6d1f7
+$ rad id update --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --title "Make private" --description "" --visibility private --no-confirm -q
+61a7e9b58b27baf26b6f2e198aea3978e5d4444f
```
We can list all revisions:
@@ -94,34 +102,34 @@ $ rad id list
╭────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Status Created │
├────────────────────────────────────────────────────────────────────────────────┤
-│ ● e6bf105 Make private alice (you) active now │
-│ ● 3cd3c7f Add Eve bob z6Mkt67…v4N1tRk accepted now │
-│ ● 069e7d5 Add Bob alice (you) accepted now │
-│ ● 0656c21 Initial revision alice (you) accepted now │
+│ ● 61a7e9b Make private alice (you) active now │
+│ ● 4e7e2ac Add Eve bob z6Mkt67…v4N1tRk accepted now │
+│ ● f48a2c5 Add Bob alice (you) accepted now │
+│ ● eeb8b44 Initial revision alice (you) accepted now │
╰────────────────────────────────────────────────────────────────────────────────╯
```
Despite being a delegate, Bob can't edit or redact Alice's revision:
``` ~bob (fail)
-$ rad id redact e6bf10593b78384eb2b281cbb18a605668a6d1f7
+$ rad id redact 61a7e9b58b27baf26b6f2e198aea3978e5d4444f
[..]
```
``` ~bob (fail)
-$ rad id edit --title "Boo!" --description "Boo!" e6bf10593b78384eb2b281cbb18a605668a6d1f7
+$ rad id edit --title "Boo!" --description "Boo!" 61a7e9b58b27baf26b6f2e198aea3978e5d4444f
[..]
```
Alice can edit:
``` ~alice
-$ rad id edit --title "Make private" --description "Privacy is cool." e6bf10593b78384eb2b281cbb18a605668a6d1f7
-✓ Revision e6bf10593b78384eb2b281cbb18a605668a6d1f7 edited
-$ rad id show e6bf10593b78384eb2b281cbb18a605668a6d1f7
+$ rad id edit --title "Make private" --description "Privacy is cool." 61a7e9b58b27baf26b6f2e198aea3978e5d4444f
+✓ Revision 61a7e9b58b27baf26b6f2e198aea3978e5d4444f edited
+$ rad id show 61a7e9b58b27baf26b6f2e198aea3978e5d4444f
╭────────────────────────────────────────────────────────────────────────╮
│ Title Make private │
-│ Revision e6bf10593b78384eb2b281cbb18a605668a6d1f7 │
-│ Blob c533865b2846ca6c5b4436ec6872257293380c3b │
+│ Revision 61a7e9b58b27baf26b6f2e198aea3978e5d4444f │
+│ Blob 94448eb3f8b81fba6d25c287c626625fd3f53d8f │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State active │
│ Quorum no │
@@ -133,8 +141,9 @@ $ rad id show e6bf10593b78384eb2b281cbb18a605668a6d1f7
│ ? did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z eve │
╰────────────────────────────────────────────────────────────────────────╯
-@@ -1,15 +1,18 @@
+@@ -1,23 +1,26 @@
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -147,33 +156,39 @@ $ rad id show e6bf10593b78384eb2b281cbb18a605668a6d1f7
"did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk",
"did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z"
],
-- "threshold": 2
-+ "threshold": 2,
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
++ },
+ "visibility": {
+ "type": "private"
-+ }
+ }
}
```
And she can redact her revision:
``` ~alice
-$ rad id redact e6bf10593b78384eb2b281cbb18a605668a6d1f7
-✓ Revision e6bf10593b78384eb2b281cbb18a605668a6d1f7 redacted
+$ rad id redact 61a7e9b58b27baf26b6f2e198aea3978e5d4444f
+✓ Revision 61a7e9b58b27baf26b6f2e198aea3978e5d4444f redacted
```
``` ~alice (fail)
-$ rad id show e6bf10593b78384eb2b281cbb18a605668a6d1f7
-✗ Error: revision `e6bf10593b78384eb2b281cbb18a605668a6d1f7` not found
+$ rad id show 61a7e9b58b27baf26b6f2e198aea3978e5d4444f
+✗ Error: revision `61a7e9b58b27baf26b6f2e198aea3978e5d4444f` not found
```
Finally, Alice can also propose to remove Bob:
``` ~alice
-$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Remove Bob" --description "" --rescind did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm
-✓ Identity revision 8ba242a80bc1181f41f9ea7a19286038c7948994 created
+$ rad id update --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --title "Remove Bob" --description "" --rescind did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm
+✓ Identity revision d8a5c75f44ee99bd66b9f7555066715c552b6fd8 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Remove Bob │
-│ Revision 8ba242a80bc1181f41f9ea7a19286038c7948994 │
-│ Blob 254d62de237117e7d7b9ceff85c47f5e3b610c1e │
+│ Revision d8a5c75f44ee99bd66b9f7555066715c552b6fd8 │
+│ Blob 14913e80b9585dfa545da9feb8ca68a42b5d085e │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State active │
│ Quorum no │
@@ -183,8 +198,9 @@ $ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Remove Bob" --
│ ? did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z eve │
╰────────────────────────────────────────────────────────────────────────╯
-@@ -1,15 +1,14 @@
+@@ -1,23 +1,22 @@
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -197,6 +213,13 @@ $ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Remove Bob" --
- "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk",
"did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z"
],
- "threshold": 2
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ }
}
```
diff --git a/radicle-cli/examples/rad-id-private.md b/radicle-cli/examples/rad-id-private.md
index 63998838b..f0a8ff670 100644
--- a/radicle-cli/examples/rad-id-private.md
+++ b/radicle-cli/examples/rad-id-private.md
@@ -9,6 +9,7 @@ $ rad id update --title "Allow Bob & Eve" --allow did:key:z6Mkt67GdsW7715MEfRuP4
...
$ rad inspect --identity
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -19,7 +20,14 @@ $ rad inspect --identity
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
- "threshold": 1,
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ },
"visibility": {
"type": "private",
"allow": [
@@ -37,6 +45,7 @@ $ rad id update --title "Remove allow list" --disallow did:key:z6Mkt67GdsW7715ME
...
$ rad inspect --identity
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -47,7 +56,14 @@ $ rad inspect --identity
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
- "threshold": 1,
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ },
"visibility": {
"type": "private"
}
diff --git a/radicle-cli/examples/rad-id-threshold-soft-fork.md b/radicle-cli/examples/rad-id-threshold-soft-fork.md
index eb9f18fc8..8de10594b 100644
--- a/radicle-cli/examples/rad-id-threshold-soft-fork.md
+++ b/radicle-cli/examples/rad-id-threshold-soft-fork.md
@@ -3,10 +3,10 @@ without having pushed the canonical default branch. For example, Bob can create
an issue in the repository:
``` ~bob
-$ rad issue open --title "Add Bob as a delegate" --description "We agreed to add me as a delegate, so I am creating an issue to track that work" --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+$ rad issue open --title "Add Bob as a delegate" --description "We agreed to add me as a delegate, so I am creating an issue to track that work" --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
╭──────────────────────────────────────────────────────────────╮
│ Title Add Bob as a delegate │
-│ Issue f12d512c51d30429f7916db038ae0360e2e938c2 │
+│ Issue 9c0484f7b773477d41787d9b0cf772c741b7b4e4 │
│ Author bob (you) │
│ Status open │
│ │
@@ -24,7 +24,7 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
└── refs
├── cobs
│ └── xyz.radicle.id
- │ └── 0656c217f917c3e06234771e9ecae53aba5e173e
+ │ └── eeb8b44890570ccf85db7f3cb2a475100a27408a
├── heads
│ └── master
└── rad
@@ -35,7 +35,7 @@ z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
└── refs
├── cobs
│ └── xyz.radicle.issue
- │ └── f12d512c51d30429f7916db038ae0360e2e938c2
+ │ └── 9c0484f7b773477d41787d9b0cf772c741b7b4e4
└── rad
├── root
└── sigrefs
@@ -46,5 +46,5 @@ as a delegate, since a threshold of 1 can still be reached:
``` ~alice
$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -q
-7be665f9fccba97abb21b2fa85a6fd3181c72858
+ba5c358894e0a58dd0772fd3eb6d070282dffc26
```
diff --git a/radicle-cli/examples/rad-id-threshold.md b/radicle-cli/examples/rad-id-threshold.md
index 482f1b9a9..e5bf03aa6 100644
--- a/radicle-cli/examples/rad-id-threshold.md
+++ b/radicle-cli/examples/rad-id-threshold.md
@@ -6,8 +6,8 @@ branch (`refs/heads/<default branch>` at the top-level of the storage)
can be updated.
``` ~alice (fail)
-$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2
-✗ Error: failed to verify delegates for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+✗ Error: failed to verify delegates for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
✗ Error: the threshold of 2 delegates cannot be met..
✗ Error: the delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk is missing
✗ Hint: run `rad follow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk` to follow this missing peer
@@ -34,6 +34,7 @@ $ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delega
@@ -1,13 +1,14 @@
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -46,7 +47,14 @@ $ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delega
+ "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
+ "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
],
- "threshold": 1
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ }
}
```
@@ -65,7 +73,7 @@ $ git commit -v -m "Define power requirements"
``` ~alice (stderr) RAD_SOCKET=/dev/null
$ git push rad master
✓ Canonical head updated to 3e674d1a1df90807e934f9ae5da2591dd6848a33
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..3e674d1 master -> master
```
@@ -87,7 +95,7 @@ $ git commit -v -m "Add README file"
``` ~alice (stderr) RAD_SOCKET=/dev/null
$ git push rad HEAD:refs/patches
✓ Patch b09b2aa0ee055671c811e9ad4ba73eed211ebaa3 opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -95,8 +103,8 @@ Any other seeds can also still fetch changes from Alice without any
errors:
``` ~seed
-$ rad sync rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji -f
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+$ rad sync rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 -f
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
```
@@ -141,7 +149,6 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
│ └── b09b2aa0ee055671c811e9ad4ba73eed211ebaa3
└── rad
├── id
- ├── root
└── sigrefs
```
@@ -149,7 +156,7 @@ Similarly, she still does not have Bob's `rad/sigrefs`:
``` ~alice
$ rad inspect --sigrefs
-z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi ae3c6b77dc1ed51c1c1e6a2772339c2779fa9ba8
+z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 7ffed605e4871bb0640ee1538181640e239b182c
```
And she can still list the project, without any worries:
@@ -159,7 +166,7 @@ $ rad ls
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
-│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public 3e674d1 Radicle Heartwood Protocol & Stack │
+│ heartwood rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 public 3e674d1 Radicle Heartwood Protocol & Stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -168,10 +175,10 @@ branch to `refs/heads/master` for this project, she can then use `rad
sync` and fetch his references:
``` ~bob
-$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z@[..]..
+$ rad clone rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Seeding policy updated for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 with scope 'all'
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkux1…nVhib7Z@[..]..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
@@ -187,15 +194,15 @@ Run `cd ./heartwood` to go to the repository directory.
``` ~bob
$ cd heartwood
$ rad fork
-✓ Forked repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+✓ Forked repository rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
```
``` ~alice
$ rad sync -f
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkux1…nVhib7Z@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
✓ Fetched repository from 2 seed(s)
$ rad inspect --sigrefs
-z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi ae3c6b77dc1ed51c1c1e6a2772339c2779fa9ba8
-z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk dace6fe948548168a2bb687718949d9b5d9076ee
+z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 7ffed605e4871bb0640ee1538181640e239b182c
+z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk cc068d93ee77dc134518d7d0fbe55b39804baf53
```
diff --git a/radicle-cli/examples/rad-id-unknown-field.md b/radicle-cli/examples/rad-id-unknown-field.md
index 68657dedb..9e2bfc0d6 100644
--- a/radicle-cli/examples/rad-id-unknown-field.md
+++ b/radicle-cli/examples/rad-id-unknown-field.md
@@ -3,11 +3,11 @@ added. Here we will add an emoji alias for the heartwood project:
```
$ rad id update --title "Add emoji alias" --description "Adding alias field" --payload xyz.radicle.project alias '"❤️🪵"'
-✓ Identity revision 05100d3f0a73b9373681677158615a53ba51940e created
+✓ Identity revision d322c2b471685aa25d8874273b6ba2d70d5702de created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add emoji alias │
-│ Revision 05100d3f0a73b9373681677158615a53ba51940e │
-│ Blob a0f421c928dcfc6eca129fc2ea1f50877de7dc20 │
+│ Revision d322c2b471685aa25d8874273b6ba2d70d5702de │
+│ Blob 92e06a7ec3b877ad66c77815ffd5270896d1d898 │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State accepted │
│ Quorum yes │
@@ -17,8 +17,9 @@ $ rad id update --title "Add emoji alias" --description "Adding alias field" --p
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰────────────────────────────────────────────────────────────────────────╯
-@@ -1,13 +1,14 @@
+@@ -1,21 +1,22 @@
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
+ "alias": "❤️🪵",
@@ -30,7 +31,14 @@ $ rad id update --title "Add emoji alias" --description "Adding alias field" --p
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
- "threshold": 1
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ }
}
```
@@ -42,6 +50,6 @@ $ rad ls
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
-│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public f2de534 Radicle Heartwood Protocol & Stack │
+│ heartwood rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 public f2de534 Radicle Heartwood Protocol & Stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-id-update-delete-field.md b/radicle-cli/examples/rad-id-update-delete-field.md
index 843d46521..6de0238cc 100644
--- a/radicle-cli/examples/rad-id-update-delete-field.md
+++ b/radicle-cli/examples/rad-id-update-delete-field.md
@@ -2,11 +2,11 @@ Let's add a payload field and then delete it.
```
$ rad id update --title "Add field" --description "Add a new 'web' field" --payload xyz.radicle.project web '"https://acme.example"'
-✓ Identity revision a8a9fee6c4f83578ab132d375f1da0c81282bef3 created
+✓ Identity revision 4914cd968cd47b7f310946ba6c8e14269ca5a627 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add field │
-│ Revision a8a9fee6c4f83578ab132d375f1da0c81282bef3 │
-│ Blob fbe268d13e60f1f3a1972e0ccd592f3cdecf08b5 │
+│ Revision 4914cd968cd47b7f310946ba6c8e14269ca5a627 │
+│ Blob 74b79e158b1fd4ee3998dd541126d9e14a9ae976 │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State accepted │
│ Quorum yes │
@@ -16,8 +16,9 @@ $ rad id update --title "Add field" --description "Add a new 'web' field" --payl
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰────────────────────────────────────────────────────────────────────────╯
-@@ -1,13 +1,14 @@
+@@ -1,21 +1,22 @@
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -30,7 +31,14 @@ $ rad id update --title "Add field" --description "Add a new 'web' field" --payl
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
- "threshold": 1
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ }
}
```
@@ -38,11 +46,11 @@ Now let's delete it by setting it to `null`.
```
$ rad id update --title "Delete field" --description "Delete 'web'" --payload xyz.radicle.project web null
-✓ Identity revision d373c35876833105f8aafed8b610660b5737cd67 created
+✓ Identity revision 899c3a31664f6433bd92ccfd7fe02a3382de9e47 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Delete field │
-│ Revision d373c35876833105f8aafed8b610660b5737cd67 │
-│ Blob d96f425412c9f8ad5d9a9a05c9831d0728e2338d │
+│ Revision 899c3a31664f6433bd92ccfd7fe02a3382de9e47 │
+│ Blob b38d81ee99d880461a3b7b3502e5d1556e440ef3 │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State accepted │
│ Quorum yes │
@@ -52,8 +60,9 @@ $ rad id update --title "Delete field" --description "Delete 'web'" --payload xy
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰────────────────────────────────────────────────────────────────────────╯
-@@ -1,14 +1,13 @@
+@@ -1,22 +1,21 @@
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -66,7 +75,14 @@ $ rad id update --title "Delete field" --description "Delete 'web'" --payload xy
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
- "threshold": 1
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ }
}
```
@@ -74,5 +90,5 @@ Note that we cannot delete mandatory fields:
``` (fails)
$ rad id update --title "Delete default branch" --payload xyz.radicle.project defaultBranch null
-✗ Error: failed to verify `xyz.radicle.project`, missing field `defaultBranch`
+✗ Error: failed to verify `xyz.radicle.project`: missing field `defaultBranch`
```
diff --git a/radicle-cli/examples/rad-id.md b/radicle-cli/examples/rad-id.md
index 2bc93ed89..04b25406a 100644
--- a/radicle-cli/examples/rad-id.md
+++ b/radicle-cli/examples/rad-id.md
@@ -1,25 +1,21 @@
At some point in the lifetime of a Radicle project you may want to
collaborate with someone else allowing them to become a project
-maintainer. This requires adding them as a `delegate` and possibly
-editing the `threshold` for passing new changes to the identity of the
-project.
+maintainer. This requires adding them as a `delegate`.
-For cases where `threshold > 1`, it is necessary to gather a quorum of
-signatures to update the Radicle identity. To do this, we use the `rad id`
-command. For now, since we are the only delegate, and `treshold` is `1`, we
-can update the identity ourselves.
+For changes made to the identity, a majority of delegate signatures is required.
+For now, since we are the only delegate, we can update the identity ourselves.
Let's add Bob as a delegate using their DID,
`did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk`, and update the
threshold to `2`.
```
-$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2
-✓ Identity revision 0ca42d376bd566631083c8913cf86bec722da392 created
+$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+✓ Identity revision ba5c358894e0a58dd0772fd3eb6d070282dffc26 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add Bob │
-│ Revision 0ca42d376bd566631083c8913cf86bec722da392 │
-│ Blob 053541ba7b90534b35dd8718e0ceaa408979b02b │
+│ Revision ba5c358894e0a58dd0772fd3eb6d070282dffc26 │
+│ Blob 8aa049fbaa433f84073983964a54ab909cb2fe9a │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State accepted │
│ Quorum yes │
@@ -27,10 +23,12 @@ $ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delega
│ Add Bob as a delegate │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
+│ ? did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk bob │
╰────────────────────────────────────────────────────────────────────────╯
-@@ -1,13 +1,14 @@
+@@ -1,21 +1,22 @@
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -43,8 +41,14 @@ $ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delega
+ "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
+ "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
],
-- "threshold": 1
-+ "threshold": 2
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ }
}
```
@@ -76,6 +80,7 @@ can verify that by listing the current identity document:
```
$ rad inspect --identity
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -87,35 +92,42 @@ $ rad inspect --identity
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
"did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
],
- "threshold": 2
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ }
}
```
We can also look at the document's COB directly:
```
-$ rad cob log --object 0656c217f917c3e06234771e9ecae53aba5e173e --type xyz.radicle.id --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-commit 0ca42d376bd566631083c8913cf86bec722da392
-parent 0656c217f917c3e06234771e9ecae53aba5e173e
+$ rad cob log --object eeb8b44 --type xyz.radicle.id --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+commit ba5c358894e0a58dd0772fd3eb6d070282dffc26
+parent eeb8b44890570ccf85db7f3cb2a475100a27408a
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date Thu, 15 Dec 2022 17:28:04 +0000
{
- "blob": "053541ba7b90534b35dd8718e0ceaa408979b02b",
+ "blob": "8aa049fbaa433f84073983964a54ab909cb2fe9a",
"description": "Add Bob as a delegate",
- "parent": "0656c217f917c3e06234771e9ecae53aba5e173e",
- "signature": "z3AyzixN2eWLtRfQWowtBXwWyRH3iJ8oJ25W6KFYFw5ANLntbzfavge15muNU6AVAUkxSxQvgg9yh2gupbUecavQY",
+ "parent": "eeb8b44890570ccf85db7f3cb2a475100a27408a",
+ "signature": "z23hpnKuBai93fnjm6qJeTtPrT7hDeLUJQLmmoE8xbgFrKCUYjYf6ZrgFKZLL8PqhMnNJTJcfmrZcABUzum2SGiju",
"title": "Add Bob",
"type": "revision"
}
-commit 0656c217f917c3e06234771e9ecae53aba5e173e
+commit eeb8b44890570ccf85db7f3cb2a475100a27408a
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date Thu, 15 Dec 2022 17:28:04 +0000
{
- "blob": "d96f425412c9f8ad5d9a9a05c9831d0728e2338d",
+ "blob": "b38d81ee99d880461a3b7b3502e5d1556e440ef3",
"parent": null,
- "signature": "z5nGqUvrmfiSyLjNCHWTWYvVMcPUZcvo9TxPKzEKXYBdSgUzbrqf1cYsmpGgbQvYunnsrLSsubEmxZaRdKM4quqQR",
+ "signature": "z246mVBUXJmr3YYeiTE7yuYteiHvA3bnqUWASB6VBnEbn6JB6eAxLv8mCGvCqaRL4BgVcn1Aho5fnVUqSdhR44SHv",
"title": "Initial revision",
"type": "revision"
}
@@ -125,11 +137,11 @@ date Thu, 15 Dec 2022 17:28:04 +0000
We can use `rad id show` to show the changes of an accepted update:
```
-$ rad id show 0ca42d376bd566631083c8913cf86bec722da392
+$ rad id show ba5c358894e0a58dd0772fd3eb6d070282dffc26
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add Bob │
-│ Revision 0ca42d376bd566631083c8913cf86bec722da392 │
-│ Blob 053541ba7b90534b35dd8718e0ceaa408979b02b │
+│ Revision ba5c358894e0a58dd0772fd3eb6d070282dffc26 │
+│ Blob 8aa049fbaa433f84073983964a54ab909cb2fe9a │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State accepted │
│ Quorum yes │
@@ -139,8 +151,9 @@ $ rad id show 0ca42d376bd566631083c8913cf86bec722da392
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰────────────────────────────────────────────────────────────────────────╯
-@@ -1,13 +1,14 @@
+@@ -1,21 +1,22 @@
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -153,8 +166,14 @@ $ rad id show 0ca42d376bd566631083c8913cf86bec722da392
+ "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
+ "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
],
-- "threshold": 1
-+ "threshold": 2
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ }
}
```
@@ -162,15 +181,15 @@ Note that once a revision is accepted, it can't be edited, redacted or otherwise
acted upon:
``` (fail)
-$ rad id redact 0ca42d376bd566631083c8913cf86bec722da392
+$ rad id redact ba5c358894e0a58dd0772fd3eb6d070282dffc26
✗ Error: [..]
```
``` (fail)
-$ rad id reject 0ca42d376bd566631083c8913cf86bec722da392
+$ rad id reject ba5c358894e0a58dd0772fd3eb6d070282dffc26
✗ Error: [..]
```
``` (fail)
-$ rad id accept 0ca42d376bd566631083c8913cf86bec722da392
+$ rad id accept ba5c358894e0a58dd0772fd3eb6d070282dffc26
✗ Error: [..]
```
diff --git a/radicle-cli/examples/rad-inbox.md b/radicle-cli/examples/rad-inbox.md
index 13a714a8c..cdab35c5f 100644
--- a/radicle-cli/examples/rad-inbox.md
+++ b/radicle-cli/examples/rad-inbox.md
@@ -125,6 +125,7 @@ $ rad inbox --all
╰──────────────────────────────────────────────────────────────────────╯
$ rad inbox show 1
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -135,6 +136,13 @@ $ rad inbox show 1
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
- "threshold": 1
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ }
}
```
diff --git a/radicle-cli/examples/rad-init-existing.md b/radicle-cli/examples/rad-init-existing.md
index a9bf5879b..bc9a76868 100644
--- a/radicle-cli/examples/rad-init-existing.md
+++ b/radicle-cli/examples/rad-init-existing.md
@@ -14,24 +14,24 @@ $ rad .
Let's pick an existing repository:
```
-$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+$ rad inspect rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
```
And initialize this working copy as that existing repository:
```
-$ rad init --existing rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Initialized existing repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji in [..]/heartwood/..
+$ rad init --existing rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Initialized existing repository rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 in [..]/heartwood/..
```
We can confirm that the working copy is initialized:
```
$ rad .
-rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
$ git remote show rad
* remote rad
- Fetch URL: rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
- Push URL: rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ Fetch URL: rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+ Push URL: rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
HEAD branch: (unknown)
Remote branch:
master new (next fetch will store in remotes/rad)
diff --git a/radicle-cli/examples/rad-init-no-seed.md b/radicle-cli/examples/rad-init-no-seed.md
index 0597b3ccc..33fc46688 100644
--- a/radicle-cli/examples/rad-init-no-seed.md
+++ b/radicle-cli/examples/rad-init-no-seed.md
@@ -6,7 +6,7 @@ Initializing public radicle 👾 repository in [..]
✓ Repository heartwood created.
-Your Repository ID (RID) is rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK.
+Your Repository ID (RID) is rad:z3Lr338KCqbiwiLSh9DQZxTiLQUHg.
You can show it any time by running `rad .` from this directory.
Your repository will be announced to the network when you start your node.
@@ -19,11 +19,11 @@ $ rad node inventory
If we then seed it, it becomes advertized in our inventory:
```
-$ rad seed rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK
-✓ Inventory updated with rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK
-✓ Seeding policy updated for rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK with scope 'all'
+$ rad seed rad:z3Lr338KCqbiwiLSh9DQZxTiLQUHg
+✓ Inventory updated with rad:z3Lr338KCqbiwiLSh9DQZxTiLQUHg
+✓ Seeding policy updated for rad:z3Lr338KCqbiwiLSh9DQZxTiLQUHg with scope 'all'
```
```
$ rad node inventory
-rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK
+rad:z3Lr338KCqbiwiLSh9DQZxTiLQUHg
```
diff --git a/radicle-cli/examples/rad-init-private-clone-seed.md b/radicle-cli/examples/rad-init-private-clone-seed.md
index 859f86dce..4ca1e864c 100644
--- a/radicle-cli/examples/rad-init-private-clone-seed.md
+++ b/radicle-cli/examples/rad-init-private-clone-seed.md
@@ -1,4 +1,4 @@
-Given a private repo `rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu` belonging to Alice,
+Given a private repo `rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT` belonging to Alice,
Alice allows Bob to fetch it, and Bob, without the updated identity document
is able to fetch it by specifiying Alice as a seed.
@@ -7,6 +7,7 @@ $ rad id update --title "Allow Bob" --description "" --allow did:key:z6Mkt67GdsW
...
$ rad inspect --identity
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -17,7 +18,14 @@ $ rad inspect --identity
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
- "threshold": 1,
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ },
"visibility": {
"type": "private",
"allow": [
@@ -29,9 +37,9 @@ $ rad inspect --identity
``` ~bob
$ rad ls --all --private
-$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
-✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
-✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi@[..]..
+$ rad clone rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
+✓ Seeding policy updated for rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT with scope 'all'
+✓ Fetching rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT from z6MknSL…StBU8Vi@[..]..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
@@ -47,7 +55,7 @@ Run `cd ./heartwood` to go to the repository directory.
We can also use `rad seed` to seed and fetch without creating a checkout.
``` ~bob
-$ rad seed rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --from z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
-✓ Seeding policy exists for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
-✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi@[..]..
+$ rad seed rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT --from z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
+✓ Seeding policy exists for rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT with scope 'all'
+✓ Fetching rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT from z6MknSL…StBU8Vi@[..]..
```
diff --git a/radicle-cli/examples/rad-init-private-clone.md b/radicle-cli/examples/rad-init-private-clone.md
index 89f94965e..d9f0718ce 100644
--- a/radicle-cli/examples/rad-init-private-clone.md
+++ b/radicle-cli/examples/rad-init-private-clone.md
@@ -1,14 +1,14 @@
-Given a private repo `rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu` belonging to Alice,
+Given a private repo `rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT` belonging to Alice,
Bob tries to fetch it, and even though he's connected to Alice, it fails.
``` ~bob
$ rad ls
```
``` ~bob (fail)
-$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
-✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
-✗ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi@[..].. error: failed to perform fetch handshake
-✗ Error: repository rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu not found
+$ rad clone rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
+✓ Seeding policy updated for rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT with scope 'all'
+✗ Fetching rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT from z6MknSL…StBU8Vi@[..].. error: failed to perform fetch handshake
+✗ Error: repository rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT not found
```
She allows Bob to view the repository. And when she syncs, one node (Bob) gets
@@ -25,14 +25,14 @@ Bob can now fetch the private repo without specifying a seed, because he knows
that Alice has the repo after she announced her refs:
``` ~bob
-$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch
-✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi@[..]..
+$ rad sync rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT --fetch
+✓ Fetching rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
$ rad ls --private --all
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
-│ heartwood rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu private f2de534 radicle heartwood protocol & stack │
+│ heartwood rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT private f2de534 radicle heartwood protocol & stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-init-private-no-seed.md b/radicle-cli/examples/rad-init-private-no-seed.md
index 9b3bd246b..991114d1e 100644
--- a/radicle-cli/examples/rad-init-private-no-seed.md
+++ b/radicle-cli/examples/rad-init-private-no-seed.md
@@ -9,7 +9,7 @@ Initializing private radicle 👾 repository in [..]
✓ Repository heartwood created.
-Your Repository ID (RID) is rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu.
+Your Repository ID (RID) is rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT.
You can show it any time by running `rad .` from this directory.
You have created a private repository.
@@ -27,8 +27,8 @@ No seeding policies to show.
We can decide to seed it later, so that others can fetch it from us, given
that they are part of the allow list:
```
-$ rad seed rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu
-✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
+$ rad seed rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT
+✓ Seeding policy updated for rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT with scope 'all'
```
But it still won't show up in our inventory, since it's private:
diff --git a/radicle-cli/examples/rad-init-private-seed.md b/radicle-cli/examples/rad-init-private-seed.md
index 501325300..3b3704ac0 100644
--- a/radicle-cli/examples/rad-init-private-seed.md
+++ b/radicle-cli/examples/rad-init-private-seed.md
@@ -9,22 +9,22 @@ $ rad id update --title "Allow Bob" --description "" --allow did:key:z6Mkt67GdsW
First, Bob seeds the repo.
``` ~bob
-$ rad seed rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --no-fetch
+$ rad seed rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT --no-fetch
[..]
```
If Bob just tries to fetch it without specifying seeds, he gets an error:
``` ~bob
-$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch
-✗ Error: no seeds found for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu
+$ rad sync rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT --fetch
+✗ Error: no seeds found for rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT
```
He has to specify a seed that isn't in his routing table:
``` ~bob
-$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
-✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi@[..]..
+$ rad sync rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Fetching rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
```
@@ -33,7 +33,7 @@ $ rad ls --private --all
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
-│ heartwood rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu private f2de534 radicle heartwood protocol & stack │
+│ heartwood rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT private f2de534 radicle heartwood protocol & stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -41,8 +41,8 @@ Note that if multiple seeds are specified, the command succeeds as long as one
seed succeeds.
``` ~bob
-$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --seed z6MkwPUeUS2fJMfc2HZN1RQTQcTTuhw4HhPySB8JeUg2mVvx
-✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi@[..]..
+$ rad sync rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --seed z6MkwPUeUS2fJMfc2HZN1RQTQcTTuhw4HhPySB8JeUg2mVvx
+✓ Fetching rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT from z6MknSL…StBU8Vi@[..]..
! Warning: no addresses found for z6MkwPUeUS2fJMfc2HZN1RQTQcTTuhw4HhPySB8JeUg2mVvx, skipping..
✓ Fetched repository from 1 seed(s)
```
diff --git a/radicle-cli/examples/rad-init-private.md b/radicle-cli/examples/rad-init-private.md
index 02654415b..ce08a2755 100644
--- a/radicle-cli/examples/rad-init-private.md
+++ b/radicle-cli/examples/rad-init-private.md
@@ -7,7 +7,7 @@ Initializing private radicle 👾 repository in [..]
✓ Repository heartwood created.
-Your Repository ID (RID) is rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu.
+Your Repository ID (RID) is rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT.
You can show it any time by running `rad .` from this directory.
You have created a private repository.
@@ -25,6 +25,6 @@ $ rad seed
╭────────────────────────────────────────────────────────────────╮
│ Repository Name Policy Scope │
├────────────────────────────────────────────────────────────────┤
-│ rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu heartwood allow all │
+│ rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT heartwood allow all │
╰────────────────────────────────────────────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-init-sync-not-connected.md b/radicle-cli/examples/rad-init-sync-not-connected.md
index a2a7d830f..d40cd862b 100644
--- a/radicle-cli/examples/rad-init-sync-not-connected.md
+++ b/radicle-cli/examples/rad-init-sync-not-connected.md
@@ -7,7 +7,7 @@ Initializing public radicle 👾 repository in [..]
✓ Repository heartwood created.
-Your Repository ID (RID) is rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji.
+Your Repository ID (RID) is rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2.
You can show it any time by running `rad .` from this directory.
✗ Announcing.. <canceled>
diff --git a/radicle-cli/examples/rad-init-sync-preferred.md b/radicle-cli/examples/rad-init-sync-preferred.md
index 7698ccc54..75559f5ee 100644
--- a/radicle-cli/examples/rad-init-sync-preferred.md
+++ b/radicle-cli/examples/rad-init-sync-preferred.md
@@ -7,7 +7,7 @@ Initializing public radicle 👾 repository in [..]
✓ Repository heartwood created.
-Your Repository ID (RID) is rad:z3Rry7rpdWuGpfjPYGzdJKQADsoNW.
+Your Repository ID (RID) is rad:z2eCRs3yG5orX2AqYiozcedMzbwg5.
You can show it any time by running `rad .` from this directory.
✓ Repository successfully synced to z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
@@ -16,7 +16,7 @@ You can show it any time by running `rad .` from this directory.
Your repository has been synced to the network and is now discoverable by peers.
View it in your browser at:
- https://app.radicle.xyz/nodes/[...]/rad:z3Rry7rpdWuGpfjPYGzdJKQADsoNW
+ https://app.radicle.xyz/nodes/[...]/rad:z2eCRs3yG5orX2AqYiozcedMzbwg5
To push changes, run `git push`.
```
diff --git a/radicle-cli/examples/rad-init-sync-timeout.md b/radicle-cli/examples/rad-init-sync-timeout.md
index 911dc2627..298179b75 100644
--- a/radicle-cli/examples/rad-init-sync-timeout.md
+++ b/radicle-cli/examples/rad-init-sync-timeout.md
@@ -8,7 +8,7 @@ Initializing public radicle 👾 repository in [..]
✓ Repository heartwood created.
-Your Repository ID (RID) is rad:z3Rry7rpdWuGpfjPYGzdJKQADsoNW.
+Your Repository ID (RID) is rad:z2eCRs3yG5orX2AqYiozcedMzbwg5.
You can show it any time by running `rad .` from this directory.
✓ Repository successfully announced to the network.
diff --git a/radicle-cli/examples/rad-init-sync.md b/radicle-cli/examples/rad-init-sync.md
index 0a9aa4134..00dc5ab35 100644
--- a/radicle-cli/examples/rad-init-sync.md
+++ b/radicle-cli/examples/rad-init-sync.md
@@ -9,7 +9,7 @@ Initializing public radicle 👾 repository in [..]
✓ Repository heartwood created.
-Your Repository ID (RID) is rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji.
+Your Repository ID (RID) is rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2.
You can show it any time by running `rad .` from this directory.
✓ Repository successfully announced to the network.
diff --git a/radicle-cli/examples/rad-init-with-existing-remote.md b/radicle-cli/examples/rad-init-with-existing-remote.md
index 323860367..c4d7bf0c6 100644
--- a/radicle-cli/examples/rad-init-with-existing-remote.md
+++ b/radicle-cli/examples/rad-init-with-existing-remote.md
@@ -26,7 +26,7 @@ Initializing public radicle 👾 repository in [..]
✓ Repository heartwood created.
-Your Repository ID (RID) is rad:z2D6wQnKapY7dn5meBnbH2rUKNZbT.
+Your Repository ID (RID) is rad:z4JnKDURwrqarjs9EkCSiY7QExoHz.
You can show it any time by running `rad .` from this directory.
Your repository will be announced to the network when you start your node.
diff --git a/radicle-cli/examples/rad-init.md b/radicle-cli/examples/rad-init.md
index f2eef99b1..1ff7c9c27 100644
--- a/radicle-cli/examples/rad-init.md
+++ b/radicle-cli/examples/rad-init.md
@@ -14,7 +14,7 @@ Initializing public radicle 👾 repository in [..]
"defaultBranch": "master"
}
-Your Repository ID (RID) is rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji.
+Your Repository ID (RID) is rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2.
You can show it any time by running `rad .` from this directory.
Your repository will be announced to the network when you start your node.
@@ -26,7 +26,7 @@ If we try to initialize it again, we get an error:
``` (fail)
$ rad init
-✗ Error: repository is already initialized with remote rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+✗ Error: repository is already initialized with remote rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
```
Repositories can be listed with the `ls` command:
@@ -36,7 +36,7 @@ $ rad ls
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
-│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public f2de534 Radicle Heartwood Protocol & Stack │
+│ heartwood rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 public f2de534 Radicle Heartwood Protocol & Stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -44,5 +44,5 @@ Public repositories are added to our inventory:
```
$ rad node inventory
-rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
```
diff --git a/radicle-cli/examples/rad-inspect-noauth.md b/radicle-cli/examples/rad-inspect-noauth.md
index 212784906..2c82a5550 100644
--- a/radicle-cli/examples/rad-inspect-noauth.md
+++ b/radicle-cli/examples/rad-inspect-noauth.md
@@ -8,5 +8,5 @@ $ rad self
```
$ rad inspect
-rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
```
diff --git a/radicle-cli/examples/rad-inspect.md b/radicle-cli/examples/rad-inspect.md
index ee8061c62..61d829747 100644
--- a/radicle-cli/examples/rad-inspect.md
+++ b/radicle-cli/examples/rad-inspect.md
@@ -3,14 +3,14 @@ command from inside a working copy:
```
$ rad inspect
-rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
```
As a shorthand, you can also simply use `rad .`:
```
$ rad .
-rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
```
It's also possible to display all of the repository's git references:
@@ -21,7 +21,7 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
└── refs
├── cobs
│ └── xyz.radicle.id
- │ └── 0656c217f917c3e06234771e9ecae53aba5e173e
+ │ └── eeb8b44890570ccf85db7f3cb2a475100a27408a
├── heads
│ └── master
└── rad
@@ -34,7 +34,7 @@ And sigrefs:
```
$ rad inspect --sigrefs
-z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 99c549702e2bcfe02b0e68d4a2224fb7a1524529
+z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 7c1445cd018b1b0f51e0d815c3c03b289140eafa
```
Or display the repository identity's payload and delegates:
@@ -57,13 +57,14 @@ history:
```
$ rad inspect --history
-commit 0656c217f917c3e06234771e9ecae53aba5e173e
-blob d96f425412c9f8ad5d9a9a05c9831d0728e2338d
+commit eeb8b44890570ccf85db7f3cb2a475100a27408a
+blob b38d81ee99d880461a3b7b3502e5d1556e440ef3
date Thu, 15 Dec 2022 17:28:04 +0000
Initialize identity
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -74,7 +75,14 @@ date Thu, 15 Dec 2022 17:28:04 +0000
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
- "threshold": 1
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
+ }
}
```
diff --git a/radicle-cli/examples/rad-issue.md b/radicle-cli/examples/rad-issue.md
index 06951bfcc..e617d0444 100644
--- a/radicle-cli/examples/rad-issue.md
+++ b/radicle-cli/examples/rad-issue.md
@@ -7,7 +7,7 @@ Let's say the new car you are designing with your peers has a problem with its f
$ rad issue open --title "flux capacitor underpowered" --description "Flux capacitor power requirements exceed current supply" --no-announce
╭─────────────────────────────────────────────────────────╮
│ Title flux capacitor underpowered │
-│ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │
+│ Issue 0d18c610be2fbb4f47d45434c581f3bf0b0ff071 │
│ Author alice (you) │
│ Status open │
│ │
@@ -22,17 +22,17 @@ $ rad issue list
╭──────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Labels Assignees Opened │
├──────────────────────────────────────────────────────────────────────────────────────────┤
-│ ● d87dcfe flux capacitor underpowered alice (you) now │
+│ ● 0d18c61 flux capacitor underpowered alice (you) now │
╰──────────────────────────────────────────────────────────────────────────────────────────╯
```
Show the issue information issue.
```
-$ rad issue show d87dcfe
+$ rad issue show 0d18c61
╭─────────────────────────────────────────────────────────╮
│ Title flux capacitor underpowered │
-│ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │
+│ Issue 0d18c610be2fbb4f47d45434c581f3bf0b0ff071 │
│ Author alice (you) │
│ Status open │
│ │
@@ -51,8 +51,8 @@ Let's assign ourselves to this one, this is to ensure work is not
duplicated. While we're at it, let's add a label.
```
-$ rad issue assign d87dcfe --add did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --no-announce
-$ rad issue label d87dcfe --add good-first-issue --no-announce
+$ rad issue assign 0d18c61 --add did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --no-announce
+$ rad issue label 0d18c61 --add good-first-issue --no-announce
```
It will now show in the list of issues assigned to us, along with the new label.
@@ -62,14 +62,14 @@ $ rad issue list --assigned
╭────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Labels Assignees Opened │
├────────────────────────────────────────────────────────────────────────────────────────────────────┤
-│ ● d87dcfe flux capacitor underpowered alice (you) good-first-issue alice now │
+│ ● 0d18c61 flux capacitor underpowered alice (you) good-first-issue alice now │
╰────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
Note: this can always be undone with the `unassign` subcommand.
```
-$ rad issue assign d87dcfe --delete did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --no-announce
+$ rad issue assign 0d18c61 --delete did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --no-announce
```
Great, now we have communicated to the world about our car's defect.
@@ -78,29 +78,29 @@ But wait! We've found an important detail about the car's power requirements.
It will help whoever works on a fix.
```
-$ rad issue comment d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --message 'The flux capacitor needs 1.21 Gigawatts' -q --no-announce
-2193e871916d18ddd0416b5198cb08c5dc7416b7
-$ rad issue comment d87dcfe8c2b3200e78b128d9b959cfdf7063fefe --reply-to 2193e871916d18ddd0416b5198cb08c5dc7416b7 --message 'More power!' -q --no-announce
-880fdcd94b36e412fc906b510f41008700d65424
+$ rad issue comment 0d18c610be2fbb4f47d45434c581f3bf0b0ff071 --message 'The flux capacitor needs 1.21 Gigawatts' -q --no-announce
+30d72f0b1e6a96e39c4c408369ea44186430d21b
+$ rad issue comment 0d18c610be2fbb4f47d45434c581f3bf0b0ff071 --reply-to 30d72f0b1e6a96e39c4c408369ea44186430d21b --message 'More power!' -q --no-announce
+4ff4fdc106a4fb3a2a035155e1fb3e8b3c1e0df4
```
We can see our comments by showing the issue:
```
-$ rad issue show d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
+$ rad issue show 0d18c610be2fbb4f47d45434c581f3bf0b0ff071
╭─────────────────────────────────────────────────────────╮
│ Title flux capacitor underpowered │
-│ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │
+│ Issue 0d18c610be2fbb4f47d45434c581f3bf0b0ff071 │
│ Author alice (you) │
│ Labels good-first-issue │
│ Status open │
│ │
│ Flux capacitor power requirements exceed current supply │
├─────────────────────────────────────────────────────────┤
-│ alice (you) now 2193e87 │
+│ alice (you) now 30d72f0 │
│ The flux capacitor needs 1.21 Gigawatts │
├─────────────────────────────────────────────────────────┤
-│ alice (you) now 880fdcd │
+│ alice (you) now 4ff4fdc │
│ More power! │
╰─────────────────────────────────────────────────────────╯
```
@@ -108,9 +108,9 @@ $ rad issue show d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
We can also edit a comment:
```
-$ rad issue comment d87dcfe --edit 880fdcd -m "Even more power!"
+$ rad issue comment 0d18c61 --edit 4ff4fdc -m "Even more power!"
╭─────────────────────────╮
-│ alice (you) now 880fdcd │
+│ alice (you) now 4ff4fdc │
│ Even more power! │
╰─────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-job.md b/radicle-cli/examples/rad-job.md
index a28668b39..5616a9d0a 100644
--- a/radicle-cli/examples/rad-job.md
+++ b/radicle-cli/examples/rad-job.md
@@ -2,7 +2,7 @@ The `rad job` command lets you manage job COBs. Let's first checkout the
`heartwood` repository:
```
-$ rad checkout rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+$ rad checkout rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
✓ Repository checkout successful under ./heartwood
$ cd heartwood
```
diff --git a/radicle-cli/examples/rad-merge-after-update.md b/radicle-cli/examples/rad-merge-after-update.md
index 0fe08a2a3..9acf06895 100644
--- a/radicle-cli/examples/rad-merge-after-update.md
+++ b/radicle-cli/examples/rad-merge-after-update.md
@@ -4,8 +4,8 @@ Let's start by creating a patch.
$ git checkout -b feature/1 -q
$ git commit --allow-empty -q -m "First change"
$ git push rad HEAD:refs/patches
-✓ Patch 696ec5508494692899337afe6713fe1796d0315c opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -16,8 +16,8 @@ $ git commit --amend --allow-empty -q -m "Amended change"
$ git checkout master -q
$ git merge feature/1 -q
$ git push rad master
-✓ Canonical head updated to 954bcdb5008447ce294a61a21d7eb87afbe7f4a6
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Canonical head for refs/heads/master updated to 954bcdb5008447ce294a61a21d7eb87afbe7f4a6
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..954bcdb master -> master
```
@@ -27,8 +27,8 @@ update it, we expect it to be updated and merged:
``` (stderr) RAD_SOCKET=/dev/null
$ git checkout feature/1 -q
$ git push -f
-✓ Patch 696ec55 updated to revision [...]
-✓ Patch 696ec5508494692899337afe6713fe1796d0315c merged
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- + 20aa5dd...954bcdb feature/1 -> patches/696ec5508494692899337afe6713fe1796d0315c (forced update)
+✓ Patch 09a3de4 updated to revision [...]
+✓ Patch 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084 merged
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ + 20aa5dd...954bcdb feature/1 -> patches/09a3de4ac2c4d012c4a9c84c0cb306a066a0b084 (forced update)
```
diff --git a/radicle-cli/examples/rad-merge-no-ff.md b/radicle-cli/examples/rad-merge-no-ff.md
index f6de66bcb..0c874d2b1 100644
--- a/radicle-cli/examples/rad-merge-no-ff.md
+++ b/radicle-cli/examples/rad-merge-no-ff.md
@@ -4,8 +4,8 @@ First, let's create a patch.
$ git checkout -b feature/1 -q
$ git commit --allow-empty -q -m "First change"
$ git push rad HEAD:refs/patches
-✓ Patch 696ec5508494692899337afe6713fe1796d0315c opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -36,8 +36,8 @@ committer radicle <radicle@localhost> 1671125284 +0000
Finally, we push master and expect the patch to be merged.
``` (stderr) RAD_SOCKET=/dev/null
$ git push rad master
-✓ Patch 696ec5508494692899337afe6713fe1796d0315c merged
-✓ Canonical head updated to 737a10cfa29111afeb0d43cf3545cee386b939ec
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084 merged
+✓ Canonical head for refs/heads/master updated to 737a10cfa29111afeb0d43cf3545cee386b939ec
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..737a10c master -> master
```
diff --git a/radicle-cli/examples/rad-merge-via-push.md b/radicle-cli/examples/rad-merge-via-push.md
index 27aa55cf0..a348dd77d 100644
--- a/radicle-cli/examples/rad-merge-via-push.md
+++ b/radicle-cli/examples/rad-merge-via-push.md
@@ -1,3 +1,4 @@
+1
Let's start by creating two patches.
```
@@ -7,8 +8,8 @@ $ git commit --allow-empty -m "First change"
```
``` (stderr) RAD_SOCKET=/dev/null
$ git push rad HEAD:refs/patches
-✓ Patch 696ec5508494692899337afe6713fe1796d0315c opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
```
@@ -18,8 +19,8 @@ $ git commit --allow-empty -m "Second change"
```
``` (stderr) RAD_SOCKET=/dev/null
$ git push rad HEAD:refs/patches
-✓ Patch 356f73863a8920455ff6e77cd9c805d68910551b opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 0e8cc60585b6bb6a1236dc9958bf09883ecba9f3 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -28,8 +29,8 @@ This creates some remote tracking branches for us:
```
$ git branch -r
rad/master
- rad/patches/356f73863a8920455ff6e77cd9c805d68910551b
- rad/patches/696ec5508494692899337afe6713fe1796d0315c
+ rad/patches/09a3de4ac2c4d012c4a9c84c0cb306a066a0b084
+ rad/patches/0e8cc60585b6bb6a1236dc9958bf09883ecba9f3
```
And some remote refs:
@@ -40,15 +41,15 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
└── refs
├── cobs
│ ├── xyz.radicle.id
- │ │ └── 0656c217f917c3e06234771e9ecae53aba5e173e
+ │ │ └── eeb8b44890570ccf85db7f3cb2a475100a27408a
│ └── xyz.radicle.patch
- │ ├── 356f73863a8920455ff6e77cd9c805d68910551b
- │ └── 696ec5508494692899337afe6713fe1796d0315c
+ │ ├── 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084
+ │ └── 0e8cc60585b6bb6a1236dc9958bf09883ecba9f3
├── heads
│ ├── master
│ └── patches
- │ ├── 356f73863a8920455ff6e77cd9c805d68910551b
- │ └── 696ec5508494692899337afe6713fe1796d0315c
+ │ ├── 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084
+ │ └── 0e8cc60585b6bb6a1236dc9958bf09883ecba9f3
└── rad
├── id
├── root
@@ -68,10 +69,10 @@ When we push to `rad/master`, we automatically merge the patches:
``` (stderr) RAD_SOCKET=/dev/null
$ git push rad master
-✓ Patch 356f73863a8920455ff6e77cd9c805d68910551b merged
-✓ Patch 696ec5508494692899337afe6713fe1796d0315c merged
-✓ Canonical head updated to d6399c71702b40bae00825b3c444478d06b4e91c
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084 merged
+✓ Patch 0e8cc60585b6bb6a1236dc9958bf09883ecba9f3 merged
+✓ Canonical head for refs/heads/master updated to d6399c71702b40bae00825b3c444478d06b4e91c
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..d6399c7 master -> master
```
```
@@ -79,28 +80,13 @@ $ rad patch --merged
╭─────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │
├─────────────────────────────────────────────────────────────────────────────┤
-│ ✔ [ ... ] Second change alice (you) - daf349f +0 -0 now │
│ ✔ [ ... ] First change alice (you) - 20aa5dd +0 -0 now │
+│ ✔ [ ... ] Second change alice (you) - daf349f +0 -0 now │
╰─────────────────────────────────────────────────────────────────────────────╯
-$ rad patch show 696ec5508494692899337afe6713fe1796d0315c
-╭────────────────────────────────────────────────────────────────╮
-│ Title First change │
-│ Patch 696ec5508494692899337afe6713fe1796d0315c │
-│ Author alice (you) │
-│ Head 20aa5dde6210796c3a2f04079b42316a31d02689 │
-│ Branches feature/1 │
-│ Commits ahead 0, behind 2 │
-│ Status merged │
-├────────────────────────────────────────────────────────────────┤
-│ 20aa5dd First change │
-├────────────────────────────────────────────────────────────────┤
-│ ● opened by alice (you) (20aa5dd) now │
-│ └─ ✓ merged by alice (you) at revision 696ec55 (20aa5dd) now │
-╰────────────────────────────────────────────────────────────────╯
-$ rad patch show 356f73863a8920455ff6e77cd9c805d68910551b
+$ rad patch show 0e8cc60585b6bb6a1236dc9958bf09883ecba9f3
╭────────────────────────────────────────────────────────────────╮
│ Title Second change │
-│ Patch 356f73863a8920455ff6e77cd9c805d68910551b │
+│ Patch 0e8cc60585b6bb6a1236dc9958bf09883ecba9f3 │
│ Author alice (you) │
│ Head daf349ff76bedf48c5f292290b682ee7be0683cf │
│ Branches feature/2 │
@@ -110,7 +96,7 @@ $ rad patch show 356f73863a8920455ff6e77cd9c805d68910551b
│ daf349f Second change │
├────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (daf349f) now │
-│ └─ ✓ merged by alice (you) at revision 356f738 (daf349f) now │
+│ └─ ✓ merged by alice (you) at revision 0e8cc60 (daf349f) now │
╰────────────────────────────────────────────────────────────────╯
```
@@ -129,10 +115,10 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
└── refs
├── cobs
│ ├── xyz.radicle.id
- │ │ └── 0656c217f917c3e06234771e9ecae53aba5e173e
+ │ │ └── eeb8b44890570ccf85db7f3cb2a475100a27408a
│ └── xyz.radicle.patch
- │ ├── 356f73863a8920455ff6e77cd9c805d68910551b
- │ └── 696ec5508494692899337afe6713fe1796d0315c
+ │ ├── 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084
+ │ └── 0e8cc60585b6bb6a1236dc9958bf09883ecba9f3
├── heads
│ └── master
└── rad
@@ -147,9 +133,9 @@ the first patch, even though they were pushed together.
``` (stderr) RAD_SOCKET=/dev/null
$ git reset --hard HEAD^
$ git push -f rad
-! Patch 356f73863a8920455ff6e77cd9c805d68910551b reverted at revision 356f738
-✓ Canonical head updated to 20aa5dde6210796c3a2f04079b42316a31d02689
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+! Patch 0e8cc60585b6bb6a1236dc9958bf09883ecba9f3 reverted at revision 0e8cc60
+✓ Canonical head for refs/heads/master updated to 20aa5dde6210796c3a2f04079b42316a31d02689
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ d6399c7...20aa5dd master -> master (forced update)
```
```
@@ -157,7 +143,7 @@ $ rad patch --all
╭─────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │
├─────────────────────────────────────────────────────────────────────────────┤
-│ ● 356f738 Second change alice (you) - daf349f +0 -0 now │
-│ ✔ 696ec55 First change alice (you) - 20aa5dd +0 -0 now │
+│ ✔ 09a3de4 First change alice (you) - 20aa5dd +0 -0 now │
+│ ● 0e8cc60 Second change alice (you) - daf349f +0 -0 now │
╰─────────────────────────────────────────────────────────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-node.md b/radicle-cli/examples/rad-node.md
index 8a0ec5f2d..9e9728fba 100644
--- a/radicle-cli/examples/rad-node.md
+++ b/radicle-cli/examples/rad-node.md
@@ -43,7 +43,7 @@ $ rad seed
╭───────────────────────────────────────────────────────────────────╮
│ Repository Name Policy Scope │
├───────────────────────────────────────────────────────────────────┤
-│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji heartwood allow followed │
+│ rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 heartwood allow followed │
╰───────────────────────────────────────────────────────────────────╯
```
@@ -68,7 +68,7 @@ $ rad node routing
╭─────────────────────────────────────────────────────╮
│ RID NID │
├─────────────────────────────────────────────────────┤
-│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji z6MknSL…StBU8Vi │
+│ rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 z6MknSL…StBU8Vi │
╰─────────────────────────────────────────────────────╯
```
@@ -90,8 +90,8 @@ $ rad node stop
Note that if we unseed a repository, it is no longer part of our inventory:
```
-$ rad unseed rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Seeding policy for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji removed
+$ rad unseed rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Seeding policy for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 removed
$ rad node inventory
```
@@ -106,11 +106,11 @@ $ rad node inventory
But if we start seeding the repository we have locally again, it'll show
up in our inventory:
```
-$ rad seed rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Inventory updated with rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
+$ rad seed rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Inventory updated with rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Seeding policy updated for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 with scope 'all'
$ rad node inventory
-rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
```
Some commands also give us a hint if the node isn't running:
diff --git a/radicle-cli/examples/rad-patch-ahead-behind.md b/radicle-cli/examples/rad-patch-ahead-behind.md
index b01547885..a099e6535 100644
--- a/radicle-cli/examples/rad-patch-ahead-behind.md
+++ b/radicle-cli/examples/rad-patch-ahead-behind.md
@@ -37,8 +37,8 @@ $ git log --graph --decorate --abbrev-commit --pretty=oneline --all
Then we create a patch from `feature/1`:
``` (stderr)
$ git push rad feature/1:refs/patches
-✓ Patch 217f050f8891def8fb863f7c0b4f85c89f97299d opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 9128a6dcd3b043b420a1dfd541cf24b5d0e65d39 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] feature/1 -> refs/patches
```
@@ -48,17 +48,17 @@ $ rad patch list
╭────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │
├────────────────────────────────────────────────────────────────────────┤
-│ ● 217f050 Add Alan alice (you) - 5c88a79 +1 -0 now │
+│ ● 9128a6d Add Alan alice (you) - 5c88a79 +1 -0 now │
╰────────────────────────────────────────────────────────────────────────╯
```
When showing the patch, we see that it is `ahead 1, behind 1`, since master has
diverged by one commit:
```
-$ rad patch show -v -p 217f050
+$ rad patch show -v -p 9128a6d
╭────────────────────────────────────────────────────╮
│ Title Add Alan │
-│ Patch 217f050f8891def8fb863f7c0b4f85c89f97299d │
+│ Patch 9128a6dcd3b043b420a1dfd541cf24b5d0e65d39 │
│ Author alice (you) │
│ Head 5c88a79d75f5c2b4cc51ee6f163d2db91ee198d7 │
│ Base f64fb2c8fe28f7c458c72ec8d700373924794943 │
@@ -93,18 +93,18 @@ $ git checkout -q -b feature/2 feature/1
$ sed -i '$a Mel Farna' CONTRIBUTORS
$ git commit -a -q -m "Add Mel"
$ git push -o patch.message="Add Mel" rad HEAD:refs/patches
-✓ Patch e22ff008e2a0ed47262890d13263031d7555b555 opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 5fd13489ca986a1a7fda5feb4c2aab1982913304 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
When we look at the patch, we see that it has both commits, because this new
patch uses the same base as the previous patch:
```
-$ rad patch show -v e22ff008e2a0ed47262890d13263031d7555b555
+$ rad patch show -v 5fd13489ca986a1a7fda5feb4c2aab1982913304
╭────────────────────────────────────────────────────╮
│ Title Add Mel │
-│ Patch e22ff008e2a0ed47262890d13263031d7555b555 │
+│ Patch 5fd13489ca986a1a7fda5feb4c2aab1982913304 │
│ Author alice (you) │
│ Head 7f63fcbcf23fc39eea784c091ad3d20d7e4bd005 │
│ Base f64fb2c8fe28f7c458c72ec8d700373924794943 │
@@ -124,8 +124,8 @@ If we want to instead create a "stacked" patch, we can do so with the
``` (stderr)
$ git push -o patch.message="Add Mel #2" -o patch.base=HEAD^ rad HEAD:refs/patches
-✓ Patch a467ffa260c4fbe355b6fb550ba0c4956078717e opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch fdde8e3672f7f5eea2371d92325c4ecfbe2066a5 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -136,10 +136,10 @@ However, since the patch is still intended to be merged into `master`, we see
that it is still two commits ahead and one behind from `master`.
```
-$ rad patch show -v a467ffa260c4fbe355b6fb550ba0c4956078717e
+$ rad patch show -v fdde8e3672f7f5eea2371d92325c4ecfbe2066a5
╭────────────────────────────────────────────────────╮
│ Title Add Mel #2 │
-│ Patch a467ffa260c4fbe355b6fb550ba0c4956078717e │
+│ Patch fdde8e3672f7f5eea2371d92325c4ecfbe2066a5 │
│ Author alice (you) │
│ Head 7f63fcbcf23fc39eea784c091ad3d20d7e4bd005 │
│ Base 5c88a79d75f5c2b4cc51ee6f163d2db91ee198d7 │
diff --git a/radicle-cli/examples/rad-patch-change-base.md b/radicle-cli/examples/rad-patch-change-base.md
index 12b295a92..5d34af102 100644
--- a/radicle-cli/examples/rad-patch-change-base.md
+++ b/radicle-cli/examples/rad-patch-change-base.md
@@ -15,11 +15,11 @@ $ git commit -v -m "Define power requirements"
```
``` (stderr)
$ git push rad flux-capacitor-power
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new branch] flux-capacitor-power -> flux-capacitor-power
$ git push rad -o patch.message="Define power requirements" -o patch.message="See details." HEAD:refs/patches
-✓ Patch aa45913e757cacd46972733bddee5472c78fa32a opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch c90967c43719b916e0b5a8b5dafe353608f8a08a opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -35,18 +35,18 @@ $ git commit --message "Add README, just for the fun"
```
``` (stderr)
$ git push rad -o patch.message="Add README, just for the fun" HEAD:refs/patches
-✓ Patch 183d343ab47d7fe18baf1b24b7209ad033d7fe5c opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 3e1ca74542ded1f51ca9a744ed6266f23bf2507f opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
Our second patch looks like the following:
```
-$ rad patch show 183d343ab47d7fe18baf1b24b7209ad033d7fe5c -v
+$ rad patch show 3e1ca74542ded1f51ca9a744ed6266f23bf2507f -v
╭────────────────────────────────────────────────────╮
│ Title Add README, just for the fun │
-│ Patch 183d343ab47d7fe18baf1b24b7209ad033d7fe5c │
+│ Patch 3e1ca74542ded1f51ca9a744ed6266f23bf2507f │
│ Author alice (you) │
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
│ Base f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 │
@@ -66,18 +66,18 @@ commit `3e674d1` as part of this patch, so we create a new revision
with a new `base`:
```
-$ rad patch update 183d343 -b 3e674d1 -m "Whoops, forgot to set the base" --no-announce
-ebe76f9c2148eb595d7a745f82275786bf3458c3
+$ rad patch update 3e1ca74 -b 3e674d1 -m "Whoops, forgot to set the base" --no-announce
+852c792f460ca485ce22b6acc41c150d7aeb4642
```
Now, if we show the patch we can see the patch's base has changed and
we have a single commit:
```
-$ rad patch show 183d343 -v
+$ rad patch show 3e1ca74 -v
╭─────────────────────────────────────────────────────────────────────╮
│ Title Add README, just for the fun │
-│ Patch 183d343ab47d7fe18baf1b24b7209ad033d7fe5c │
+│ Patch 3e1ca74542ded1f51ca9a744ed6266f23bf2507f │
│ Author alice (you) │
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
│ Base 3e674d1a1df90807e934f9ae5da2591dd6848a33 │
@@ -88,6 +88,6 @@ $ rad patch show 183d343 -v
│ 27857ec Add README, just for the fun │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (27857ec) now │
-│ ↑ updated to ebe76f9c2148eb595d7a745f82275786bf3458c3 (27857ec) now │
+│ ↑ updated to 852c792f460ca485ce22b6acc41c150d7aeb4642 (27857ec) now │
╰─────────────────────────────────────────────────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-patch-checkout-force.md b/radicle-cli/examples/rad-patch-checkout-force.md
index ff88d7eb7..3971545c5 100644
--- a/radicle-cli/examples/rad-patch-checkout-force.md
+++ b/radicle-cli/examples/rad-patch-checkout-force.md
@@ -13,9 +13,9 @@ $ git commit -v -m "Define power requirements"
``` ~alice (stderr)
$ git push rad -o patch.message="Define power requirements" -o patch.message="See details." HEAD:refs/patches
-✓ Patch aa45913e757cacd46972733bddee5472c78fa32a opened
+✓ Patch c90967c43719b916e0b5a8b5dafe353608f8a08a opened
✓ Synced with 1 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -24,11 +24,11 @@ On the other end, Bob uses `rad patch checkout` to view the patch:
``` ~bob
$ cd heartwood
$ rad sync -f
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
-$ rad patch checkout aa45913 --name alice-init
-✓ Switched to branch alice-init at revision aa45913
-✓ Branch alice-init setup to track rad/patches/aa45913e757cacd46972733bddee5472c78fa32a
+$ rad patch checkout c90967c --name alice-init
+✓ Switched to branch alice-init at revision c90967c
+✓ Branch alice-init setup to track rad/patches/c90967c43719b916e0b5a8b5dafe353608f8a08a
```
Meanwhile, we may see some more changes that we need to make, so we
@@ -45,28 +45,28 @@ $ git commit --message "Add README, just for the fun"
``` ~alice (stderr)
$ git push rad -o patch.message="Add README, just for the fun"
-✓ Patch aa45913 updated to revision 3156bed9d64d4675d6cf56612d217fc5f4e8a53a
-To compare against your previous revision aa45913, run:
+✓ Patch c90967c updated to revision 594bb93b4ba836777c111053af7b61ff772afbc5
+To compare against your previous revision c90967c, run:
git range-diff f2de534[..] 3e674d1[..] 27857ec[..]
✓ Synced with 1 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- 3e674d1..27857ec flux-capacitor-power -> patches/aa45913e757cacd46972733bddee5472c78fa32a
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 3e674d1..27857ec flux-capacitor-power -> patches/c90967c43719b916e0b5a8b5dafe353608f8a08a
```
Bob fetches these new changes and can see their branch is now behind:
``` ~bob (stderr)
$ git fetch rad
-From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
- 3e674d1..27857ec patches/aa45913e757cacd46972733bddee5472c78fa32a -> rad/patches/aa45913e757cacd46972733bddee5472c78fa32a
+From rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+ 3e674d1..27857ec patches/c90967c43719b916e0b5a8b5dafe353608f8a08a -> rad/patches/c90967c43719b916e0b5a8b5dafe353608f8a08a
```
``` ~bob
$ git status
On branch alice-init
-Your branch is behind 'rad/patches/aa45913e757cacd46972733bddee5472c78fa32a' by 1 commit, and can be fast-forwarded.
+Your branch is behind 'rad/patches/c90967c43719b916e0b5a8b5dafe353608f8a08a' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
@@ -78,17 +78,17 @@ overwrite any changes. Bob can choose to use the `--force` (`-f`) flag to
ensure that they are looking at the latest changes:
``` ~bob (fail)
-$ rad patch checkout aa45913 --name alice-init
+$ rad patch checkout c90967c --name alice-init
✗ Performing checkout... <canceled>
✗ Error: branch 'alice-init' already exists (use `--force` to overwrite)
```
``` ~bob
-$ rad patch checkout aa45913 -f --name alice-init
-✓ Switched to branch alice-init at revision 3156bed
+$ rad patch checkout c90967c -f --name alice-init
+✓ Switched to branch alice-init at revision 594bb93
$ git status
On branch alice-init
-Your branch is up to date with 'rad/patches/aa45913e757cacd46972733bddee5472c78fa32a'.
+Your branch is up to date with 'rad/patches/c90967c43719b916e0b5a8b5dafe353608f8a08a'.
nothing to commit, working tree clean
```
diff --git a/radicle-cli/examples/rad-patch-checkout-revision.md b/radicle-cli/examples/rad-patch-checkout-revision.md
index f135130f9..bcaadfe84 100644
--- a/radicle-cli/examples/rad-patch-checkout-revision.md
+++ b/radicle-cli/examples/rad-patch-checkout-revision.md
@@ -5,7 +5,7 @@ So first, let's add another change to the patch and a `LICENSE` file.
$ touch LICENSE
$ git add LICENSE
$ git commit --message "Add LICENSE, just for the business"
-[patch/aa45913 639f44a] Add LICENSE, just for the business
+[patch/c90967c 639f44a] Add LICENSE, just for the business
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 LICENSE
$ git push rad -o patch.message="Add LICENSE, just for the business"
@@ -14,13 +14,13 @@ $ git push rad -o patch.message="Add LICENSE, just for the business"
We can see the list of revisions of the patch by `show`ing it:
```
-$ rad patch show aa45913
+$ rad patch show c90967c
╭─────────────────────────────────────────────────────────────────────╮
│ Title Define power requirements │
-│ Patch aa45913e757cacd46972733bddee5472c78fa32a │
+│ Patch c90967c43719b916e0b5a8b5dafe353608f8a08a │
│ Author alice (you) │
│ Head 639f44a25145a37f747f3c84265037a9461e44c5 │
-│ Branches patch/aa45913 │
+│ Branches patch/c90967c │
│ Commits ahead 3, behind 0 │
│ Status open │
│ │
@@ -31,16 +31,16 @@ $ rad patch show aa45913
│ 3e674d1 Define power requirements │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (3e674d1) now │
-│ ↑ updated to 3156bed9d64d4675d6cf56612d217fc5f4e8a53a (27857ec) now │
-│ ↑ updated to 2f5324f61e05cda65b667eeea02570d077a8e724 (639f44a) now │
+│ ↑ updated to 594bb93b4ba836777c111053af7b61ff772afbc5 (27857ec) now │
+│ ↑ updated to 92a95e995d436248d844bdd6c94704725efc283d (639f44a) now │
╰─────────────────────────────────────────────────────────────────────╯
```
So, let's checkout the previous revision, `0c0942e2`:
```
-$ rad patch checkout aa45913 --revision 3156bed9d64d4675d6cf56612d217fc5f4e8a53a -f
-✓ Switched to branch patch/aa45913 at revision 3156bed
+$ rad patch checkout c90967c --revision 594bb93b4ba836777c111053af7b61ff772afbc5 -f
+✓ Switched to branch patch/c90967c at revision 594bb93
```
And we can confirm that the current commit corresponds to `27857ec`:
diff --git a/radicle-cli/examples/rad-patch-checkout.md b/radicle-cli/examples/rad-patch-checkout.md
index 044e6c26f..e12effba8 100644
--- a/radicle-cli/examples/rad-patch-checkout.md
+++ b/radicle-cli/examples/rad-patch-checkout.md
@@ -22,17 +22,17 @@ Once the code is ready, we open (or create) a patch with our changes for the pro
``` (stderr)
$ git push rad -o patch.message="Define power requirements" -o patch.message="See details." HEAD:refs/patches
-✓ Patch aa45913e757cacd46972733bddee5472c78fa32a opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch c90967c43719b916e0b5a8b5dafe353608f8a08a opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
Now, let's checkout the patch that we just created:
```
-$ rad patch checkout aa45913e757cacd46972733bddee5472c78fa32a
-✓ Switched to branch patch/aa45913 at revision aa45913
-✓ Branch patch/aa45913 setup to track rad/patches/aa45913e757cacd46972733bddee5472c78fa32a
+$ rad patch checkout c90967c43719b916e0b5a8b5dafe353608f8a08a
+✓ Switched to branch patch/c90967c at revision c90967c
+✓ Branch patch/c90967c setup to track rad/patches/c90967c43719b916e0b5a8b5dafe353608f8a08a
```
Note that `rad patch checkout` can be used to switch to the patch branch
@@ -40,8 +40,8 @@ as long as we haven't made changes to it.
```
$ git checkout master -q
-$ rad patch checkout aa45913
-✓ Switched to branch patch/aa45913 at revision aa45913
+$ rad patch checkout c90967c
+✓ Switched to branch patch/c90967c at revision c90967c
```
Now, let's add a README too!
@@ -50,7 +50,7 @@ Now, let's add a README too!
$ touch README.md
$ git add README.md
$ git commit --message "Add README, just for the fun"
-[patch/aa45913 27857ec] Add README, just for the fun
+[patch/c90967c 27857ec] Add README, just for the fun
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README.md
```
@@ -59,11 +59,11 @@ We can now finish off the update:
``` (stderr)
$ git push rad -o patch.message="Add README, just for the fun"
-✓ Patch aa45913 updated to revision 3156bed9d64d4675d6cf56612d217fc5f4e8a53a
-To compare against your previous revision aa45913, run:
+✓ Patch c90967c updated to revision 594bb93b4ba836777c111053af7b61ff772afbc5
+To compare against your previous revision c90967c, run:
git range-diff f2de534[..] 3e674d1[..] 27857ec[..]
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- 3e674d1..27857ec patch/aa45913 -> patches/aa45913e757cacd46972733bddee5472c78fa32a
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 3e674d1..27857ec patch/c90967c -> patches/c90967c43719b916e0b5a8b5dafe353608f8a08a
```
diff --git a/radicle-cli/examples/rad-patch-delete.md b/radicle-cli/examples/rad-patch-delete.md
index 062890218..ead2cec81 100644
--- a/radicle-cli/examples/rad-patch-delete.md
+++ b/radicle-cli/examples/rad-patch-delete.md
@@ -10,30 +10,30 @@ $ git commit -m "Introduce license"
``` ~alice (stderr)
$ git push rad -o patch.draft -o patch.message="Define LICENSE for project" HEAD:refs/patches
-✓ Patch 6c61ef1716ad8a5c11e04dd7a3fec51e01fba70b drafted
+✓ Patch e5dc5fd15fbe952da6a0f43934eae57d47b93e36 drafted
✓ Synced with 2 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
``` ~bob
$ cd heartwood
$ rad sync -f
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
-$ rad patch comment 6c61ef1 -m "I think we should use MIT"
+$ rad patch comment e5dc5fd -m "I think we should use MIT"
╭───────────────────────────╮
-│ bob (you) now 833db19 │
+│ bob (you) now 2ec2cc1 │
│ I think we should use MIT │
╰───────────────────────────╯
✓ Synced with 2 node(s)
```
``` ~alice
-$ rad patch show 6c61ef1 -v
+$ rad patch show e5dc5fd -v
╭────────────────────────────────────────────────────╮
│ Title Define LICENSE for project │
-│ Patch 6c61ef1716ad8a5c11e04dd7a3fec51e01fba70b │
+│ Patch e5dc5fd15fbe952da6a0f43934eae57d47b93e36 │
│ Author alice (you) │
│ Head 717c900ec17735639587325e0fd9fe09991c9edd │
│ Base f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 │
@@ -45,12 +45,12 @@ $ rad patch show 6c61ef1 -v
├────────────────────────────────────────────────────┤
│ ● opened by alice (you) (717c900) now │
├────────────────────────────────────────────────────┤
-│ bob z6Mkt67…v4N1tRk now 833db19 │
+│ bob z6Mkt67…v4N1tRk now 2ec2cc1 │
│ I think we should use MIT │
╰────────────────────────────────────────────────────╯
-$ rad patch comment 6c61ef1 --reply-to 833db19 -m "Thanks, I'll add it!"
+$ rad patch comment e5dc5fd --reply-to 2ec2cc1 -m "Thanks, I'll add it!"
╭─────────────────────────╮
-│ alice (you) now 1803a38 │
+│ alice (you) now 737dcab │
│ Thanks, I'll add it! │
╰─────────────────────────╯
✓ Synced with 2 node(s)
@@ -68,24 +68,24 @@ $ git commit -am "Add MIT License"
``` ~alice (stderr)
$ git push -f
-✓ Patch 6c61ef1 updated to revision 93915b9afa94a9dc4f52f12cdf077d4613ea3eb3
-To compare against your previous revision 6c61ef1, run:
+✓ Patch e5dc5fd updated to revision 1a1082a96f552767d352d69b8e6524aeb82f67a4
+To compare against your previous revision e5dc5fd, run:
git range-diff f2de534[..] 717c900[..] 1cc8cd9[..]
✓ Synced with 2 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- 717c900..1cc8cd9 prepare-license -> patches/6c61ef1716ad8a5c11e04dd7a3fec51e01fba70b
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 717c900..1cc8cd9 prepare-license -> patches/e5dc5fd15fbe952da6a0f43934eae57d47b93e36
```
``` ~bob
-$ rad patch review 6c61ef1 --accept -m "LGTM!"
-✓ Patch 6c61ef1 accepted
+$ rad patch review e5dc5fd --accept -m "LGTM!"
+✓ Patch e5dc5fd accepted
✓ Synced with 2 node(s)
-$ rad patch show 6c61ef1 -v
+$ rad patch show e5dc5fd -v
╭─────────────────────────────────────────────────────────────────────╮
│ Title Define LICENSE for project │
-│ Patch 6c61ef1716ad8a5c11e04dd7a3fec51e01fba70b │
+│ Patch e5dc5fd15fbe952da6a0f43934eae57d47b93e36 │
│ Author alice z6MknSL…StBU8Vi │
│ Head 1cc8cd9de8ccc44b4fe3876f2dbd2cd1cf9ddc0e │
│ Base f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 │
@@ -96,21 +96,21 @@ $ rad patch show 6c61ef1 -v
│ 717c900 Introduce license │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice z6MknSL…StBU8Vi (717c900) now │
-│ ↑ updated to 93915b9afa94a9dc4f52f12cdf077d4613ea3eb3 (1cc8cd9) now │
+│ ↑ updated to 1a1082a96f552767d352d69b8e6524aeb82f67a4 (1cc8cd9) now │
│ └─ ✓ accepted by bob (you) now │
╰─────────────────────────────────────────────────────────────────────╯
```
``` ~bob
-$ rad patch delete 6c61ef1
+$ rad patch delete e5dc5fd
✓ Synced with 2 node(s)
```
``` ~alice
-$ rad patch show 6c61ef1 -v
+$ rad patch show e5dc5fd -v
╭─────────────────────────────────────────────────────────────────────╮
│ Title Define LICENSE for project │
-│ Patch 6c61ef1716ad8a5c11e04dd7a3fec51e01fba70b │
+│ Patch e5dc5fd15fbe952da6a0f43934eae57d47b93e36 │
│ Author alice (you) │
│ Head 1cc8cd9de8ccc44b4fe3876f2dbd2cd1cf9ddc0e │
│ Base f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 │
@@ -122,7 +122,7 @@ $ rad patch show 6c61ef1 -v
│ 717c900 Introduce license │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (717c900) now │
-│ ↑ updated to 93915b9afa94a9dc4f52f12cdf077d4613ea3eb3 (1cc8cd9) now │
+│ ↑ updated to 1a1082a96f552767d352d69b8e6524aeb82f67a4 (1cc8cd9) now │
╰─────────────────────────────────────────────────────────────────────╯
```
@@ -130,11 +130,11 @@ If Alice also decides to delete the patch, then any seeds that have synced with
Alice should no longer have the patch:
``` ~alice
-$ rad patch delete 6c61ef1
+$ rad patch delete e5dc5fd
✓ Synced with 2 node(s)
```
``` ~seed (fails)
-$ rad patch show --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji 6c61ef1 -v
-✗ Error: Patch `6c61ef1716ad8a5c11e04dd7a3fec51e01fba70b` not found
+$ rad patch show --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 e5dc5fd -v
+✗ Error: Patch `e5dc5fd15fbe952da6a0f43934eae57d47b93e36` not found
```
diff --git a/radicle-cli/examples/rad-patch-detached-head.md b/radicle-cli/examples/rad-patch-detached-head.md
index ab0c3f8e1..5cf6404a6 100644
--- a/radicle-cli/examples/rad-patch-detached-head.md
+++ b/radicle-cli/examples/rad-patch-detached-head.md
@@ -30,10 +30,10 @@ Now, we can create a commit on top of this and create a patch, as usual:
``` (stderr) RAD_HINT=1
$ git commit -a -m "Add things" -q --allow-empty
$ git push -o patch.message="Add things #1" -o patch.message="See commits for details." rad HEAD:refs/patches
-✓ Patch 6035d2f582afbe01ff23ea87528ae523d76875b6 opened
+✓ Patch a183e324b82e94c548eb43b7acb7c7d92ebe7761 opened
hint: offline push, your node is not running
hint: to sync with the network, run `rad node start`
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
diff --git a/radicle-cli/examples/rad-patch-diff.md b/radicle-cli/examples/rad-patch-diff.md
index bb00955f5..5b50a26f6 100644
--- a/radicle-cli/examples/rad-patch-diff.md
+++ b/radicle-cli/examples/rad-patch-diff.md
@@ -10,7 +10,13 @@ $ git commit -m "Add README" -q
$ git push rad HEAD:refs/patches
```
```
-$ rad patch diff 147309e
+$ rad patch
+╭──────────────────────────────────────────────────────────────────────────╮
+│ ● ID Title Author Reviews Head + - Updated │
+├──────────────────────────────────────────────────────────────────────────┤
+│ ● a44b0da Add README alice (you) - 2420bc3 +1 -0 now │
+╰──────────────────────────────────────────────────────────────────────────╯
+$ rad patch diff a44b0da
╭───────────────────────────╮
│ README.md +1 ❲created❳ │
├───────────────────────────┤
@@ -31,7 +37,7 @@ $ git commit --amend -q
$ git push -f
```
```
-$ rad patch diff 147309e
+$ rad patch diff a44b0da
╭─────────────────────────────╮
│ RADICLE.md +1 ❲created❳ │
├─────────────────────────────┤
@@ -52,7 +58,7 @@ Buf if we only want to see the changes from the first revision, we can do that
too.
```
-$ rad patch diff 147309e --revision 147309e
+$ rad patch diff a44b0da --revision a44b0da
╭───────────────────────────╮
│ README.md +1 ❲created❳ │
├───────────────────────────┤
diff --git a/radicle-cli/examples/rad-patch-draft.md b/radicle-cli/examples/rad-patch-draft.md
index b5167217f..fa5bca8b0 100644
--- a/radicle-cli/examples/rad-patch-draft.md
+++ b/radicle-cli/examples/rad-patch-draft.md
@@ -9,18 +9,18 @@ To open a patch in draft mode, we use the `--draft` option:
``` (stderr)
$ git push -o patch.draft -o patch.message="Nothing yet" rad HEAD:refs/patches
-✓ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 drafted
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch acee9948a4ff68e49a678734e8a0b86ff29f2e40 drafted
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
We can confirm it's a draft by running `show`:
```
-$ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97
+$ rad patch show acee9948a4ff68e49a678734e8a0b86ff29f2e40
╭────────────────────────────────────────────────────╮
│ Title Nothing yet │
-│ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 │
+│ Patch acee9948a4ff68e49a678734e8a0b86ff29f2e40 │
│ Author alice (you) │
│ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │
│ Branches cloudhead/draft │
@@ -36,14 +36,14 @@ $ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97
Once the patch is ready for review, we can use the `ready` command:
```
-$ rad patch ready 97e18f8598237a396a1c0ac1509c89028e666c97 --no-announce
+$ rad patch ready acee9948a4ff68e49a678734e8a0b86ff29f2e40 --no-announce
```
```
-$ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97
+$ rad patch show acee9948a4ff68e49a678734e8a0b86ff29f2e40
╭────────────────────────────────────────────────────╮
│ Title Nothing yet │
-│ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 │
+│ Patch acee9948a4ff68e49a678734e8a0b86ff29f2e40 │
│ Author alice (you) │
│ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │
│ Branches cloudhead/draft │
@@ -60,11 +60,11 @@ If for whatever reason, it needed to go back into draft mode, we could use
the `--undo` flag:
```
-$ rad patch ready --undo 97e18f8598237a396a1c0ac1509c89028e666c97 --no-announce
-$ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97
+$ rad patch ready --undo acee9948a4ff68e49a678734e8a0b86ff29f2e40 --no-announce
+$ rad patch show acee9948a4ff68e49a678734e8a0b86ff29f2e40
╭────────────────────────────────────────────────────╮
│ Title Nothing yet │
-│ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 │
+│ Patch acee9948a4ff68e49a678734e8a0b86ff29f2e40 │
│ Author alice (you) │
│ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │
│ Branches cloudhead/draft │
diff --git a/radicle-cli/examples/rad-patch-edit.md b/radicle-cli/examples/rad-patch-edit.md
index 53c8570b9..d864c4c8f 100644
--- a/radicle-cli/examples/rad-patch-edit.md
+++ b/radicle-cli/examples/rad-patch-edit.md
@@ -16,8 +16,8 @@ $ git commit --message "Add README, just for the fun"
``` (stderr)
$ git push rad -o patch.message="Add README, just for the fun" HEAD:refs/patches
-✓ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch f699e2299e9ee734758626924df7e15fd9a68553 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -32,22 +32,22 @@ $ git commit -v -m "Define the LICENSE"
``` (stderr)
$ git push -f -o patch.message="Add License"
-✓ Patch 89f7afb updated to revision 5d78dd5376453e25df5988ec86951c99cb73742c
-To compare against your previous revision 89f7afb, run:
+✓ Patch f699e22 updated to revision 13b5240e3f7ecb60fea4f66eb1a09fa3ffc1de7f
+To compare against your previous revision f699e22, run:
git range-diff f2de534[..] 03c02af[..] 8945f61[..]
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- 03c02af..8945f61 changes -> patches/89f7afb1511b976482b21f6b2f39aef7f4fb88a2
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 03c02af..8945f61 changes -> patches/f699e2299e9ee734758626924df7e15fd9a68553
```
Let's look at the patch, to see what it looks like before editing it:
```
-$ rad patch show 89f7afb
+$ rad patch show f699e22
╭─────────────────────────────────────────────────────────────────────╮
│ Title Add README, just for the fun │
-│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
+│ Patch f699e2299e9ee734758626924df7e15fd9a68553 │
│ Author alice (you) │
│ Head 8945f6189adf027892c85ac57f7e9341049c2537 │
│ Branches changes │
@@ -58,7 +58,7 @@ $ rad patch show 89f7afb
│ 03c02af Add README, just for the fun │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (03c02af) now │
-│ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
+│ ↑ updated to 13b5240e3f7ecb60fea4f66eb1a09fa3ffc1de7f (8945f61) now │
╰─────────────────────────────────────────────────────────────────────╯
```
@@ -66,11 +66,11 @@ 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
+$ rad patch edit f699e22 --message "Add Metadata" --message "Add README & LICENSE" --no-announce
+$ rad patch show f699e22
╭─────────────────────────────────────────────────────────────────────╮
│ Title Add Metadata │
-│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
+│ Patch f699e2299e9ee734758626924df7e15fd9a68553 │
│ Author alice (you) │
│ Head 8945f6189adf027892c85ac57f7e9341049c2537 │
│ Branches changes │
@@ -83,7 +83,7 @@ $ rad patch show 89f7afb
│ 03c02af Add README, just for the fun │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (03c02af) now │
-│ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
+│ ↑ updated to 13b5240e3f7ecb60fea4f66eb1a09fa3ffc1de7f (8945f61) now │
╰─────────────────────────────────────────────────────────────────────╯
```
@@ -94,11 +94,11 @@ If we want to change a specific revision's description, we can use the
`--revision` option:
```
-$ rad patch edit 89f7afb --revision 5d78dd5 --message "Changes: Adds LICENSE file" --no-announce
-$ rad patch show 89f7afb
+$ rad patch edit f699e22 --revision 13b5240 --message "Changes: Adds LICENSE file" --no-announce
+$ rad patch show f699e22
╭─────────────────────────────────────────────────────────────────────╮
│ Title Add Metadata │
-│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
+│ Patch f699e2299e9ee734758626924df7e15fd9a68553 │
│ Author alice (you) │
│ Head 8945f6189adf027892c85ac57f7e9341049c2537 │
│ Branches changes │
@@ -111,7 +111,7 @@ $ rad patch show 89f7afb
│ 03c02af Add README, just for the fun │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (03c02af) now │
-│ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
+│ ↑ updated to 13b5240e3f7ecb60fea4f66eb1a09fa3ffc1de7f (8945f61) now │
╰─────────────────────────────────────────────────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-patch-fetch-1.md b/radicle-cli/examples/rad-patch-fetch-1.md
index 28fdc9fc8..731344928 100644
--- a/radicle-cli/examples/rad-patch-fetch-1.md
+++ b/radicle-cli/examples/rad-patch-fetch-1.md
@@ -20,9 +20,9 @@ $ git fetch --all
Fetching rad
Fetching alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
$ cat .git/FETCH_HEAD
-f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 branch 'master' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-7461703ce0fda972df450d071d1d3702057a6352 not-for-merge branch 'alice/1' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
-f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 not-for-merge branch 'master' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 branch 'master' of rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+7461703ce0fda972df450d071d1d3702057a6352 not-for-merge branch 'alice/1' of rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 not-for-merge branch 'master' of rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
$ git merge FETCH_HEAD
Already up to date.
$ git rev-parse master
diff --git a/radicle-cli/examples/rad-patch-fetch-2.md b/radicle-cli/examples/rad-patch-fetch-2.md
index a9392665c..abe744166 100644
--- a/radicle-cli/examples/rad-patch-fetch-2.md
+++ b/radicle-cli/examples/rad-patch-fetch-2.md
@@ -12,7 +12,7 @@ $ git push rad -o patch.message="Changes" HEAD:refs/patches
$ git checkout master -q
$ git branch -D alice/1 -q
$ git update-ref -d refs/remotes/rad/alice/1
-$ git update-ref -d refs/remotes/rad/patches/5e2dedcc5d515fcbc1cca483d3376609fe889bfb
+$ git update-ref -d refs/remotes/rad/patches/819af456d77847724bcaed7e1c999ffd398e499e
$ git gc --prune=now
$ git branch -r
rad/master
@@ -23,5 +23,5 @@ $ git pull
Already up to date.
$ git branch -r
rad/master
- rad/patches/5e2dedcc5d515fcbc1cca483d3376609fe889bfb
+ rad/patches/819af456d77847724bcaed7e1c999ffd398e499e
```
diff --git a/radicle-cli/examples/rad-patch-merge-draft.md b/radicle-cli/examples/rad-patch-merge-draft.md
index f13f6e830..17d2d1b8e 100644
--- a/radicle-cli/examples/rad-patch-merge-draft.md
+++ b/radicle-cli/examples/rad-patch-merge-draft.md
@@ -4,8 +4,8 @@ Let's start by creating a draft patch.
$ git checkout -b feature/1 -q
$ git commit --allow-empty -q -m "First change"
$ git push -o patch.draft rad HEAD:refs/patches
-✓ Patch 8dfb4dcafc4346158c8160410dd3f2b0616ad4fe drafted
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 304d0a97bf9c4dadd4c732196a0f68dcfb2b6738 drafted
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -13,8 +13,8 @@ To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkE
$ git checkout master -q
$ git merge feature/1
$ git push rad master
-✓ Patch 8dfb4dcafc4346158c8160410dd3f2b0616ad4fe merged
-✓ Canonical head updated to 20aa5dde6210796c3a2f04079b42316a31d02689
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 304d0a97bf9c4dadd4c732196a0f68dcfb2b6738 merged
+✓ Canonical head for refs/heads/master updated to 20aa5dde6210796c3a2f04079b42316a31d02689
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..20aa5dd master -> master
```
diff --git a/radicle-cli/examples/rad-patch-open-explore.md b/radicle-cli/examples/rad-patch-open-explore.md
index dee835714..10be0a5eb 100644
--- a/radicle-cli/examples/rad-patch-open-explore.md
+++ b/radicle-cli/examples/rad-patch-open-explore.md
@@ -4,12 +4,12 @@ When preferred seeds are configured, opening a patch outputs the patch URL.
$ git checkout -b changes -q
$ git commit --allow-empty -q -m "Changes"
$ git push rad HEAD:refs/patches
-✓ Patch acab0ec777a97d013f30be5d5d1aec32562ecb02 opened
+✓ Patch c47f80b133e5c0b930cbd21890e4fc535c854c16 opened
✓ Synced with 1 node(s)
- https://app.radicle.xyz/nodes/[..]/rad:z3yXbb1sR6UG6ixxV2YF9jUP7ABra/patches/acab0ec777a97d013f30be5d5d1aec32562ecb02
+ https://app.radicle.xyz/nodes/[..]/rad:zPHvWyMMwBBH24oGGtkndq9wZmDC/patches/c47f80b133e5c0b930cbd21890e4fc535c854c16
-To rad://z3yXbb1sR6UG6ixxV2YF9jUP7ABra/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+To rad://zPHvWyMMwBBH24oGGtkndq9wZmDC/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
* [new reference] HEAD -> refs/patches
```
@@ -18,17 +18,17 @@ If we update the patch, the URL is also output.
``` (stderr)
$ git commit --amend --allow-empty -q -m "Other changes"
$ git push -f
-✓ Patch acab0ec updated to revision f7a830d829d0cdf398f63a32b0d5ee31f08e21ab
-To compare against your previous revision acab0ec, run:
+✓ Patch c47f80b updated to revision 9c9e16fdeac971460cff8d4dbd4fbfd651bc1e72
+To compare against your previous revision c47f80b, run:
git range-diff f2de534[..] e12525d[..] b2b6432[..]
✓ Synced with 1 node(s)
- https://app.radicle.xyz/nodes/[..]/rad:z3yXbb1sR6UG6ixxV2YF9jUP7ABra/patches/acab0ec777a97d013f30be5d5d1aec32562ecb02
+ https://app.radicle.xyz/nodes/[..]/rad:zPHvWyMMwBBH24oGGtkndq9wZmDC/patches/c47f80b133e5c0b930cbd21890e4fc535c854c16
-To rad://z3yXbb1sR6UG6ixxV2YF9jUP7ABra/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
- + e12525d...b2b6432 changes -> patches/acab0ec777a97d013f30be5d5d1aec32562ecb02 (forced update)
+To rad://zPHvWyMMwBBH24oGGtkndq9wZmDC/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+ + e12525d...b2b6432 changes -> patches/c47f80b133e5c0b930cbd21890e4fc535c854c16 (forced update)
```
While simply pushing a commit outputs a URL to the new source tree.
@@ -37,12 +37,12 @@ While simply pushing a commit outputs a URL to the new source tree.
$ git checkout master -q
$ git merge changes -q
$ git push rad master
-✓ Patch acab0ec777a97d013f30be5d5d1aec32562ecb02 merged
-✓ Canonical head updated to b2b6432af93f8fe188e32d400263021b602cfec8
+✓ Patch c47f80b133e5c0b930cbd21890e4fc535c854c16 merged
+✓ Canonical head for refs/heads/master updated to b2b6432af93f8fe188e32d400263021b602cfec8
✓ Synced with 1 node(s)
- https://app.radicle.xyz/nodes/[..]/rad:z3yXbb1sR6UG6ixxV2YF9jUP7ABra/tree/b2b6432af93f8fe188e32d400263021b602cfec8
+ https://app.radicle.xyz/nodes/[..]/rad:zPHvWyMMwBBH24oGGtkndq9wZmDC/tree/b2b6432af93f8fe188e32d400263021b602cfec8
-To rad://z3yXbb1sR6UG6ixxV2YF9jUP7ABra/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+To rad://zPHvWyMMwBBH24oGGtkndq9wZmDC/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
f2de534..b2b6432 master -> master
```
diff --git a/radicle-cli/examples/rad-patch-pull-update.md b/radicle-cli/examples/rad-patch-pull-update.md
index 6eef5e71c..fa42160b6 100644
--- a/radicle-cli/examples/rad-patch-pull-update.md
+++ b/radicle-cli/examples/rad-patch-pull-update.md
@@ -9,7 +9,7 @@ Initializing public radicle 👾 repository in [..]
✓ Repository heartwood created.
-Your Repository ID (RID) is rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK.
+Your Repository ID (RID) is rad:z3Lr338KCqbiwiLSh9DQZxTiLQUHg.
You can show it any time by running `rad .` from this directory.
✓ Repository successfully announced to the network.
@@ -21,9 +21,9 @@ To push changes, run `git push`.
```
``` ~bob
-$ rad clone rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK
-✓ Seeding policy updated for rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK with scope 'all'
-✓ Fetching rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK from z6MknSL…StBU8Vi@[..]..
+$ rad clone rad:z3Lr338KCqbiwiLSh9DQZxTiLQUHg
+✓ Seeding policy updated for rad:z3Lr338KCqbiwiLSh9DQZxTiLQUHg with scope 'all'
+✓ Fetching rad:z3Lr338KCqbiwiLSh9DQZxTiLQUHg from z6MknSL…StBU8Vi@[..]..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
@@ -43,7 +43,7 @@ our fork:
$ cd heartwood
$ git push rad master
✓ Synced with 1 node(s)
-To rad://zhbMU4DUXrzB8xT6qAJh6yZ7bFMK/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+To rad://z3Lr338KCqbiwiLSh9DQZxTiLQUHg/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
* [new branch] master -> master
```
@@ -53,22 +53,22 @@ Bob then opens a patch.
$ git checkout -b bob/feature -q
$ git commit --allow-empty -m "Bob's commit #1" -q
$ git push rad -o sync -o patch.message="Bob's patch" HEAD:refs/patches
-✓ Patch 55b9721ed7f6bfec38f43729e9b6631c5dc812fb opened
+✓ Patch f4563fc729c1361df8040cc26fd7bc7cf51a81fc opened
✓ Synced with 1 node(s)
-To rad://zhbMU4DUXrzB8xT6qAJh6yZ7bFMK/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+To rad://z3Lr338KCqbiwiLSh9DQZxTiLQUHg/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
* [new reference] HEAD -> refs/patches
```
``` ~bob
$ git status --short --branch
-## bob/feature...rad/patches/55b9721ed7f6bfec38f43729e9b6631c5dc812fb
+## bob/feature...rad/patches/f4563fc729c1361df8040cc26fd7bc7cf51a81fc
```
Alice checks it out.
``` ~alice
-$ rad patch checkout 55b9721ed7f6bfec38f43729e9b6631c5dc812fb
-✓ Switched to branch patch/55b9721 at revision 55b9721
-✓ Branch patch/55b9721 setup to track rad/patches/55b9721ed7f6bfec38f43729e9b6631c5dc812fb
+$ rad patch checkout f4563fc729c1361df8040cc26fd7bc7cf51a81fc
+✓ Switched to branch patch/f4563fc at revision f4563fc
+✓ Branch patch/f4563fc setup to track rad/patches/f4563fc729c1361df8040cc26fd7bc7cf51a81fc
$ git show
commit bdcdb30b3c0f513620dd0f1c24ff8f4f71de956b
Author: radicle <radicle@localhost>
@@ -82,23 +82,23 @@ Bob then updates the patch.
``` ~bob (stderr)
$ git commit --allow-empty -m "Bob's commit #2" -q
$ git push rad -o sync -o patch.message="Updated."
-✓ Patch 55b9721 updated to revision f91e056da05b2d9a58af1160c76245bc3debf7a8
-To compare against your previous revision 55b9721, run:
+✓ Patch f4563fc updated to revision ae9105fdd4c9d91ea920f8a651e088a3bbdab830
+To compare against your previous revision f4563fc, run:
git range-diff f2de534[..] bdcdb30[..] cad2666[..]
✓ Synced with 1 node(s)
-To rad://zhbMU4DUXrzB8xT6qAJh6yZ7bFMK/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
- bdcdb30..cad2666 bob/feature -> patches/55b9721ed7f6bfec38f43729e9b6631c5dc812fb
+To rad://z3Lr338KCqbiwiLSh9DQZxTiLQUHg/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+ bdcdb30..cad2666 bob/feature -> patches/f4563fc729c1361df8040cc26fd7bc7cf51a81fc
```
Alice pulls the update.
``` ~alice
-$ rad patch show 55b9721
+$ rad patch show f4563fc
╭─────────────────────────────────────────────────────────────────────╮
│ Title Bob's patch │
-│ Patch 55b9721ed7f6bfec38f43729e9b6631c5dc812fb │
+│ Patch f4563fc729c1361df8040cc26fd7bc7cf51a81fc │
│ Author bob z6Mkt67…v4N1tRk │
│ Head cad2666a8a2250e4dee175ed5044be2c251ff08b │
│ Commits ahead 2, behind 0 │
@@ -108,16 +108,16 @@ $ rad patch show 55b9721
│ bdcdb30 Bob's commit #1 │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by bob z6Mkt67…v4N1tRk (bdcdb30) now │
-│ ↑ updated to f91e056da05b2d9a58af1160c76245bc3debf7a8 (cad2666) now │
+│ ↑ updated to ae9105fdd4c9d91ea920f8a651e088a3bbdab830 (cad2666) now │
╰─────────────────────────────────────────────────────────────────────╯
$ git ls-remote rad
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
-cad2666a8a2250e4dee175ed5044be2c251ff08b refs/heads/patches/55b9721ed7f6bfec38f43729e9b6631c5dc812fb
+cad2666a8a2250e4dee175ed5044be2c251ff08b refs/heads/patches/f4563fc729c1361df8040cc26fd7bc7cf51a81fc
```
``` ~alice
$ git fetch rad
$ git status --short --branch
-## patch/55b9721...rad/patches/55b9721ed7f6bfec38f43729e9b6631c5dc812fb [behind 1]
+## patch/f4563fc...rad/patches/f4563fc729c1361df8040cc26fd7bc7cf51a81fc [behind 1]
```
``` ~alice
$ git pull
diff --git a/radicle-cli/examples/rad-patch-revert-merge.md b/radicle-cli/examples/rad-patch-revert-merge.md
index efb33561b..6546a5016 100644
--- a/radicle-cli/examples/rad-patch-revert-merge.md
+++ b/radicle-cli/examples/rad-patch-revert-merge.md
@@ -4,26 +4,26 @@ Let's create a patch, merge it and then revert it.
$ git checkout -b feature/1 -q
$ git commit --allow-empty -q -m "First change"
$ git push rad HEAD:refs/patches
-✓ Patch 696ec5508494692899337afe6713fe1796d0315c opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
$ git checkout master
Switched to branch 'master'
$ git merge feature/1
$ git push rad master
-✓ Patch 696ec5508494692899337afe6713fe1796d0315c merged
-✓ Canonical head updated to 20aa5dde6210796c3a2f04079b42316a31d02689
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084 merged
+✓ Canonical head for refs/heads/master updated to 20aa5dde6210796c3a2f04079b42316a31d02689
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..20aa5dd master -> master
```
First we see the patch as merged.
```
-$ rad patch show 696ec5508494692899337afe6713fe1796d0315c
+$ rad patch show 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084
╭────────────────────────────────────────────────────────────────╮
│ Title First change │
-│ Patch 696ec5508494692899337afe6713fe1796d0315c │
+│ Patch 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084 │
│ Author alice (you) │
│ Head 20aa5dde6210796c3a2f04079b42316a31d02689 │
│ Branches feature/1, master │
@@ -33,7 +33,7 @@ $ rad patch show 696ec5508494692899337afe6713fe1796d0315c
│ 20aa5dd First change │
├────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (20aa5dd) now │
-│ └─ ✓ merged by alice (you) at revision 696ec55 (20aa5dd) now │
+│ └─ ✓ merged by alice (you) at revision 09a3de4 (20aa5dd) now │
╰────────────────────────────────────────────────────────────────╯
```
@@ -49,19 +49,19 @@ When pushing, notice that we're told our patch is reverted.
``` (stderr) RAD_SOCKET=/dev/null
$ git push rad master --force
-! Patch 696ec5508494692899337afe6713fe1796d0315c reverted at revision 696ec55
-✓ Canonical head updated to f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+! Patch 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084 reverted at revision 09a3de4
+✓ Canonical head for refs/heads/master updated to f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 20aa5dd...f2de534 master -> master (forced update)
```
The patch shows up as open again.
```
-$ rad patch show 696ec5508494692899337afe6713fe1796d0315c
+$ rad patch show 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084
╭────────────────────────────────────────────────────╮
│ Title First change │
-│ Patch 696ec5508494692899337afe6713fe1796d0315c │
+│ Patch 09a3de4ac2c4d012c4a9c84c0cb306a066a0b084 │
│ Author alice (you) │
│ Head 20aa5dde6210796c3a2f04079b42316a31d02689 │
│ Branches feature/1 │
diff --git a/radicle-cli/examples/rad-patch-update.md b/radicle-cli/examples/rad-patch-update.md
index 1b897efc0..0be8a6448 100644
--- a/radicle-cli/examples/rad-patch-update.md
+++ b/radicle-cli/examples/rad-patch-update.md
@@ -6,16 +6,16 @@ $ git commit -q -m "Not a real change" --allow-empty
```
``` (stderr)
$ git push rad HEAD:refs/patches
-✓ Patch b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch e5ae577cdccf08de7dde10f7c136c75e5fa17633 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
```
-$ rad patch show b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5
+$ rad patch show e5ae577cdccf08de7dde10f7c136c75e5fa17633
╭────────────────────────────────────────────────────╮
│ Title Not a real change │
-│ Patch b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 │
+│ Patch e5ae577cdccf08de7dde10f7c136c75e5fa17633 │
│ Author alice (you) │
│ Head 51b2f0f77b9849bfaa3e9d3ff68ee2f57771d20c │
│ Branches feature/1 │
@@ -46,17 +46,17 @@ Now, instead of using `git push` to update the patch, as we normally would,
we run:
```
-$ rad patch update b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 -m "Updated patch" --no-announce
-ea7def3857f62f404606d7cd6490cd0de4eaebd1
+$ rad patch update e5ae577cdccf08de7dde10f7c136c75e5fa17633 -m "Updated patch" --no-announce
+d65bde5c0374b4488406c75a3fbef395067726fb
```
The command outputs the new Revision ID, which we can now see here:
```
-$ rad patch show b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5
+$ rad patch show e5ae577cdccf08de7dde10f7c136c75e5fa17633
╭─────────────────────────────────────────────────────────────────────╮
│ Title Not a real change │
-│ Patch b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 │
+│ Patch e5ae577cdccf08de7dde10f7c136c75e5fa17633 │
│ Author alice (you) │
│ Head 4d272148458a17620541555b1f0905c01658aa9f │
│ Branches feature/1 │
@@ -67,6 +67,6 @@ $ rad patch show b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5
│ 51b2f0f Not a real change │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (51b2f0f) now │
-│ ↑ updated to ea7def3857f62f404606d7cd6490cd0de4eaebd1 (4d27214) now │
+│ ↑ updated to d65bde5c0374b4488406c75a3fbef395067726fb (4d27214) now │
╰─────────────────────────────────────────────────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-patch-via-push.md b/radicle-cli/examples/rad-patch-via-push.md
index b21a8cc40..501d90c90 100644
--- a/radicle-cli/examples/rad-patch-via-push.md
+++ b/radicle-cli/examples/rad-patch-via-push.md
@@ -8,21 +8,21 @@ $ git checkout -b feature/1
Switched to a new branch 'feature/1'
$ git commit -a -m "Add things" -q --allow-empty
$ git push -o patch.message="Add things #1" -o patch.message="See commits for details." rad HEAD:refs/patches
-✓ Patch 6035d2f582afbe01ff23ea87528ae523d76875b6 opened
-hint: to update, run `git push` or `git push rad -f HEAD:patches/6035d2f582afbe01ff23ea87528ae523d76875b6`
+✓ Patch a183e324b82e94c548eb43b7acb7c7d92ebe7761 opened
+hint: to update, run `git push` or `git push rad -f HEAD:patches/a183e324b82e94c548eb43b7acb7c7d92ebe7761`
hint: offline push, your node is not running
hint: to sync with the network, run `rad node start`
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
We can see a patch was created:
```
-$ rad patch show 6035d2f582afbe01ff23ea87528ae523d76875b6
+$ rad patch show a183e324b82e94c548eb43b7acb7c7d92ebe7761
╭────────────────────────────────────────────────────╮
│ Title Add things #1 │
-│ Patch 6035d2f582afbe01ff23ea87528ae523d76875b6 │
+│ Patch a183e324b82e94c548eb43b7acb7c7d92ebe7761 │
│ Author alice (you) │
│ Head 42d894a83c9c356552a57af09ccdbd5587a99045 │
│ Branches feature/1 │
@@ -42,7 +42,7 @@ branch associated with this patch:
```
$ git branch -vv
-* feature/1 42d894a [rad/patches/6035d2f582afbe01ff23ea87528ae523d76875b6] Add things
+* feature/1 42d894a [rad/patches/a183e324b82e94c548eb43b7acb7c7d92ebe7761] Add things
master f2de534 [rad/master] Second commit
```
@@ -50,7 +50,7 @@ Let's check that it's up to date with our local head:
```
$ git status --short --branch
-## feature/1...rad/patches/6035d2f582afbe01ff23ea87528ae523d76875b6
+## feature/1...rad/patches/a183e324b82e94c548eb43b7acb7c7d92ebe7761
$ git fetch
$ git push
```
@@ -62,14 +62,14 @@ $ git show-ref
42d894a83c9c356552a57af09ccdbd5587a99045 refs/heads/feature/1
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/remotes/rad/master
-42d894a83c9c356552a57af09ccdbd5587a99045 refs/remotes/rad/patches/6035d2f582afbe01ff23ea87528ae523d76875b6
+42d894a83c9c356552a57af09ccdbd5587a99045 refs/remotes/rad/patches/a183e324b82e94c548eb43b7acb7c7d92ebe7761
```
```
-$ git ls-remote rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji 'refs/heads/patches/*'
-42d894a83c9c356552a57af09ccdbd5587a99045 refs/heads/patches/6035d2f582afbe01ff23ea87528ae523d76875b6
-$ git ls-remote rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 'refs/cobs/*'
-0656c217f917c3e06234771e9ecae53aba5e173e refs/cobs/xyz.radicle.id/0656c217f917c3e06234771e9ecae53aba5e173e
-6035d2f582afbe01ff23ea87528ae523d76875b6 refs/cobs/xyz.radicle.patch/6035d2f582afbe01ff23ea87528ae523d76875b6
+$ git ls-remote rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 'refs/heads/patches/*'
+42d894a83c9c356552a57af09ccdbd5587a99045 refs/heads/patches/a183e324b82e94c548eb43b7acb7c7d92ebe7761
+$ git ls-remote rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 'refs/cobs/*'
+eeb8b44890570ccf85db7f3cb2a475100a27408a refs/cobs/xyz.radicle.id/eeb8b44890570ccf85db7f3cb2a475100a27408a
+a183e324b82e94c548eb43b7acb7c7d92ebe7761 refs/cobs/xyz.radicle.patch/a183e324b82e94c548eb43b7acb7c7d92ebe7761
```
We can create another patch:
@@ -78,8 +78,8 @@ We can create another patch:
$ git checkout -b feature/2 -q master
$ git commit -a -m "Add more things" -q --allow-empty
$ git push rad HEAD:refs/patches
-✓ Patch 95808913573cead52ad7b42c7b475260ec45c4b2 opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch 9831feba4a7cef108346b32703023934b9265f66 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -87,8 +87,8 @@ We see both branches with upstreams now:
```
$ git branch -vv
- feature/1 42d894a [rad/patches/6035d2f582afbe01ff23ea87528ae523d76875b6] Add things
-* feature/2 8b0ea80 [rad/patches/95808913573cead52ad7b42c7b475260ec45c4b2] Add more things
+ feature/1 42d894a [rad/patches/a183e324b82e94c548eb43b7acb7c7d92ebe7761] Add things
+* feature/2 8b0ea80 [rad/patches/9831feba4a7cef108346b32703023934b9265f66] Add more things
master f2de534 [rad/master] Second commit
```
@@ -99,8 +99,8 @@ $ rad patch
╭───────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │
├───────────────────────────────────────────────────────────────────────────────┤
-│ ● 6035d2f Add things #1 alice (you) - 42d894a +0 -0 now │
-│ ● 9580891 Add more things alice (you) - 8b0ea80 +0 -0 now │
+│ ● 9831feb Add more things alice (you) - 8b0ea80 +0 -0 now │
+│ ● a183e32 Add things #1 alice (you) - 42d894a +0 -0 now │
╰───────────────────────────────────────────────────────────────────────────────╯
```
@@ -112,13 +112,13 @@ $ git commit -a -m "Improve code" -q --allow-empty
``` (stderr)
$ git push rad
-✓ Patch 9580891 updated to revision d7040c6c97629c2b94f86fb639bebbff5de39697
-To compare against your previous revision 9580891, run:
+✓ Patch 9831feb updated to revision d5bf76a631d5b47eb80ab99014d5f9c170fa3421
+To compare against your previous revision 9831feb, run:
git range-diff f2de534[..] 8b0ea80[..] 02bef3f[..]
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- 8b0ea80..02bef3f feature/2 -> patches/95808913573cead52ad7b42c7b475260ec45c4b2
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 8b0ea80..02bef3f feature/2 -> patches/9831feba4a7cef108346b32703023934b9265f66
```
This last `git push` worked without specifying an upstream branch despite the
@@ -136,10 +136,10 @@ This allows for pushing to the remote patch branch without using the full
We can then see that the patch head has moved:
```
-$ rad patch show 9580891
+$ rad patch show 9831feb
╭─────────────────────────────────────────────────────────────────────╮
│ Title Add more things │
-│ Patch 95808913573cead52ad7b42c7b475260ec45c4b2 │
+│ Patch 9831feba4a7cef108346b32703023934b9265f66 │
│ Author alice (you) │
│ Head 02bef3fac41b2f98bb3c02b868a53ddfecb55b5f │
│ Branches feature/2 │
@@ -150,7 +150,7 @@ $ rad patch show 9580891
│ 8b0ea80 Add more things │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (8b0ea80) now │
-│ ↑ updated to d7040c6c97629c2b94f86fb639bebbff5de39697 (02bef3f) now │
+│ ↑ updated to d5bf76a631d5b47eb80ab99014d5f9c170fa3421 (02bef3f) now │
╰─────────────────────────────────────────────────────────────────────╯
```
@@ -163,14 +163,14 @@ $ git rev-parse HEAD
```
$ git status --short --branch
-## feature/2...rad/patches/95808913573cead52ad7b42c7b475260ec45c4b2
+## feature/2...rad/patches/9831feba4a7cef108346b32703023934b9265f66
```
```
-$ git rev-parse refs/remotes/rad/patches/95808913573cead52ad7b42c7b475260ec45c4b2
+$ git rev-parse refs/remotes/rad/patches/9831feba4a7cef108346b32703023934b9265f66
02bef3fac41b2f98bb3c02b868a53ddfecb55b5f
-$ git ls-remote rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi refs/heads/patches/95808913573cead52ad7b42c7b475260ec45c4b2
-02bef3fac41b2f98bb3c02b868a53ddfecb55b5f refs/heads/patches/95808913573cead52ad7b42c7b475260ec45c4b2
+$ git ls-remote rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi refs/heads/patches/9831feba4a7cef108346b32703023934b9265f66
+02bef3fac41b2f98bb3c02b868a53ddfecb55b5f refs/heads/patches/9831feba4a7cef108346b32703023934b9265f66
```
## Force push
@@ -190,9 +190,9 @@ Now let's push to the patch head.
``` (stderr) (fail)
$ git push
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- ! [rejected] feature/2 -> patches/95808913573cead52ad7b42c7b475260ec45c4b2 (non-fast-forward)
-error: failed to push some refs to 'rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ ! [rejected] feature/2 -> patches/9831feba4a7cef108346b32703023934b9265f66 (non-fast-forward)
+error: failed to push some refs to 'rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
hint: [..]
hint: [..]
hint: [..]
@@ -204,22 +204,22 @@ use `--force` to force the update.
``` (stderr)
$ git push --force
-✓ Patch 9580891 updated to revision 670d02794aa05afd6e0851f4aa848bc87c4712c7
-To compare against your previous revision d7040c6, run:
+✓ Patch 9831feb updated to revision af84c8793b8eabbfc07f125c247334a388b143ea
+To compare against your previous revision d5bf76a, run:
git range-diff f2de534[..] 02bef3f[..] 9304dbc[..]
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- + 02bef3f...9304dbc feature/2 -> patches/95808913573cead52ad7b42c7b475260ec45c4b2 (forced update)
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ + 02bef3f...9304dbc feature/2 -> patches/9831feba4a7cef108346b32703023934b9265f66 (forced update)
```
That worked. We can see the new revision if we call `rad patch show`:
```
-$ rad patch show 9580891
+$ rad patch show 9831feb
╭─────────────────────────────────────────────────────────────────────╮
│ Title Add more things │
-│ Patch 95808913573cead52ad7b42c7b475260ec45c4b2 │
+│ Patch 9831feba4a7cef108346b32703023934b9265f66 │
│ Author alice (you) │
│ Head 9304dbc445925187994a7a93222a3f8bde73b785 │
│ Branches feature/2 │
@@ -230,8 +230,8 @@ $ rad patch show 9580891
│ 8b0ea80 Add more things │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (8b0ea80) now │
-│ ↑ updated to d7040c6c97629c2b94f86fb639bebbff5de39697 (02bef3f) now │
-│ ↑ updated to 670d02794aa05afd6e0851f4aa848bc87c4712c7 (9304dbc) now │
+│ ↑ updated to d5bf76a631d5b47eb80ab99014d5f9c170fa3421 (02bef3f) now │
+│ ↑ updated to af84c8793b8eabbfc07f125c247334a388b143ea (9304dbc) now │
╰─────────────────────────────────────────────────────────────────────╯
```
@@ -242,7 +242,7 @@ we should get an error:
``` (stderr) (fail)
$ git push rad master:refs/patches
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
! [remote rejected] master -> refs/patches (patch commits are already included in the base branch)
-error: failed to push some refs to 'rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
+error: failed to push some refs to 'rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
```
diff --git a/radicle-cli/examples/rad-patch.md b/radicle-cli/examples/rad-patch.md
index aaef9fef6..2ae1b4d91 100644
--- a/radicle-cli/examples/rad-patch.md
+++ b/radicle-cli/examples/rad-patch.md
@@ -1,6 +1,6 @@
When contributing to another's project, it is common for the contribution to be
of many commits and involve a discussion with the project's maintainer. This is supported
-via Radicle's patches.
+via Radicle's Patches.
Here we give a brief overview for using patches in our hypothetical car
scenario. It turns out instructions containing the power requirements were
@@ -26,8 +26,8 @@ Once the code is ready, we open (or create) a patch with our changes for the pro
``` (stderr)
$ git push rad -o patch.message="Define power requirements" -o patch.message="See details." HEAD:refs/patches
-✓ Patch aa45913e757cacd46972733bddee5472c78fa32a opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch c90967c43719b916e0b5a8b5dafe353608f8a08a opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -38,14 +38,14 @@ $ rad patch
╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │
├─────────────────────────────────────────────────────────────────────────────────────────┤
-│ ● aa45913 Define power requirements alice (you) - 3e674d1 +0 -0 now │
+│ ● c90967c Define power requirements alice (you) - 3e674d1 +0 -0 now │
╰─────────────────────────────────────────────────────────────────────────────────────────╯
```
```
-$ rad patch show aa45913e757cacd46972733bddee5472c78fa32a -p
+$ rad patch show c90967c43719b916e0b5a8b5dafe353608f8a08a -p
╭────────────────────────────────────────────────────╮
│ Title Define power requirements │
-│ Patch aa45913e757cacd46972733bddee5472c78fa32a │
+│ Patch c90967c43719b916e0b5a8b5dafe353608f8a08a │
│ Author alice (you) │
│ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │
│ Branches flux-capacitor-power │
@@ -78,14 +78,14 @@ $ rad patch list --authored
╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │
├─────────────────────────────────────────────────────────────────────────────────────────┤
-│ ● aa45913 Define power requirements alice (you) - 3e674d1 +0 -0 now │
+│ ● c90967c Define power requirements alice (you) - 3e674d1 +0 -0 now │
╰─────────────────────────────────────────────────────────────────────────────────────────╯
```
We can also see that it set an upstream for our patch branch:
```
$ git branch -vv
-* flux-capacitor-power 3e674d1 [rad/patches/aa45913e757cacd46972733bddee5472c78fa32a] Define power requirements
+* flux-capacitor-power 3e674d1 [rad/patches/c90967c43719b916e0b5a8b5dafe353608f8a08a] Define power requirements
master f2de534 [rad/master] Second commit
```
@@ -93,12 +93,12 @@ We can also label patches as well as assign DIDs to the patch to help
organise your workflow:
```
-$ rad patch label aa45913 --add fun --no-announce
-$ rad patch assign aa45913 --add did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --no-announce
-$ rad patch show aa45913
+$ rad patch label c90967c --add fun --no-announce
+$ rad patch assign c90967c --add did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --no-announce
+$ rad patch show c90967c
╭────────────────────────────────────────────────────╮
│ Title Define power requirements │
-│ Patch aa45913e757cacd46972733bddee5472c78fa32a │
+│ Patch c90967c43719b916e0b5a8b5dafe353608f8a08a │
│ Author alice (you) │
│ Labels fun │
│ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │
@@ -126,33 +126,33 @@ $ git commit --message "Add README, just for the fun"
```
``` (stderr)
$ git push rad -o patch.message="Add README, just for the fun"
-✓ Patch aa45913 updated to revision 6e5a3b7b2ce27b32e7ccc2f0b3f4594897dde638
-To compare against your previous revision aa45913, run:
+✓ Patch c90967c updated to revision a7fe44ba5d9c2339b0e9731874791db375aeebbe
+To compare against your previous revision c90967c, run:
git range-diff f2de534[..] 3e674d1[..] 27857ec[..]
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- 3e674d1..27857ec flux-capacitor-power -> patches/aa45913e757cacd46972733bddee5472c78fa32a
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 3e674d1..27857ec flux-capacitor-power -> patches/c90967c43719b916e0b5a8b5dafe353608f8a08a
```
And let's leave a quick comment for our team:
```
-$ rad patch comment aa45913 --message 'I cannot wait to get back to the 90s!' --no-announce
+$ rad patch comment c90967c --message 'I cannot wait to get back to the 90s!' --no-announce
╭───────────────────────────────────────╮
-│ alice (you) now 686ec1c │
+│ alice (you) now 055f0d2 │
│ I cannot wait to get back to the 90s! │
╰───────────────────────────────────────╯
-$ rad patch comment aa45913 --message 'My favorite decade!' --reply-to 686ec1c -q --no-announce
-f4336e42daf76342f787d574b5ee779d89d05c7a
+$ rad patch comment c90967c --message 'My favorite decade!' --reply-to 055f0d2 -q --no-announce
+84336c0ffd31e607839d2f4dd3389556dd766124
```
If we realize we made a mistake in the comment, we can go back and edit it:
```
-$ rad patch comment aa45913 --edit 686ec1c --message 'I cannot wait to get back to the 80s!' --no-announce
+$ rad patch comment c90967c --edit 055f0d2 --message 'I cannot wait to get back to the 80s!' --no-announce
╭───────────────────────────────────────╮
-│ alice (you) now 686ec1c │
+│ alice (you) now 055f0d2 │
│ I cannot wait to get back to the 80s! │
╰───────────────────────────────────────╯
```
@@ -160,36 +160,36 @@ $ rad patch comment aa45913 --edit 686ec1c --message 'I cannot wait to get back
And if we really made a mistake, then we can redact the comment entirely:
```
-$ rad patch comment aa45913 --redact f4336e4 --no-announce
-✓ Redacted comment f4336e42daf76342f787d574b5ee779d89d05c7a
+$ rad patch comment c90967c --redact 84336c0 --no-announce
+✓ Redacted comment 84336c0ffd31e607839d2f4dd3389556dd766124
```
Now, let's checkout the patch that we just created:
```
-$ rad patch checkout aa45913
-✓ Switched to branch patch/aa45913 at revision 6e5a3b7
-✓ Branch patch/aa45913 setup to track rad/patches/aa45913e757cacd46972733bddee5472c78fa32a
+$ rad patch checkout c90967c
+✓ Switched to branch patch/c90967c at revision a7fe44b
+✓ Branch patch/c90967c setup to track rad/patches/c90967c43719b916e0b5a8b5dafe353608f8a08a
```
We can also add a review verdict as such:
```
-$ rad patch review aa45913 --accept --no-message --no-announce
-✓ Patch aa45913 accepted
+$ rad patch review c90967c --accept --no-message --no-announce
+✓ Patch c90967c accepted
```
Showing the patch list now will reveal the favorable verdict:
```
-$ rad patch show aa45913
+$ rad patch show c90967c
╭─────────────────────────────────────────────────────────────────────╮
│ Title Define power requirements │
-│ Patch aa45913e757cacd46972733bddee5472c78fa32a │
+│ Patch c90967c43719b916e0b5a8b5dafe353608f8a08a │
│ Author alice (you) │
│ Labels fun │
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
-│ Branches flux-capacitor-power, patch/aa45913 │
+│ Branches flux-capacitor-power, patch/c90967c │
│ Commits ahead 2, behind 0 │
│ Status open │
│ │
@@ -199,29 +199,29 @@ $ rad patch show aa45913
│ 3e674d1 Define power requirements │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (3e674d1) now │
-│ ↑ updated to 6e5a3b7b2ce27b32e7ccc2f0b3f4594897dde638 (27857ec) now │
+│ ↑ updated to a7fe44ba5d9c2339b0e9731874791db375aeebbe (27857ec) now │
│ └─ ✓ accepted by alice (you) now │
╰─────────────────────────────────────────────────────────────────────╯
$ rad patch list
╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │
├─────────────────────────────────────────────────────────────────────────────────────────┤
-│ ● aa45913 Define power requirements alice (you) ✔ 27857ec +0 -0 now │
+│ ● c90967c Define power requirements alice (you) ✔ 27857ec +0 -0 now │
╰─────────────────────────────────────────────────────────────────────────────────────────╯
```
If you make a mistake on the patch description, you can always change it!
```
-$ rad patch edit aa45913 --message "Define power requirements" --message "Add requirements file" --no-announce
-$ rad patch show aa45913
+$ rad patch edit c90967c --message "Define power requirements" --message "Add requirements file" --no-announce
+$ rad patch show c90967c
╭─────────────────────────────────────────────────────────────────────╮
│ Title Define power requirements │
-│ Patch aa45913e757cacd46972733bddee5472c78fa32a │
+│ Patch c90967c43719b916e0b5a8b5dafe353608f8a08a │
│ Author alice (you) │
│ Labels fun │
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
-│ Branches flux-capacitor-power, patch/aa45913 │
+│ Branches flux-capacitor-power, patch/c90967c │
│ Commits ahead 2, behind 0 │
│ Status open │
│ │
@@ -231,7 +231,7 @@ $ rad patch show aa45913
│ 3e674d1 Define power requirements │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by alice (you) (3e674d1) now │
-│ ↑ updated to 6e5a3b7b2ce27b32e7ccc2f0b3f4594897dde638 (27857ec) now │
+│ ↑ updated to a7fe44ba5d9c2339b0e9731874791db375aeebbe (27857ec) now │
│ └─ ✓ accepted by alice (you) now │
╰─────────────────────────────────────────────────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-publish.md b/radicle-cli/examples/rad-publish.md
index e1145a007..7d4f26e9a 100644
--- a/radicle-cli/examples/rad-publish.md
+++ b/radicle-cli/examples/rad-publish.md
@@ -15,7 +15,7 @@ public
The repository is now in our inventory:
```
$ rad node inventory
-rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu
+rad:z2gud85wgGxzN7MNvi8wDEBFqLqmT
```
If we try to publish again, we get an error:
@@ -34,11 +34,11 @@ repository private again __will not_ be replicated.
```
$ rad id update --visibility private --title "Privatise" --description "Reverting the rad publish event"
-✓ Identity revision 774cc1e72641d97d7dc9377745b7f454a9171747 created
+✓ Identity revision 26ed629bb7c835bd94537e8226ca359c53b32cd3 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Privatise │
-│ Revision 774cc1e72641d97d7dc9377745b7f454a9171747 │
-│ Blob 88f759a4d46e9535766fccec0cbfe1fed6160b1a │
+│ Revision 26ed629bb7c835bd94537e8226ca359c53b32cd3 │
+│ Blob 792ab13be76827e022b71941582a1b6217e6368a │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State accepted │
│ Quorum yes │
@@ -48,8 +48,9 @@ $ rad id update --visibility private --title "Privatise" --description "Revertin
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰────────────────────────────────────────────────────────────────────────╯
-@@ -1,13 +1,16 @@
+@@ -1,21 +1,24 @@
{
+ "version": 2,
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
@@ -60,10 +61,16 @@ $ rad id update --visibility private --title "Privatise" --description "Revertin
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
-- "threshold": 1
-+ "threshold": 1,
+ "canonicalRefs": {
+ "rules": {
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+ }
++ },
+ "visibility": {
+ "type": "private"
-+ }
+ }
}
```
diff --git a/radicle-cli/examples/rad-push-and-pull-patches.md b/radicle-cli/examples/rad-push-and-pull-patches.md
index 0d3654439..5a6367159 100644
--- a/radicle-cli/examples/rad-push-and-pull-patches.md
+++ b/radicle-cli/examples/rad-push-and-pull-patches.md
@@ -12,34 +12,40 @@ $ git checkout -b alice/1 -q
$ git rev-parse HEAD
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
$ git checkout master -q
-$ rad patch checkout d004b67
-✓ Switched to branch patch/d004b67 at revision d004b67
-✓ Branch patch/d004b67 setup to track rad/patches/d004b67355456c46de10c0d287e4a791ad1a6945
+$ rad patch
+╭─────────────────────────────────────────────────────────────────────────────────╮
+│ ● ID Title Author Reviews Head + - Updated │
+├─────────────────────────────────────────────────────────────────────────────────┤
+│ ● 74aa72b Changes bob z6Mkt67…v4N1tRk - 8d5f1ba +0 -0 now │
+╰─────────────────────────────────────────────────────────────────────────────────╯
+$ rad patch checkout 74aa72b
+✓ Switched to branch patch/74aa72b at revision 74aa72b
+✓ Branch patch/74aa72b setup to track rad/patches/74aa72bbd79a88dcb5ae98bb6d06bb493b5ed4c5
$ rad remote add z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
✓ Follow policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
✓ Remote bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk added
✓ Remote-tracking branch bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk/master created for z6Mkt67…v4N1tRk
$ git checkout master -q
$ cat .git/FETCH_HEAD
-f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 not-for-merge branch 'master' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
-8d5f1bae4b69d8e3f6cbfc6f4bd675ed19990afc not-for-merge branch 'patches/d004b67355456c46de10c0d287e4a791ad1a6945' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 not-for-merge branch 'master' of rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+8d5f1bae4b69d8e3f6cbfc6f4bd675ed19990afc not-for-merge branch 'patches/74aa72bbd79a88dcb5ae98bb6d06bb493b5ed4c5' of rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
$ git rev-parse master
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
```
``` ~alice (stderr)
-$ git checkout patch/d004b67 -q
+$ git checkout patch/74aa72b -q
$ git commit --allow-empty -m "Changes #2" -q
$ git push
-✓ Patch d004b67 updated to revision 2eb705c3da98e05c083df15be5b1bd6856a0bd77
-To compare against your previous revision d004b67, run:
+✓ Patch 74aa72b updated to revision 2bc70f5c1d567db16df991a10a618733f3e29d82
+To compare against your previous revision 74aa72b, run:
git range-diff f2de534[..] 8d5f1ba[..] c2aaf1c[..]
✓ Synced with 1 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- * [new branch] patch/d004b67 -> patches/d004b67355456c46de10c0d287e4a791ad1a6945
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ * [new branch] patch/74aa72b -> patches/74aa72bbd79a88dcb5ae98bb6d06bb493b5ed4c5
```
``` ~bob
@@ -50,32 +56,32 @@ $ git push
``` ~alice (stderr)
$ git checkout master -q
$ git pull
-From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
- + c2aaf1c...8d5f1ba patches/d004b67355456c46de10c0d287e4a791ad1a6945 -> rad/patches/d004b67355456c46de10c0d287e4a791ad1a6945 (forced update)
+From rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+ + c2aaf1c...8d5f1ba patches/74aa72bbd79a88dcb5ae98bb6d06bb493b5ed4c5 -> rad/patches/74aa72bbd79a88dcb5ae98bb6d06bb493b5ed4c5 (forced update)
$ git checkout - -q
$ git commit --allow-empty -m "Changes #3" -q
$ git push
-✓ Patch d004b67 updated to revision 7b5015a8dac188bb0d44a334aa68a51298750b07
-To compare against your previous revision d004b67, run:
+✓ Patch 74aa72b updated to revision c541164492cae34700c601bbe5fdf068183a7d6f
+To compare against your previous revision 74aa72b, run:
git range-diff f2de534[..] 8d5f1ba[..] d9f8caf[..]
✓ Synced with 1 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- c2aaf1c..d9f8caf patch/d004b67 -> patches/d004b67355456c46de10c0d287e4a791ad1a6945
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ c2aaf1c..d9f8caf patch/74aa72b -> patches/74aa72bbd79a88dcb5ae98bb6d06bb493b5ed4c5
```
``` ~alice
$ cat .git/FETCH_HEAD
-f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 branch 'master' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-8d5f1bae4b69d8e3f6cbfc6f4bd675ed19990afc not-for-merge branch 'patches/d004b67355456c46de10c0d287e4a791ad1a6945' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 branch 'master' of rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+8d5f1bae4b69d8e3f6cbfc6f4bd675ed19990afc not-for-merge branch 'patches/74aa72bbd79a88dcb5ae98bb6d06bb493b5ed4c5' of rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
```
``` ~bob (stderr)
$ git checkout master -q
$ git pull
-From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
- + c2aaf1c...8d5f1ba patches/d004b67355456c46de10c0d287e4a791ad1a6945 -> rad/patches/d004b67355456c46de10c0d287e4a791ad1a6945 (forced update)
+From rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+ + c2aaf1c...8d5f1ba patches/74aa72bbd79a88dcb5ae98bb6d06bb493b5ed4c5 -> rad/patches/74aa72bbd79a88dcb5ae98bb6d06bb493b5ed4c5 (forced update)
```
``` ~bob
diff --git a/radicle-cli/examples/rad-review-by-hunk.md b/radicle-cli/examples/rad-review-by-hunk.md
index d21b15e1d..6fbbc6324 100644
--- a/radicle-cli/examples/rad-review-by-hunk.md
+++ b/radicle-cli/examples/rad-review-by-hunk.md
@@ -61,8 +61,8 @@ $ git commit -q -m "Update files"
``` (stderr)
$ git push rad HEAD:refs/patches
-✓ Patch 7a2ac7e2841cc1e7394f99f107555a499b1d3f23 opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+✓ Patch d34084970fdd4de9d8125165f5ac39ac70d3806c opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
@@ -70,7 +70,7 @@ Finally, we do a review of the patch by hunk. The output of this command should
match `git diff master -W100% -U5 --patience`:
```
-$ rad patch review --patch -U5 7a2ac7e2841cc1e7394f99f107555a499b1d3f23 --no-announce
+$ rad patch review --patch -U5 d34084970fdd4de9d8125165f5ac39ac70d3806c --no-announce
╭──────────────────────╮
│ .gitignore ❲deleted❳ │
├──────────────────────┤
@@ -117,8 +117,8 @@ $ rad patch review --patch -U5 7a2ac7e2841cc1e7394f99f107555a499b1d3f23 --no-ann
Now let's accept these hunks one by one..
```
-$ rad patch review --patch --accept --hunk 1 7a2ac7e2841cc1e7394f99f107555a499b1d3f23 --no-announce
-✓ Loaded existing review ([..]) for patch 7a2ac7e2841cc1e7394f99f107555a499b1d3f23
+$ rad patch review --patch --accept --hunk 1 d34084970fdd4de9d8125165f5ac39ac70d3806c --no-announce
+✓ Loaded existing review ([..]) for patch d34084970fdd4de9d8125165f5ac39ac70d3806c
╭──────────────────────╮
│ .gitignore ❲deleted❳ │
├──────────────────────┤
@@ -128,8 +128,8 @@ $ rad patch review --patch --accept --hunk 1 7a2ac7e2841cc1e7394f99f107555a499b1
✓ Updated brain to a5fccf0e977225ff13c3f74c43faf4cb679bf835
```
```
-$ rad patch review --patch --accept --hunk 1 7a2ac7e2841cc1e7394f99f107555a499b1d3f23 --no-announce
-✓ Loaded existing review ([..]) for patch 7a2ac7e2841cc1e7394f99f107555a499b1d3f23
+$ rad patch review --patch --accept --hunk 1 d34084970fdd4de9d8125165f5ac39ac70d3806c --no-announce
+✓ Loaded existing review ([..]) for patch d34084970fdd4de9d8125165f5ac39ac70d3806c
╭──────────────────────────────────────────────────────────╮
│ DISCLAIMER.txt ❲created❳ │
├──────────────────────────────────────────────────────────┤
@@ -139,8 +139,8 @@ $ rad patch review --patch --accept --hunk 1 7a2ac7e2841cc1e7394f99f107555a499b1
✓ Updated brain to 2cdb82ea726e64d3b52847c7699d0d4759198f5c
```
```
-$ rad patch review --patch --accept -U3 --hunk 1 7a2ac7e2841cc1e7394f99f107555a499b1d3f23 --no-announce
-✓ Loaded existing review ([..]) for patch 7a2ac7e2841cc1e7394f99f107555a499b1d3f23
+$ rad patch review --patch --accept -U3 --hunk 1 d34084970fdd4de9d8125165f5ac39ac70d3806c --no-announce
+✓ Loaded existing review ([..]) for patch d34084970fdd4de9d8125165f5ac39ac70d3806c
╭─────────────────────────────╮
│ MENU.txt │
├─────────────────────────────┤
@@ -155,8 +155,8 @@ $ rad patch review --patch --accept -U3 --hunk 1 7a2ac7e2841cc1e7394f99f107555a4
✓ Updated brain to d4aecbb859a802a3215def0b538358bf63593953
```
```
-$ rad patch review --patch --accept -U3 --hunk 1 7a2ac7e2841cc1e7394f99f107555a499b1d3f23 --no-announce
-✓ Loaded existing review ([..]) for patch 7a2ac7e2841cc1e7394f99f107555a499b1d3f23
+$ rad patch review --patch --accept -U3 --hunk 1 d34084970fdd4de9d8125165f5ac39ac70d3806c --no-announce
+✓ Loaded existing review ([..]) for patch d34084970fdd4de9d8125165f5ac39ac70d3806c
╭───────────────────────────────────────╮
│ MENU.txt │
├───────────────────────────────────────┤
@@ -172,8 +172,8 @@ $ rad patch review --patch --accept -U3 --hunk 1 7a2ac7e2841cc1e7394f99f107555a4
```
```
-$ rad patch review --patch --accept --hunk 1 7a2ac7e2841cc1e7394f99f107555a499b1d3f23 --no-announce
-✓ Loaded existing review ([..]) for patch 7a2ac7e2841cc1e7394f99f107555a499b1d3f23
+$ rad patch review --patch --accept --hunk 1 d34084970fdd4de9d8125165f5ac39ac70d3806c --no-announce
+✓ Loaded existing review ([..]) for patch d34084970fdd4de9d8125165f5ac39ac70d3806c
╭────────────────────────────────────────────────────╮
│ INSTRUCTIONS.txt -> notes/INSTRUCTIONS.txt ❲moved❳ │
╰────────────────────────────────────────────────────╯
@@ -181,7 +181,7 @@ $ rad patch review --patch --accept --hunk 1 7a2ac7e2841cc1e7394f99f107555a499b1
```
```
-$ rad patch review --patch --accept --hunk 1 7a2ac7e2841cc1e7394f99f107555a499b1d3f23 --no-announce
-✓ Loaded existing review ([..]) for patch 7a2ac7e2841cc1e7394f99f107555a499b1d3f23
+$ rad patch review --patch --accept --hunk 1 d34084970fdd4de9d8125165f5ac39ac70d3806c --no-announce
+✓ Loaded existing review ([..]) for patch d34084970fdd4de9d8125165f5ac39ac70d3806c
✓ All hunks have been reviewed
```
diff --git a/radicle-cli/examples/rad-seed-and-follow.md b/radicle-cli/examples/rad-seed-and-follow.md
index ae6d4ff8d..10d9fc781 100644
--- a/radicle-cli/examples/rad-seed-and-follow.md
+++ b/radicle-cli/examples/rad-seed-and-follow.md
@@ -21,8 +21,8 @@ $ rad follow
Now let's seed one of Eve's repositories:
```
-$ rad seed rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed --no-fetch
-✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'followed'
+$ rad seed rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --scope followed --no-fetch
+✓ Seeding policy updated for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 with scope 'followed'
```
We can list the repositories we are seeding by omitting the RID:
@@ -32,6 +32,6 @@ $ rad seed
╭──────────────────────────────────────────────────────────────╮
│ Repository Name Policy Scope │
├──────────────────────────────────────────────────────────────┤
-│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji allow followed │
+│ rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 allow followed │
╰──────────────────────────────────────────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-seed-many.md b/radicle-cli/examples/rad-seed-many.md
index d06ee3d9f..87091ed0b 100644
--- a/radicle-cli/examples/rad-seed-many.md
+++ b/radicle-cli/examples/rad-seed-many.md
@@ -3,9 +3,9 @@ same time, where each repository specified will be fetched (unless `--no-fetch`
is used):
```
-$ rad seed rad:z3Rry7rpdWuGpfjPYGzdJKQADsoNW rad:z3zTnCfi6cVSZG8eCGn6AMDypgAPm
-✓ Seeding policy updated for rad:z3Rry7rpdWuGpfjPYGzdJKQADsoNW with scope 'all'
-✓ Fetching rad:z3Rry7rpdWuGpfjPYGzdJKQADsoNW from z6Mkt67…v4N1tRk@[..]
-✓ Seeding policy updated for rad:z3zTnCfi6cVSZG8eCGn6AMDypgAPm with scope 'all'
-✓ Fetching rad:z3zTnCfi6cVSZG8eCGn6AMDypgAPm from z6Mkt67…v4N1tRk@[..]
+$ rad seed rad:z2eCRs3yG5orX2AqYiozcedMzbwg5 rad:z2kYoZsp3L6PuDD6s2dizTwX7p6jk
+✓ Seeding policy updated for rad:z2eCRs3yG5orX2AqYiozcedMzbwg5 with scope 'all'
+✓ Fetching rad:z2eCRs3yG5orX2AqYiozcedMzbwg5 from z6Mkt67…v4N1tRk@[..]
+✓ Seeding policy updated for rad:z2kYoZsp3L6PuDD6s2dizTwX7p6jk with scope 'all'
+✓ Fetching rad:z2kYoZsp3L6PuDD6s2dizTwX7p6jk from z6Mkt67…v4N1tRk@[..]
```
diff --git a/radicle-cli/examples/rad-sync.md b/radicle-cli/examples/rad-sync.md
index 851b0382d..385c51977 100644
--- a/radicle-cli/examples/rad-sync.md
+++ b/radicle-cli/examples/rad-sync.md
@@ -15,9 +15,9 @@ $ rad sync status --sort-by alias
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
│ ● Node Address Status Tip Timestamp │
├──────────────────────────────────────────────────────────────────────────────────────────────┤
-│ ● alice (you) alice.radicle.example:8776 unannounced 056b1db [ ... ] │
-│ ● bob z6Mkt67…v4N1tRk bob.radicle.example:8776 out-of-sync 99c5497 [ ... ] │
-│ ● eve z6Mkux1…nVhib7Z eve.radicle.example:8776 out-of-sync 99c5497 [ ... ] │
+│ ● alice (you) alice.radicle.example:8776 unannounced 0d6b305 [ ... ] │
+│ ● bob z6Mkt67…v4N1tRk bob.radicle.example:8776 out-of-sync 7c1445c [ ... ] │
+│ ● eve z6Mkux1…nVhib7Z eve.radicle.example:8776 out-of-sync 7c1445c [ ... ] │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -37,9 +37,9 @@ $ rad sync status --sort-by alias
╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ ● Node Address Status Tip Timestamp │
├─────────────────────────────────────────────────────────────────────────────────────────┤
-│ ● alice (you) alice.radicle.example:8776 056b1db [ ... ] │
-│ ● bob z6Mkt67…v4N1tRk bob.radicle.example:8776 synced 056b1db [ ... ] │
-│ ● eve z6Mkux1…nVhib7Z eve.radicle.example:8776 synced 056b1db [ ... ] │
+│ ● alice (you) alice.radicle.example:8776 0d6b305 [ ... ] │
+│ ● bob z6Mkt67…v4N1tRk bob.radicle.example:8776 synced 0d6b305 [ ... ] │
+│ ● eve z6Mkux1…nVhib7Z eve.radicle.example:8776 synced 0d6b305 [ ... ] │
╰─────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -55,8 +55,8 @@ We can also use the `--fetch` option to only fetch objects:
```
$ rad sync --fetch
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkux1…nVhib7Z@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
✓ Fetched repository from 2 seed(s)
```
@@ -64,8 +64,8 @@ Specifying both `--fetch` and `--announce` is equivalent to specifying none:
```
$ rad sync --fetch --announce
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z@[..]..
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkux1…nVhib7Z@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
✓ Fetched repository from 2 seed(s)
✓ Nothing to announce, already in sync with 2 node(s) (see `rad sync status`)
```
@@ -74,7 +74,7 @@ It's also possible to use the `--seed` flag to only sync with a specific node:
```
$ rad sync --fetch --seed z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
✓ Fetched repository from 1 seed(s)
```
@@ -87,7 +87,7 @@ $ rad issue open --title "Test `rad sync --replicas`" --description "Check that
```
$ rad sync --replicas 1
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkux1…nVhib7Z@[..]..
✓ Fetched repository from 1 seed(s)
✓ Synced with 1 node(s)
```
diff --git a/radicle-cli/examples/rad-unseed-many.md b/radicle-cli/examples/rad-unseed-many.md
index edeca073f..1d4ea8297 100644
--- a/radicle-cli/examples/rad-unseed-many.md
+++ b/radicle-cli/examples/rad-unseed-many.md
@@ -8,15 +8,15 @@ $ rad ls
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
-│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public f2de534 Radicle Heartwood Protocol & Stack │
-│ nixpkgs rad:zyFFr2iwoTEfNF4jGNZHuoy7odMh public f2de534 Home for Nix Packages │
+│ heartwood rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 public f2de534 Radicle Heartwood Protocol & Stack │
+│ nixpkgs rad:z3rK5Ldp958XdzwL88vYRvhdQj5WR public f2de534 Home for Nix Packages │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
We could stop seeding them if we didn't want other nodes to fetch them from us:
```
-$ rad unseed rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji rad:zyFFr2iwoTEfNF4jGNZHuoy7odMh
-✓ Seeding policy for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji removed
-✓ Seeding policy for rad:zyFFr2iwoTEfNF4jGNZHuoy7odMh removed
+$ rad unseed rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 rad:z3rK5Ldp958XdzwL88vYRvhdQj5WR
+✓ Seeding policy for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 removed
+✓ Seeding policy for rad:z3rK5Ldp958XdzwL88vYRvhdQj5WR removed
```
diff --git a/radicle-cli/examples/rad-unseed.md b/radicle-cli/examples/rad-unseed.md
index f258e51e2..4dde9d489 100644
--- a/radicle-cli/examples/rad-unseed.md
+++ b/radicle-cli/examples/rad-unseed.md
@@ -5,15 +5,15 @@ $ rad ls
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
-│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public f2de534 Radicle Heartwood Protocol & Stack │
+│ heartwood rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 public f2de534 Radicle Heartwood Protocol & Stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
We could stop seeding it if we didn't want other nodes to fetch it from us:
```
-$ rad unseed rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-✓ Seeding policy for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji removed
+$ rad unseed rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
+✓ Seeding policy for rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 removed
```
Now, if we run `rad ls`, we see it's gone:
@@ -32,7 +32,7 @@ $ rad ls --all
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
-│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji local f2de534 Radicle Heartwood Protocol & Stack │
+│ heartwood rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 local f2de534 Radicle Heartwood Protocol & Stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-watch.md b/radicle-cli/examples/rad-watch.md
index 39e3fc841..804020a5f 100644
--- a/radicle-cli/examples/rad-watch.md
+++ b/radicle-cli/examples/rad-watch.md
@@ -16,5 +16,5 @@ $ git push rad master
```
``` ~bob
-$ rad watch --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --node z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --ref 'refs/heads/master' --target e09c4dc1b54443ceea715ea648afecdcfd1dd7d0 --interval 500
+$ rad watch --repo rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 --node z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --ref 'refs/heads/master' --target e09c4dc1b54443ceea715ea648afecdcfd1dd7d0 --interval 500
```
diff --git a/radicle-cli/examples/workflow/3-issues.md b/radicle-cli/examples/workflow/3-issues.md
index 895246c1d..2c213b327 100644
--- a/radicle-cli/examples/workflow/3-issues.md
+++ b/radicle-cli/examples/workflow/3-issues.md
@@ -7,7 +7,7 @@ Let's say the new car you are designing with your peers has a problem with its f
$ rad issue open --title "flux capacitor underpowered" --description "Flux capacitor power requirements exceed current supply" --no-announce
╭─────────────────────────────────────────────────────────╮
│ Title flux capacitor underpowered │
-│ Issue 9037b7a42323d4b79e6a48b7d05d3bbaae11d69b │
+│ Issue 3b2f7e674bc39d5ff93abf2c68d8233fa4aa8806 │
│ Author bob (you) │
│ Status open │
│ │
@@ -22,7 +22,7 @@ $ rad issue list
╭──────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Labels Assignees Opened │
├──────────────────────────────────────────────────────────────────────────────────────────┤
-│ ● 9037b7a flux capacitor underpowered bob (you) now │
+│ ● 3b2f7e6 flux capacitor underpowered bob (you) now │
╰──────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -31,6 +31,6 @@ found an important detail about the car's power requirements. It will help
whoever works on a fix.
```
-$ rad issue comment 9037b7a42323d4b79e6a48b7d05d3bbaae11d69b --message 'The flux capacitor needs 1.21 Gigawatts' -q --no-announce
-400cb155f512b4880858bb05f935104c34167b28
+$ rad issue comment 3b2f7e674bc39d5ff93abf2c68d8233fa4aa8806 --message 'The flux capacitor needs 1.21 Gigawatts' -q --no-announce
+3a4261f49c44a5832c7f18179d00cffa0deb17f9
```
diff --git a/radicle-cli/examples/workflow/4-patching-contributor.md b/radicle-cli/examples/workflow/4-patching-contributor.md
index 181072c93..a4a6818c5 100644
--- a/radicle-cli/examples/workflow/4-patching-contributor.md
+++ b/radicle-cli/examples/workflow/4-patching-contributor.md
@@ -26,8 +26,8 @@ Once the code is ready, we open a patch with our changes.
``` (stderr)
$ git push rad -o no-sync -o patch.message="Define power requirements" -o patch.message="See details." HEAD:refs/patches
-✓ Patch e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46 opened
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+✓ Patch 3aa3bbfbc4162e34ab6787b3508e7ec84166d182 opened
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
* [new reference] HEAD -> refs/patches
```
@@ -38,12 +38,12 @@ $ rad patch
╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │
├─────────────────────────────────────────────────────────────────────────────────────────┤
-│ ● e4934b6 Define power requirements bob (you) - 3e674d1 +0 -0 now │
+│ ● 3aa3bbf Define power requirements bob (you) - 3e674d1 +0 -0 now │
╰─────────────────────────────────────────────────────────────────────────────────────────╯
-$ rad patch show e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46
+$ rad patch show 3aa3bbfbc4162e34ab6787b3508e7ec84166d182
╭────────────────────────────────────────────────────╮
│ Title Define power requirements │
-│ Patch e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46 │
+│ Patch 3aa3bbfbc4162e34ab6787b3508e7ec84166d182 │
│ Author bob (you) │
│ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │
│ Branches flux-capacitor-power │
@@ -61,8 +61,8 @@ $ rad patch show e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46
We can also confirm that the patch branch is in storage:
```
-$ git ls-remote rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk refs/heads/patches/*
-3e674d1a1df90807e934f9ae5da2591dd6848a33 refs/heads/patches/e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46
+$ git ls-remote rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk refs/heads/patches/*
+3e674d1a1df90807e934f9ae5da2591dd6848a33 refs/heads/patches/3aa3bbfbc4162e34ab6787b3508e7ec84166d182
```
Wait, let's add a README too! Just for fun.
@@ -77,19 +77,19 @@ $ git commit --message "Add README, just for the fun"
```
``` (stderr) RAD_SOCKET=/dev/null
$ git push -o patch.message="Add README, just for the fun"
-✓ Patch e4934b6 updated to revision 773b9aab58b11e9fa83d0ed0baca2bea6ff889c9
-To compare against your previous revision e4934b6, run:
+✓ Patch 3aa3bbf updated to revision 8ea87be8cb7d590f381338348532200b230368af
+To compare against your previous revision 3aa3bbf, run:
git range-diff f2de534[..] 3e674d1[..] 27857ec[..]
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
- 3e674d1..27857ec flux-capacitor-power -> patches/e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
+ 3e674d1..27857ec flux-capacitor-power -> patches/3aa3bbfbc4162e34ab6787b3508e7ec84166d182
```
And let's leave a quick comment for our team:
```
-$ rad patch comment e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46 --message 'I cannot wait to get back to the 90s!' -q
-8c66f87afadc7c7c857f8bb92973c25f64e75776
+$ rad patch comment 3aa3bbfbc4162e34ab6787b3508e7ec84166d182 --message 'I cannot wait to get back to the 90s!' -q
+528abde17e16bef2aa12157c745a9a74e4005051
✓ Synced with 1 node(s)
```
diff --git a/radicle-cli/examples/workflow/5-patching-maintainer.md b/radicle-cli/examples/workflow/5-patching-maintainer.md
index 85078ed30..8cfc0e060 100644
--- a/radicle-cli/examples/workflow/5-patching-maintainer.md
+++ b/radicle-cli/examples/workflow/5-patching-maintainer.md
@@ -8,7 +8,7 @@ of this.
```
$ rad remote add z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob --sync --fetch
✓ Follow policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6Mkt67…v4N1tRk@[..]..
✓ Remote bob added
✓ Remote-tracking branch bob/master created for z6Mkt67…v4N1tRk
```
@@ -20,16 +20,16 @@ $ rad inbox --sort-by id
╭────────────────────────────────────────────────────────────────────────────╮
│ heartwood │
├────────────────────────────────────────────────────────────────────────────┤
-│ 001 ● 9037b7a flux capacitor underpowered issue open bob now │
-│ 002 ● e4934b6 Define power requirements patch open bob now │
+│ 001 ● 3b2f7e6 flux capacitor underpowered issue open bob now │
+│ 002 ● 3aa3bbf Define power requirements patch open bob now │
╰────────────────────────────────────────────────────────────────────────────╯
$ git branch -r
- bob/patches/e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46
+ bob/patches/3aa3bbfbc4162e34ab6787b3508e7ec84166d182
rad/master
-$ rad patch show e4934b6
+$ rad patch show 3aa3bbf
╭─────────────────────────────────────────────────────────────────────╮
│ Title Define power requirements │
-│ Patch e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46 │
+│ Patch 3aa3bbfbc4162e34ab6787b3508e7ec84166d182 │
│ Author bob z6Mkt67…v4N1tRk │
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
│ Commits ahead 2, behind 0 │
@@ -41,7 +41,7 @@ $ rad patch show e4934b6
│ 3e674d1 Define power requirements │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by bob z6Mkt67…v4N1tRk (3e674d1) now │
-│ ↑ updated to 773b9aab58b11e9fa83d0ed0baca2bea6ff889c9 (27857ec) now │
+│ ↑ updated to 8ea87be8cb7d590f381338348532200b230368af (27857ec) now │
╰─────────────────────────────────────────────────────────────────────╯
```
@@ -51,35 +51,35 @@ way will tell others about the corrections we needed before merging the
changes.
```
-$ rad patch checkout e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46
-✓ Switched to branch patch/e4934b6 at revision 773b9aa
-✓ Branch patch/e4934b6 setup to track rad/patches/e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46
+$ rad patch checkout 3aa3bbfbc4162e34ab6787b3508e7ec84166d182
+✓ Switched to branch patch/3aa3bbf at revision 8ea87be
+✓ Branch patch/3aa3bbf setup to track rad/patches/3aa3bbfbc4162e34ab6787b3508e7ec84166d182
$ git mv REQUIREMENTS REQUIREMENTS.md
$ git commit -m "Use markdown for requirements"
-[patch/e4934b6 f567f69] Use markdown for requirements
+[patch/3aa3bbf f567f69] Use markdown for requirements
1 file changed, 0 insertions(+), 0 deletions(-)
rename REQUIREMENTS => REQUIREMENTS.md (100%)
```
``` (stderr)
$ git push rad -o no-sync -o patch.message="Use markdown for requirements"
-✓ Patch e4934b6 updated to revision 9d62420e779e5cfe1dc02c51eddec9a0907aa844
-To compare against your previous revision 773b9aa, run:
+✓ Patch 3aa3bbf updated to revision 83812f465f23c6f1262e4d526f52e5a5f02330a0
+To compare against your previous revision 8ea87be, run:
git range-diff f2de534[..] 27857ec[..] f567f69[..]
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- * [new branch] patch/e4934b6 -> patches/e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ * [new branch] patch/3aa3bbf -> patches/3aa3bbfbc4162e34ab6787b3508e7ec84166d182
```
Great, all fixed up, lets accept and merge the code.
```
-$ rad patch review e4934b6 --revision 9d62420 --accept
-✓ Patch e4934b6 accepted
+$ rad patch review 3aa3bbf --revision 83812f4 --accept
+✓ Patch 3aa3bbf accepted
✓ Synced with 1 node(s)
$ git checkout master
Your branch is up to date with 'rad/master'.
-$ git merge patch/e4934b6
+$ git merge patch/3aa3bbf
Updating f2de534..f567f69
Fast-forward
README.md | 0
@@ -90,20 +90,20 @@ Fast-forward
```
``` (stderr)
$ git push rad master
-✓ Patch e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46 merged at revision 9d62420
-✓ Canonical head updated to f567f695d25b4e8fb63b5f5ad2a584529826e908
+✓ Patch 3aa3bbfbc4162e34ab6787b3508e7ec84166d182 merged at revision 83812f4
+✓ Canonical head for refs/heads/master updated to f567f695d25b4e8fb63b5f5ad2a584529826e908
✓ Synced with 1 node(s)
-To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+To rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..f567f69 master -> master
```
The patch is now merged and closed :).
```
-$ rad patch show e4934b6
+$ rad patch show 3aa3bbf
╭─────────────────────────────────────────────────────────────────────╮
│ Title Define power requirements │
-│ Patch e4934b6d9dbe01ce3c7fbb5b77a80d5f1dacdc46 │
+│ Patch 3aa3bbfbc4162e34ab6787b3508e7ec84166d182 │
│ Author bob z6Mkt67…v4N1tRk │
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
│ Commits ahead 0, behind 1 │
@@ -115,10 +115,10 @@ $ rad patch show e4934b6
│ 3e674d1 Define power requirements │
├─────────────────────────────────────────────────────────────────────┤
│ ● opened by bob z6Mkt67…v4N1tRk (3e674d1) now │
-│ ↑ updated to 773b9aab58b11e9fa83d0ed0baca2bea6ff889c9 (27857ec) now │
-│ * revised by alice (you) in 9d62420 (f567f69) now │
+│ ↑ updated to 8ea87be8cb7d590f381338348532200b230368af (27857ec) now │
+│ * revised by alice (you) in 83812f4 (f567f69) now │
│ └─ ✓ accepted by alice (you) now │
-│ └─ ✓ merged by alice (you) at revision 9d62420 (f567f69) now │
+│ └─ ✓ merged by alice (you) at revision 83812f4 (f567f69) now │
╰─────────────────────────────────────────────────────────────────────╯
```
@@ -132,7 +132,7 @@ Finally, we will close the issue that was opened for this
patch, marking it as solved:
```
-$ rad issue state 9037b7a --solved
-✓ Issue 9037b7a is now solved
+$ rad issue state 3b2f7e6 --solved
+✓ Issue 3b2f7e6 is now solved
✓ Synced with 1 node(s)
```
diff --git a/radicle-cli/examples/workflow/6-pulling-contributor.md b/radicle-cli/examples/workflow/6-pulling-contributor.md
index 5d3f2083c..928a53bb3 100644
--- a/radicle-cli/examples/workflow/6-pulling-contributor.md
+++ b/radicle-cli/examples/workflow/6-pulling-contributor.md
@@ -10,7 +10,7 @@ f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
Then, we call `rad sync --fetch` to fetch from the maintainer:
```
$ rad sync --fetch
-✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi@[..]..
+✓ Fetching rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2 from z6MknSL…StBU8Vi@[..]..
✓ Fetched repository from 1 seed(s)
```
@@ -21,9 +21,9 @@ Your branch is up to date with 'rad/master'.
```
``` (stderr) RAD_SOCKET=/dev/null
$ git pull --all --ff
-From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+From rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2
f2de534..f567f69 master -> rad/master
-From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+From rad://z3W5xAVWJ9Gc4LbN16mE3tjWX92t2/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..f567f69 master -> alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
```
diff --git a/radicle-cli/src/commands.rs b/radicle-cli/src/commands.rs
index 60d5eb14e..82a5fcba7 100644
--- a/radicle-cli/src/commands.rs
+++ b/radicle-cli/src/commands.rs
@@ -12,6 +12,8 @@ pub mod rad_clone;
pub mod rad_cob;
#[path = "commands/config.rs"]
pub mod rad_config;
+#[path = "commands/cref.rs"]
+pub mod rad_cref;
#[path = "commands/debug.rs"]
pub mod rad_debug;
#[path = "commands/diff.rs"]
diff --git a/radicle-cli/src/commands/cref.rs b/radicle-cli/src/commands/cref.rs
new file mode 100644
index 000000000..6466520fd
--- /dev/null
+++ b/radicle-cli/src/commands/cref.rs
@@ -0,0 +1,411 @@
+use std::ffi::OsString;
+use std::path::Path;
+
+use anyhow::{anyhow, Context};
+
+use nonempty::NonEmpty;
+use radicle::cob;
+use radicle::cob::identity::Identity;
+use radicle::git::canonical::rules;
+use radicle::git::canonical::rules::Rule;
+use radicle::git::canonical::RawRule;
+use radicle::identity::Did;
+use radicle::identity::IdentityMut;
+use radicle::identity::RawDoc;
+use radicle::identity::RepoId;
+use radicle::prelude::Profile;
+use radicle::storage::ReadStorage;
+use radicle::storage::WriteRepository;
+use radicle_term::Element as _;
+
+use crate::id;
+use crate::terminal as term;
+use crate::terminal::args::{Args, Error, Help};
+use crate::terminal::json;
+
+pub const HELP: Help = Help {
+ name: "cref",
+ description: "Manage canonical reference rules",
+ version: env!("RADICLE_VERSION"),
+ usage: r#"
+Usage
+
+ rad cref list [<option>...]
+ rad cref add <refspec> [--allow <did>..] [--threshold <num>] [<option>...]
+ rad cref edit <refspec> [--allow <did>..] [--threshold <num>] [<option>...]
+ rad cref remove <refspec> [<option>...]
+
+ The *rad cref* command is used to manage the rules for setting
+ canonical references in the Radicle repository.
+
+ The *list* command lists all the rules associated with the repository.
+
+ The *add* command will add the rule for the given *refspec*.
+
+ The *edit* command will amend the rule for the given *refspec*.
+
+ The *remove* command will remove the rule for the given *refspec*.
+
+Add/Edit options
+
+ --allow <did> DID to be used for the canonical reference rule.
+ Can be used multiple times to specify more delegates.
+ If not specified the default 'delegates' token will be used.
+ --threshold <num> The threshold number of votes required to make a reference canonical (default: 1)
+
+Options
+
+ --repo <rid> Repository (defaults to the current repository)
+ --quiet, -q Don't print anything
+ --help Print help
+"#,
+};
+
+pub enum Operation {
+ Add {
+ title: Option<String>,
+ description: Option<String>,
+ pattern: rules::Pattern,
+ allow: rules::Allowed,
+ threshold: usize,
+ },
+ Edit {
+ title: Option<String>,
+ description: Option<String>,
+ pattern: rules::Pattern,
+ allow: rules::Allowed,
+ threshold: usize,
+ },
+ List,
+ Remove {
+ title: Option<String>,
+ description: Option<String>,
+ pattern: rules::Pattern,
+ },
+}
+
+#[derive(Debug, Default)]
+pub enum OperationName {
+ Add,
+ Edit,
+ #[default]
+ List,
+ Remove,
+}
+
+pub struct Options {
+ pub op: Operation,
+ pub rid: Option<RepoId>,
+ pub quiet: bool,
+}
+
+impl Args for Options {
+ fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
+ use lexopt::prelude::*;
+
+ let mut parser = lexopt::Parser::from_args(args);
+ let mut op: Option<OperationName> = None;
+ let mut pattern: Option<rules::Pattern> = None;
+ let mut allow: Vec<Did> = Vec::new();
+ let mut threshold: Option<usize> = None;
+ let mut rid: Option<RepoId> = None;
+ let mut title: Option<String> = None;
+ let mut description: Option<String> = None;
+ let mut quiet = false;
+
+ while let Some(arg) = parser.next()? {
+ match arg {
+ Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
+ "a" | "add" => op = Some(OperationName::Add),
+ "e" | "edit" => op = Some(OperationName::Edit),
+ "r" | "remove" => op = Some(OperationName::Remove),
+ "l" | "list" => op = Some(OperationName::List),
+
+ unknown => anyhow::bail!("unknown operation '{}'", unknown),
+ },
+
+ Value(val)
+ if matches!(
+ op,
+ Some(OperationName::Add | OperationName::Edit | OperationName::Remove)
+ ) =>
+ {
+ pattern = Some(rules::Pattern::try_from(
+ term::args::qualified_pattern_string(val)?,
+ )?);
+ }
+
+ Long("title")
+ if matches!(
+ op,
+ Some(OperationName::Add | OperationName::Edit | OperationName::Remove)
+ ) =>
+ {
+ title = Some(parser.value()?.to_string_lossy().into());
+ }
+ Long("description")
+ if matches!(
+ op,
+ Some(OperationName::Add | OperationName::Edit | OperationName::Remove)
+ ) =>
+ {
+ description = Some(parser.value()?.to_string_lossy().into());
+ }
+ Long("allow") if matches!(op, Some(OperationName::Add | OperationName::Edit)) => {
+ let did = term::args::did(&parser.value()?)?;
+ allow.push(did);
+ }
+ Long("threshold")
+ if matches!(op, Some(OperationName::Add | OperationName::Edit)) =>
+ {
+ threshold = Some(parser.value()?.to_string_lossy().parse()?);
+ }
+ Long("repo") => {
+ let val = parser.value()?;
+ let val = term::args::rid(&val)?;
+
+ rid = Some(val);
+ }
+ Long("quiet") | Short('q') => {
+ quiet = true;
+ }
+ Long("help") | Short('h') => {
+ return Err(Error::Help.into());
+ }
+ _ => {
+ return Err(anyhow!(arg.unexpected()));
+ }
+ }
+ }
+
+ let op = match op.unwrap_or_default() {
+ OperationName::Add => Operation::Add {
+ title,
+ description,
+ pattern: pattern.ok_or_else(|| anyhow!("a refspec must be provided"))?,
+ allow: NonEmpty::from_vec(allow)
+ .map(rules::Allowed::Set)
+ .unwrap_or(rules::Allowed::Delegates),
+ threshold: threshold.unwrap_or(1),
+ },
+ OperationName::Edit => Operation::Edit {
+ title,
+ description,
+ pattern: pattern.ok_or_else(|| anyhow!("a refspec must be provided"))?,
+ allow: NonEmpty::from_vec(allow)
+ .map(rules::Allowed::Set)
+ .unwrap_or(rules::Allowed::Delegates),
+ threshold: threshold.unwrap_or(1),
+ },
+ OperationName::Remove => Operation::Remove {
+ title,
+ description,
+ pattern: pattern.ok_or_else(|| anyhow!("a refspec must be provided"))?,
+ },
+ OperationName::List => Operation::List,
+ };
+
+ Ok((Options { op, rid, quiet }, vec![]))
+ }
+}
+
+pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
+ let profile = ctx.profile()?;
+ let storage = &profile.storage;
+ let rid = options
+ .rid
+ .map(Ok)
+ .unwrap_or_else(|| radicle::rad::cwd().map(|(_, rid)| rid))?;
+ let repo = storage
+ .repository(rid)
+ .context(anyhow!("repository `{rid}` not found in local storage"))?;
+ let mut identity = Identity::load_mut(&repo)?;
+ let current = identity.current().clone();
+
+ match options.op {
+ Operation::Add {
+ title,
+ description,
+ pattern,
+ allow,
+ threshold,
+ } => {
+ let rule = Rule::new(allow, threshold);
+ let proposal = current.doc.clone().edit();
+ add(
+ proposal,
+ pattern,
+ rule,
+ &profile,
+ &repo,
+ &mut identity,
+ title,
+ description,
+ options.quiet,
+ )?;
+ }
+ Operation::Edit {
+ title,
+ description,
+ pattern,
+ allow,
+ threshold,
+ } => {
+ let rule = Rule::new(allow, threshold);
+ let proposal = current.doc.clone().edit();
+ edit(
+ proposal,
+ pattern,
+ rule,
+ &profile,
+ &repo,
+ &mut identity,
+ title,
+ description,
+ options.quiet,
+ )?;
+ }
+ Operation::Remove {
+ title,
+ description,
+ pattern,
+ } => {
+ let proposal = current.doc.clone().edit();
+ remove(
+ proposal,
+ pattern,
+ profile,
+ &repo,
+ &mut identity,
+ title,
+ description,
+ options.quiet,
+ )?;
+ }
+ Operation::List => {
+ print_rules(current.rules())?;
+ }
+ }
+ Ok(())
+}
+
+fn remove<R>(
+ mut proposal: RawDoc,
+ pattern: rules::Pattern,
+ profile: Profile,
+ repo: &R,
+ identity: &mut IdentityMut<R>,
+ title: Option<String>,
+ description: Option<String>,
+ quiet: bool,
+) -> Result<(), anyhow::Error>
+where
+ R: cob::Store + WriteRepository,
+{
+ proposal.canonical_refs.rules.remove(&pattern);
+ let revision = id::propose_changes(&profile, repo, proposal, identity, title, description)?;
+ match revision {
+ Some(revision) => {
+ if !quiet {
+ term::success!("Rule for {} has been removed", pattern);
+ term::success!(
+ "Identity revision {} created",
+ term::format::tertiary(revision.id)
+ );
+ }
+ }
+ None => {
+ if !quiet {
+ term::print(term::format::italic(
+ "Nothing to do. The rules are up to date. See `rad cref list`.",
+ ));
+ }
+ }
+ }
+ Ok(())
+}
+
+fn add<R>(
+ mut proposal: RawDoc,
+ pattern: rules::Pattern,
+ rule: RawRule,
+ profile: &Profile,
+ repo: &R,
+ identity: &mut IdentityMut<R>,
+ title: Option<String>,
+ description: Option<String>,
+ quiet: bool,
+) -> Result<(), anyhow::Error>
+where
+ R: cob::Store + WriteRepository,
+{
+ proposal.canonical_refs.rules.insert(pattern.clone(), rule);
+ let revision = id::propose_changes(profile, repo, proposal, identity, title, description)?;
+ match revision {
+ Some(revision) => {
+ if !quiet {
+ term::success!("Rule for {pattern} has been added");
+ term::success!(
+ "Identity revision {} created",
+ term::format::tertiary(revision.id)
+ );
+ }
+ }
+ None => {
+ if !quiet {
+ term::print(term::format::italic(
+ "Nothing to do. The rules are up to date. See `rad cref list`.",
+ ));
+ }
+ }
+ }
+ Ok(())
+}
+
+fn edit<R>(
+ mut proposal: RawDoc,
+ pattern: rules::Pattern,
+ rule: RawRule,
+ profile: &Profile,
+ repo: &R,
+ identity: &mut IdentityMut<R>,
+ title: Option<String>,
+ description: Option<String>,
+ quiet: bool,
+) -> Result<(), anyhow::Error>
+where
+ R: cob::Store + WriteRepository,
+{
+ let changed = proposal.canonical_refs.rules.insert(pattern.clone(), rule);
+ if changed.is_none() {
+ term::print(term::format::italic(
+ "Nothing to do. The rules are up to date. See `rad cref list`.",
+ ));
+ return Ok(());
+ }
+ let revision = id::propose_changes(profile, repo, proposal, identity, title, description)?;
+ match revision {
+ Some(revision) => {
+ if !quiet {
+ term::success!("Rule for {pattern} has been modified");
+ term::success!(
+ "Identity revision {} created",
+ term::format::tertiary(revision.id)
+ );
+ }
+ }
+ None => {
+ if !quiet {
+ term::print(term::format::italic(
+ "Nothing to do. The rules are up to date. See `rad cref list`.",
+ ));
+ }
+ }
+ }
+ Ok(())
+}
+
+fn print_rules(rules: &rules::Rules) -> anyhow::Result<()> {
+ json::to_pretty(&rules, Path::new("radicle.json"))?.print();
+ Ok(())
+}
diff --git a/radicle-cli/src/commands/help.rs b/radicle-cli/src/commands/help.rs
index 8b0f4d558..534694756 100644
--- a/radicle-cli/src/commands/help.rs
+++ b/radicle-cli/src/commands/help.rs
@@ -18,6 +18,7 @@ const COMMANDS: &[Help] = &[
rad_checkout::HELP,
rad_clone::HELP,
rad_config::HELP,
+ rad_cref::HELP,
rad_fork::HELP,
rad_help::HELP,
rad_id::HELP,
diff --git a/radicle-cli/src/commands/id.rs b/radicle-cli/src/commands/id.rs
index f49eb74a6..8cf36d525 100644
--- a/radicle-cli/src/commands/id.rs
+++ b/radicle-cli/src/commands/id.rs
@@ -4,21 +4,20 @@ use std::{ffi::OsString, io};
use anyhow::{anyhow, Context};
-use radicle::cob::identity::{self, IdentityMut, Revision, RevisionId};
-use radicle::identity::{doc, Doc, Identity, PayloadError, RawDoc, Visibility};
-use radicle::prelude::{Did, RepoId, Signer};
-use radicle::storage::refs;
+use radicle::cob::identity;
+use radicle::cob::identity::Revision;
+use radicle::identity::{doc, Doc, Identity, RawDoc, Visibility};
+use radicle::prelude::{Did, RepoId};
+use radicle::storage::{refs, HasRepoId};
use radicle::storage::{ReadRepository, ReadStorage as _, WriteRepository};
-use radicle::{cob, Profile};
-use radicle_surf::diff::Diff;
+use radicle::Profile;
use radicle_term::Element;
use serde_json as json;
-use crate::git::unified_diff::Encode as _;
use crate::git::Rev;
+use crate::id;
use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
-use crate::terminal::patch::Message;
use crate::terminal::Interactive;
pub const HELP: Help = Help {
@@ -31,12 +30,13 @@ Usage
rad id list [<option>...]
rad id update [--title <string>] [--description <string>]
[--delegate <did>] [--rescind <did>]
- [--threshold <num>] [--visibility <private | public>]
+ [--visibility <private | public>]
[--allow <did>] [--disallow <did>]
[--no-confirm] [--payload <id> <key> <val>...] [--edit] [<option>...]
rad id edit <revision-id> [--title <string>] [--description <string>] [<option>...]
rad id show <revision-id> [<option>...]
rad id <accept | reject | redact> <revision-id> [<option>...]
+ rad id migrate [--no-confirm] [--title <string>] [--description <string>]
The *rad id* command is used to manage and propose changes to the
identity of a Radicle repository.
@@ -53,12 +53,15 @@ Options
#[derive(Clone, Debug, Default)]
pub enum Operation {
+ Migrate {
+ title: Option<String>,
+ description: Option<String>,
+ },
Update {
title: Option<String>,
description: Option<String>,
delegate: Vec<Did>,
rescind: Vec<Did>,
- threshold: Option<usize>,
visibility: Option<EditVisibility>,
allow: BTreeSet<Did>,
disallow: BTreeSet<Did>,
@@ -115,6 +118,7 @@ pub enum OperationName {
Reject,
Edit,
Update,
+ Migrate,
Show,
Redact,
#[default]
@@ -143,7 +147,6 @@ impl Args for Options {
let mut visibility: Option<EditVisibility> = None;
let mut allow: BTreeSet<Did> = BTreeSet::new();
let mut disallow: BTreeSet<Did> = BTreeSet::new();
- let mut threshold: Option<usize> = None;
let mut interactive = Interactive::new(io::stdout());
let mut payload = Vec::new();
let mut edit = false;
@@ -176,6 +179,7 @@ impl Args for Options {
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
"e" | "edit" => op = Some(OperationName::Edit),
"u" | "update" => op = Some(OperationName::Update),
+ "m" | "migrate" => op = Some(OperationName::Migrate),
"l" | "list" => op = Some(OperationName::List),
"s" | "show" => op = Some(OperationName::Show),
"a" | "accept" => op = Some(OperationName::Accept),
@@ -214,9 +218,6 @@ impl Args for Options {
visibility = Some(value);
}
- Long("threshold") => {
- threshold = Some(parser.value()?.to_string_lossy().parse()?);
- }
Long("payload") => {
let mut values = parser.values()?;
let id = values
@@ -275,13 +276,13 @@ impl Args for Options {
description,
delegate,
rescind,
- threshold,
visibility,
allow,
disallow,
payload,
edit,
},
+ OperationName::Migrate => Operation::Migrate { title, description },
};
Ok((
Options {
@@ -371,7 +372,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
if !revision.is_active() {
anyhow::bail!("revision can no longer be edited");
}
- let Some((title, description)) = edit_title_description(title, description)? else {
+ let Some((title, description)) = id::edit_title_description(title, description)? else {
anyhow::bail!("revision title or description missing");
};
identity.edit(revision.id, title, description, &signer)?;
@@ -385,7 +386,6 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
description,
delegate: delegates,
rescind,
- threshold,
visibility,
allow,
disallow,
@@ -394,7 +394,6 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
} => {
let proposal = {
let mut proposal = current.doc.clone().edit();
- proposal.threshold = threshold.unwrap_or(proposal.threshold);
if !allow.is_disjoint(&disallow) {
let overlap = allow
@@ -438,17 +437,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
.chain(delegates)
.filter(|d| !rescind.contains(d))
.collect::<Vec<_>>();
- if let Some(errs) = verify_delegates(&proposal, &repo)? {
- term::error(format!("failed to verify delegates for {rid}"));
- term::error(format!(
- "the threshold of {} delegates cannot be met..",
- proposal.threshold
- ));
- for e in errs {
- e.print();
- }
- anyhow::bail!("fatal: refusing to update identity document");
- }
+
+ verify_project_delegates(&proposal, ¤t, &repo)?;
for (id, key, val) in payload {
if let Some(ref mut payload) = proposal.payload.get_mut(&id) {
@@ -487,34 +477,27 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
proposal
};
- // Verify that the project payload can still be parsed into the `Project` type.
- if let Err(PayloadError::Json(e)) = proposal.project() {
- anyhow::bail!("failed to verify `xyz.radicle.project`, {e}");
- }
- let proposal = proposal.verified()?;
- if proposal == current.doc {
- if !options.quiet {
- term::print(term::format::italic(
- "Nothing to do. The document is up to date. See `rad inspect --identity`.",
- ));
+ let revision =
+ id::propose_changes(&profile, &repo, proposal, &mut identity, title, description)?;
+ match revision {
+ Some(revision) => {
+ if options.quiet {
+ term::print(revision.id);
+ } else {
+ term::success!(
+ "Identity revision {} created",
+ term::format::tertiary(revision.id)
+ );
+ id::print(&revision, &identity, &repo, &profile)?;
+ }
+ }
+ None => {
+ if !options.quiet {
+ term::print(term::format::italic(
+ "Nothing to do. The document is up to date. See `rad inspect --identity`.",
+ ));
+ }
}
- return Ok(());
- }
- let signer = term::signer(&profile)?;
- let revision = update(title, description, proposal, &mut identity, &signer)?;
-
- if revision.is_accepted() && revision.parent == Some(current.id) {
- // Update the canonical head to point to the latest accepted revision.
- repo.set_identity_head_to(revision.id)?;
- }
- if options.quiet {
- term::print(revision.id);
- } else {
- term::success!(
- "Identity revision {} created",
- term::format::tertiary(revision.id)
- );
- print(&revision, ¤t, &repo, &profile)?;
}
}
Operation::ListRevisions => {
@@ -576,7 +559,42 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
.revision(&previous)
.ok_or(anyhow!("revision `{previous}` not found"))?;
- print(revision, previous, &repo, &profile)?;
+ id::print(revision, previous, &repo, &profile)?;
+ }
+ Operation::Migrate { title, description } => {
+ let proposal = repo.identity_doc()?.doc;
+ if options.interactive.confirm(format!(
+ "Perform migration?\n{}",
+ term::format::tertiary(serde_json::to_string_pretty(&proposal)?)
+ )) {
+ let revision = id::propose_changes(
+ &profile,
+ &repo,
+ proposal.edit(),
+ &mut identity,
+ title,
+ description,
+ )?;
+ match revision {
+ Some(revision) => {
+ if options.quiet {
+ term::print(revision.id);
+ } else {
+ term::success!(
+ "Identity revision {} created",
+ term::format::tertiary(revision.id)
+ );
+ id::print(&revision, &identity, &repo, &profile)?;
+ }
+ }
+ None => {
+ if !options.quiet {
+ let msg = "Nothing to do. The document is up to date. See `rad inspect --identity`.";
+ term::print(term::format::italic(msg));
+ }
+ }
+ }
+ }
}
}
Ok(())
@@ -682,107 +700,7 @@ fn print_meta(revision: &Revision, previous: &Doc, profile: &Profile) -> anyhow:
Ok(())
}
-fn print(
- revision: &identity::Revision,
- previous: &identity::Revision,
- repo: &radicle::storage::git::Repository,
- profile: &Profile,
-) -> anyhow::Result<()> {
- print_meta(revision, previous, profile)?;
- println!();
- print_diff(revision.parent.as_ref(), &revision.id, repo)?;
-
- Ok(())
-}
-
-fn edit_title_description(
- title: Option<String>,
- description: Option<String>,
-) -> anyhow::Result<Option<(String, String)>> {
- const HELP: &str = r#"<!--
-Please enter a patch message for your changes. An empty
-message aborts the patch proposal.
-
-The first line is the patch title. The patch description
-follows, and must be separated with a blank line, just
-like a commit message. Markdown is supported in the title
-and description.
--->"#;
-
- let result = if let (Some(t), d) = (title.as_ref(), description.as_deref()) {
- Some((t.to_owned(), d.unwrap_or_default().to_owned()))
- } else {
- let result = Message::edit_title_description(title, description, HELP)?;
- if let Some((title, description)) = result {
- Some((title, description))
- } else {
- None
- }
- };
- Ok(result)
-}
-
-fn update<R: WriteRepository + cob::Store, G: Signer>(
- title: Option<String>,
- description: Option<String>,
- doc: Doc,
- current: &mut IdentityMut<R>,
- signer: &G,
-) -> anyhow::Result<Revision> {
- if let Some((title, description)) = edit_title_description(title, description)? {
- let id = current.update(title, description, &doc, signer)?;
- let revision = current
- .revision(&id)
- .ok_or(anyhow!("update failed: revision {id} is missing"))?;
-
- Ok(revision.clone())
- } else {
- Err(anyhow!("you must provide a revision title and description"))
- }
-}
-
-fn print_diff(
- previous: Option<&RevisionId>,
- current: &RevisionId,
- repo: &radicle::storage::git::Repository,
-) -> anyhow::Result<()> {
- let previous = if let Some(previous) = previous {
- let previous = Doc::load_at(*previous, repo)?;
- let previous = serde_json::to_string_pretty(&previous.doc)?;
-
- Some(previous)
- } else {
- None
- };
- let current = Doc::load_at(*current, repo)?;
- let current = serde_json::to_string_pretty(¤t.doc)?;
-
- let tmp = tempfile::tempdir()?;
- let repo = radicle::git::raw::Repository::init_bare(tmp.path())?;
-
- let previous = if let Some(previous) = previous {
- let tree = radicle::git::write_tree(&doc::PATH, previous.as_bytes(), &repo)?;
- Some(tree)
- } else {
- None
- };
- let current = radicle::git::write_tree(&doc::PATH, current.as_bytes(), &repo)?;
- let mut opts = radicle::git::raw::DiffOptions::new();
- opts.context_lines(u32::MAX);
-
- let diff = repo.diff_tree_to_tree(previous.as_ref(), Some(¤t), Some(&mut opts))?;
- let diff = Diff::try_from(diff)?;
-
- if let Some(modified) = diff.modified().next() {
- let diff = modified.diff.to_unified_string()?;
- print!("{diff}");
- } else {
- term::print(term::format::italic("No changes."));
- }
- Ok(())
-}
-
-#[derive(Clone)]
+#[derive(Clone, Debug)]
enum VerificationError {
MissingDefaultBranch {
branch: radicle::git::RefString,
@@ -811,19 +729,47 @@ impl VerificationError {
}
}
-fn verify_delegates<S>(
- proposal: &RawDoc,
+// N.b. if we are modifying a project repository, we want to ensure that we have
+// rule for the default branch and we can verify the number of delegates for it
+fn verify_project_delegates<S>(proposal: &RawDoc, current: &Doc, repo: &S) -> anyhow::Result<()>
+where
+ S: ReadRepository,
+{
+ let rule = current.default_branch_rule()?;
+ let threshold = rule.map(|rule| *rule.threshold()).ok_or(anyhow::anyhow!(
+ "failed to find canonical ref rule for default branch"
+ ))?;
+
+ if let Some(errs) = verify_delegates(&proposal.delegates, threshold.into(), repo)? {
+ term::error(format!("failed to verify delegates for {}", repo.rid()));
+ term::error(format!(
+ "the threshold of {} delegates cannot be met..",
+ threshold
+ ));
+ for e in errs {
+ e.print();
+ }
+ anyhow::bail!("fatal: refusing to update identity document");
+ }
+ Ok(())
+}
+
+fn verify_delegates<'a, I, S>(
+ delegates: I,
+ threshold: usize,
repo: &S,
) -> anyhow::Result<Option<Vec<VerificationError>>>
where
+ I: IntoIterator<Item = &'a Did>,
+ I::IntoIter: ExactSizeIterator,
S: ReadRepository,
{
- let dids = &proposal.delegates;
- let threshold = proposal.threshold;
+ let delegates = delegates.into_iter();
+ let n = delegates.len();
let (canonical, _) = repo.canonical_head()?;
- let mut missing = Vec::with_capacity(dids.len());
+ let mut missing = Vec::with_capacity(n);
- for did in dids {
+ for did in delegates {
match refs::SignedRefsAt::load((*did).into(), repo)? {
None => {
missing.push(VerificationError::MissingDelegate { did: *did });
@@ -839,5 +785,5 @@ where
}
}
- Ok((dids.len() - missing.len() < threshold).then_some(missing))
+ Ok((n - missing.len() < threshold).then_some(missing))
}
diff --git a/radicle-cli/src/id.rs b/radicle-cli/src/id.rs
new file mode 100644
index 000000000..5b67cf1a8
--- /dev/null
+++ b/radicle-cli/src/id.rs
@@ -0,0 +1,241 @@
+use anyhow::anyhow;
+
+use radicle::cob;
+use radicle::cob::identity::{IdentityMut, Revision, RevisionId};
+use radicle::crypto::Signer;
+use radicle::identity::{doc, Doc, RawDoc};
+use radicle::storage::{ReadRepository, WriteRepository};
+use radicle::Profile;
+use radicle_surf::diff::Diff;
+use radicle_term::Element as _;
+
+use crate::git::unified_diff::Encode as _;
+use crate::terminal as term;
+use crate::terminal::patch::Message;
+
+pub fn propose_changes<R>(
+ profile: &Profile,
+ repo: &R,
+ proposal: RawDoc,
+ current: &mut IdentityMut<R>,
+ title: Option<String>,
+ description: Option<String>,
+) -> anyhow::Result<Option<Revision>>
+where
+ R: ReadRepository + WriteRepository + cob::Store,
+{
+ // Verify that the project payload can still be parsed into the `Project` type.
+ if let Err(doc::PayloadError::Json(e)) = proposal.project() {
+ anyhow::bail!("failed to verify `xyz.radicle.project`: {e}");
+ }
+
+ let proposal = proposal.verified()?;
+ if proposal == current.doc {
+ return Ok(None);
+ }
+ let signer = term::signer(profile)?;
+ // N.b. get the parent OID before updating the identity
+ let parent = current.current().id;
+ let revision = update(title, description, proposal, current, &signer)?;
+
+ if revision.is_accepted() && revision.parent == Some(parent) {
+ // Update the canonical head to point to the latest accepted revision.
+ repo.set_identity_head_to(revision.id)?;
+ }
+ Ok(Some(revision))
+}
+
+pub fn update<R, G>(
+ title: Option<String>,
+ description: Option<String>,
+ doc: Doc,
+ current: &mut IdentityMut<R>,
+ signer: &G,
+) -> anyhow::Result<Revision>
+where
+ R: WriteRepository + cob::Store,
+ G: Signer,
+{
+ if let Some((title, description)) = edit_title_description(title, description)? {
+ let id = current.update(title, description, &doc, signer)?;
+ let revision = current
+ .revision(&id)
+ .ok_or(anyhow!("update failed: revision {id} is missing"))?;
+
+ Ok(revision.clone())
+ } else {
+ Err(anyhow!("you must provide a revision title and description"))
+ }
+}
+
+pub fn edit_title_description(
+ title: Option<String>,
+ description: Option<String>,
+) -> anyhow::Result<Option<(String, String)>> {
+ const HELP: &str = r#"<!--
+Please enter a message for your changes. An empty message aborts the proposal.
+
+The first line is the title. The description follows, and must be separated with
+a blank line, just like a commit message. Markdown is supported in the title and
+description.
+-->"#;
+
+ let result = if let (Some(t), d) = (title.as_ref(), description.as_deref()) {
+ Some((t.to_owned(), d.unwrap_or_default().to_owned()))
+ } else {
+ let result = Message::edit_title_description(title, description, HELP)?;
+ if let Some((title, description)) = result {
+ Some((title, description))
+ } else {
+ None
+ }
+ };
+ Ok(result)
+}
+
+pub fn print<R>(
+ revision: &Revision,
+ previous: &Revision,
+ repo: &R,
+ profile: &Profile,
+) -> anyhow::Result<()>
+where
+ R: ReadRepository,
+{
+ print_meta(revision, previous, profile)?;
+ println!();
+ print_diff(revision.parent.as_ref(), &revision.id, repo)?;
+
+ Ok(())
+}
+
+fn print_meta(revision: &Revision, previous: &Doc, profile: &Profile) -> anyhow::Result<()> {
+ let mut attrs = term::Table::<2, term::Label>::new(Default::default());
+
+ attrs.push([
+ term::format::bold("Title").into(),
+ term::label(revision.title.to_owned()),
+ ]);
+ attrs.push([
+ term::format::bold("Revision").into(),
+ term::label(revision.id.to_string()),
+ ]);
+ attrs.push([
+ term::format::bold("Blob").into(),
+ term::label(revision.blob.to_string()),
+ ]);
+ attrs.push([
+ term::format::bold("Author").into(),
+ term::label(revision.author.to_string()),
+ ]);
+ attrs.push([
+ term::format::bold("State").into(),
+ term::label(revision.state.to_string()),
+ ]);
+ attrs.push([
+ term::format::bold("Quorum").into(),
+ if revision.is_accepted() {
+ term::format::positive("yes").into()
+ } else {
+ term::format::negative("no").into()
+ },
+ ]);
+
+ let mut meta = term::VStack::default()
+ .border(Some(term::colors::FAINT))
+ .child(attrs)
+ .children(if !revision.description.is_empty() {
+ vec![
+ term::Label::blank().boxed(),
+ term::textarea(revision.description.to_owned()).boxed(),
+ ]
+ } else {
+ vec![]
+ })
+ .divider();
+
+ let accepted = revision.accepted().collect::<Vec<_>>();
+ let rejected = revision.rejected().collect::<Vec<_>>();
+ let unknown = previous
+ .delegates()
+ .iter()
+ .filter(|id| !accepted.contains(id) && !rejected.contains(id))
+ .collect::<Vec<_>>();
+ let mut signatures = term::Table::<4, _>::default();
+
+ for id in accepted {
+ let author = term::format::Author::new(&id, profile);
+ signatures.push([
+ term::format::positive("✓").into(),
+ id.to_string().into(),
+ author.alias().unwrap_or_default(),
+ author.you().unwrap_or_default(),
+ ]);
+ }
+ for id in rejected {
+ let author = term::format::Author::new(&id, profile);
+ signatures.push([
+ term::format::negative("✗").into(),
+ id.to_string().into(),
+ author.alias().unwrap_or_default(),
+ author.you().unwrap_or_default(),
+ ]);
+ }
+ for id in unknown {
+ let author = term::format::Author::new(id, profile);
+ signatures.push([
+ term::format::dim("?").into(),
+ id.to_string().into(),
+ author.alias().unwrap_or_default(),
+ author.you().unwrap_or_default(),
+ ]);
+ }
+ meta.push(signatures);
+ meta.print();
+
+ Ok(())
+}
+
+fn print_diff<R>(
+ previous: Option<&RevisionId>,
+ current: &RevisionId,
+ repo: &R,
+) -> anyhow::Result<()>
+where
+ R: ReadRepository,
+{
+ let previous = if let Some(previous) = previous {
+ let previous = Doc::load_at(*previous, repo)?;
+ let previous = serde_json::to_string_pretty(&previous.doc)?;
+
+ Some(previous)
+ } else {
+ None
+ };
+ let current = Doc::load_at(*current, repo)?;
+ let current = serde_json::to_string_pretty(¤t.doc)?;
+
+ let tmp = tempfile::tempdir()?;
+ let repo = radicle::git::raw::Repository::init_bare(tmp.path())?;
+
+ let previous = if let Some(previous) = previous {
+ let tree = radicle::git::write_tree(&doc::PATH, previous.as_bytes(), &repo)?;
+ Some(tree)
+ } else {
+ None
+ };
+ let current = radicle::git::write_tree(&doc::PATH, current.as_bytes(), &repo)?;
+ let mut opts = radicle::git::raw::DiffOptions::new();
+ opts.context_lines(u32::MAX);
+
+ let diff = repo.diff_tree_to_tree(previous.as_ref(), Some(¤t), Some(&mut opts))?;
+ let diff = Diff::try_from(diff)?;
+
+ if let Some(modified) = diff.modified().next() {
+ let diff = modified.diff.to_unified_string()?;
+ print!("{diff}");
+ } else {
+ term::print(term::format::italic("No changes."));
+ }
+ Ok(())
+}
diff --git a/radicle-cli/src/lib.rs b/radicle-cli/src/lib.rs
index 32019151d..9b1e739bb 100644
--- a/radicle-cli/src/lib.rs
+++ b/radicle-cli/src/lib.rs
@@ -3,6 +3,7 @@
#![allow(clippy::too_many_arguments)]
pub mod commands;
pub mod git;
+pub mod id;
pub mod node;
pub mod pager;
pub mod project;
diff --git a/radicle-cli/src/main.rs b/radicle-cli/src/main.rs
index 11b998ff6..77965ff74 100644
--- a/radicle-cli/src/main.rs
+++ b/radicle-cli/src/main.rs
@@ -140,6 +140,13 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
args.to_vec(),
);
}
+ "cref" => {
+ term::run_command_args::<rad_cref::Options, _>(
+ rad_cref::HELP,
+ rad_cref::run,
+ args.to_vec(),
+ );
+ }
"checkout" => {
term::run_command_args::<rad_checkout::Options, _>(
rad_checkout::HELP,
diff --git a/radicle-cli/src/terminal/args.rs b/radicle-cli/src/terminal/args.rs
index 9215bacd7..b3012df98 100644
--- a/radicle-cli/src/terminal/args.rs
+++ b/radicle-cli/src/terminal/args.rs
@@ -5,9 +5,10 @@ use std::time;
use anyhow::anyhow;
-use radicle::cob::{self, issue, patch};
+use radicle::cob;
+use radicle::cob::{issue, patch};
use radicle::crypto;
-use radicle::git::{Oid, RefString};
+use radicle::git::{refspec::QualifiedPattern, Oid, PatternString, RefString};
use radicle::node::{Address, Alias};
use radicle::prelude::{Did, NodeId, RepoId};
@@ -107,6 +108,19 @@ pub fn refstring(flag: &str, value: OsString) -> anyhow::Result<RefString> {
})
}
+pub fn qualified_pattern_string(value: OsString) -> anyhow::Result<QualifiedPattern<'static>> {
+ let value = value
+ .into_string()
+ .map_err(|_| anyhow!("the <refspec> value is not valid UTF-8"))?;
+ PatternString::try_from(value.clone())
+ .map_err(|_| anyhow!("'{value}' is not a valid refspec string",))
+ .and_then(|s| {
+ let q = QualifiedPattern::from_patternstr(&s)
+ .ok_or(anyhow!("'{value}' is a not a qualified refspec string"))?;
+ Ok(q.to_owned())
+ })
+}
+
pub fn did(val: &OsString) -> anyhow::Result<Did> {
let val = val.to_string_lossy();
let Ok(peer) = Did::from_str(&val) else {
diff --git a/radicle-cli/tests/commands.rs b/radicle-cli/tests/commands.rs
index cfaadc498..4635a7040 100644
--- a/radicle-cli/tests/commands.rs
+++ b/radicle-cli/tests/commands.rs
@@ -244,6 +244,20 @@ fn rad_cob_multiset() {
test("examples/rad-cob-multiset.md", &working, Some(home), []).unwrap();
}
+#[test]
+fn rad_cref() {
+ let mut environment = Environment::new();
+ let profile = environment.profile(config::profile("alice"));
+ let home = &profile.home;
+ let working = environment.tmp().join("working");
+
+ // Setup a test repository.
+ fixtures::repository(&working);
+
+ test("examples/rad-init.md", &working, Some(home), []).unwrap();
+ test("examples/rad-cref.md", &working, Some(home), []).unwrap();
+}
+
#[test]
fn rad_cob_log() {
let mut environment = Environment::new();
@@ -480,7 +494,7 @@ fn rad_id() {
let bob = environment.node(config::node("bob"));
let working = tempfile::tempdir().unwrap();
let working = working.path();
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
// Setup a test repository.
fixtures::repository(working.join("alice"));
@@ -521,7 +535,11 @@ fn rad_id() {
.unwrap();
}
+// FIXME: the test for this would require being able to modify the threshold of
+// the default branch rule and the delegate set at the same time, but right now
+// that's not possible via the CLI.
#[test]
+#[ignore = "not implemented"]
fn rad_id_threshold() {
let mut environment = Environment::new();
let alice = environment.node(config::node("alice"));
@@ -529,7 +547,7 @@ fn rad_id_threshold() {
let seed = environment.node(config::node("seed"));
let working = tempfile::tempdir().unwrap();
let working = working.path();
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
// Setup a test repository.
fixtures::repository(working.join("alice"));
@@ -586,7 +604,7 @@ fn rad_id_threshold_soft_fork() {
let bob = environment.node(config::node("bob"));
let working = tempfile::tempdir().unwrap();
let working = working.path();
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
// Setup a test repository.
fixtures::repository(working.join("alice"));
@@ -664,7 +682,7 @@ fn rad_id_multi_delegate() {
let eve = environment.node(Config::test(Alias::new("eve")));
let working = tempfile::tempdir().unwrap();
let working = working.path();
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
// Setup a test repository.
fixtures::repository(working.join("alice"));
@@ -725,7 +743,7 @@ fn rad_id_collaboration() {
let distrustful = environment.node(config::seed("distrustful"));
let working = tempfile::tempdir().unwrap();
let working = working.path();
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
// Setup a test repository.
fixtures::repository(working.join("alice"));
@@ -827,7 +845,7 @@ fn rad_id_conflict() {
let bob = environment.node(Config::test(Alias::new("bob")));
let working = tempfile::tempdir().unwrap();
let working = working.path();
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
// Setup a test repository.
fixtures::repository(working.join("alice"));
@@ -1072,7 +1090,7 @@ fn rad_patch_checkout_force() {
let alice = environment.node(Config::test(Alias::new("alice")));
let bob = environment.node(Config::test(Alias::new("bob")));
let working = environment.tmp().join("working");
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
// Setup a test repository.
fixtures::repository(working.join("alice"));
@@ -1309,7 +1327,7 @@ fn rad_patch_delete() {
let bob = environment.node(config::relay("bob"));
let seed = environment.node(config::relay("seed"));
let working = environment.tmp().join("working");
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
// Setup a test repository.
fixtures::repository(working.join("alice"));
@@ -1369,7 +1387,7 @@ fn rad_clean() {
let working = environment.tmp().join("working");
// Setup a test project.
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
fixtures::repository(working.join("acme"));
test(
"examples/rad-init.md",
@@ -1651,7 +1669,7 @@ fn rad_clone_connect() {
let alice = environment.node(Config::test(Alias::new("alice")));
let bob = environment.node(Config::test(Alias::new("bob")));
let mut eve = environment.node(Config::test(Alias::new("eve")));
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
let ua = UserAgent::default();
let now = localtime::LocalTime::now().into();
@@ -2141,7 +2159,7 @@ fn rad_sync() {
let alice = environment.node(config::seed("alice"));
let bob = environment.node(config::seed("bob"));
let eve = environment.node(config::seed("eve"));
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
fixtures::repository(working.join("acme"));
@@ -2191,7 +2209,7 @@ fn test_replication_via_seed() {
..config::relay("seed")
});
let working = environment.tmp().join("working");
- let rid = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let rid = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
let mut alice = alice.spawn();
let mut bob = bob.spawn();
@@ -2278,7 +2296,7 @@ fn rad_remote() {
let eve = environment.node(config::relay("eve"));
let working = environment.tmp().join("working");
let home = alice.home.clone();
- let rid = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let rid = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
// Setup a test repository.
fixtures::repository(working.join("alice"));
@@ -2666,7 +2684,7 @@ fn git_push_diverge() {
let alice = environment.node(Config::test(Alias::new("alice")));
let bob = environment.node(Config::test(Alias::new("bob")));
let working = environment.tmp().join("working");
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
fixtures::repository(working.join("alice"));
@@ -2710,7 +2728,7 @@ fn git_push_converge() {
let bob = environment.node(Config::test(Alias::new("bob")));
let eve = environment.node(Config::test(Alias::new("eve")));
let working = environment.tmp().join("working");
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
fixtures::repository(working.join("alice"));
@@ -2771,7 +2789,7 @@ fn git_push_amend() {
let alice = environment.node(Config::test(Alias::new("alice")));
let bob = environment.node(Config::test(Alias::new("bob")));
let working = environment.tmp().join("working");
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
fixtures::repository(working.join("alice"));
@@ -2812,7 +2830,7 @@ fn git_push_rollback() {
let alice = environment.node(Config::test(Alias::new("alice")));
let bob = environment.node(Config::test(Alias::new("bob")));
let working = environment.tmp().join("working");
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
fixtures::repository(working.join("alice"));
@@ -2853,7 +2871,7 @@ fn rad_push_and_pull_patches() {
let alice = environment.node(Config::test(Alias::new("alice")));
let bob = environment.node(Config::test(Alias::new("bob")));
let working = environment.tmp().join("working");
- let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+ let acme = RepoId::from_str("z3W5xAVWJ9Gc4LbN16mE3tjWX92t2").unwrap();
fixtures::repository(working.join("alice"));
@@ -3061,6 +3079,54 @@ fn git_push_and_fetch() {
.unwrap();
}
+#[test]
+fn git_push_canonical_tags() {
+ let mut environment = Environment::new();
+ let alice = environment.node(Config::test(Alias::new("alice")));
+ let bob = environment.node(Config::test(Alias::new("bob")));
+ let working = environment.tmp().join("working");
+
+ fixtures::repository(working.join("alice"));
+
+ test(
+ "examples/rad-init.md",
+ working.join("alice"),
+ Some(&alice.home),
+ [],
+ )
+ .unwrap();
+
+ let alice = alice.spawn();
+ let mut bob = bob.spawn();
+
+ bob.connect(&alice).converge([&alice]);
+
+ test(
+ "examples/rad-clone.md",
+ working.join("bob"),
+ Some(&bob.home),
+ [],
+ )
+ .unwrap();
+ formula(
+ &environment.tmp(),
+ "examples/git/git-push-canonical-tags.md",
+ )
+ .unwrap()
+ .home(
+ "alice",
+ working.join("alice"),
+ [("RAD_HOME", alice.home.path().display())],
+ )
+ .home(
+ "bob",
+ working.join("bob"),
+ [("RAD_HOME", bob.home.path().display())],
+ )
+ .run()
+ .unwrap();
+}
+
#[test]
fn git_tag() {
let mut environment = Environment::new();
diff --git a/radicle-fetch/src/state.rs b/radicle-fetch/src/state.rs
index 6e47c02f0..a899c7428 100644
--- a/radicle-fetch/src/state.rs
+++ b/radicle-fetch/src/state.rs
@@ -32,8 +32,8 @@ pub const DEFAULT_FETCH_DATA_REFS_LIMIT: u64 = 1024 * 1024 * 1024 * 5;
pub mod error {
use std::io;
- use radicle::git::Oid;
use radicle::prelude::PublicKey;
+ use radicle::{git::Oid, identity::doc};
use thiserror::Error;
use crate::{git, git::repository, handle, sigrefs, stage};
@@ -67,6 +67,8 @@ pub mod error {
#[error("canonical 'refs/rad/id' is missing")]
MissingRadId,
#[error(transparent)]
+ Payload(#[from] doc::PayloadError),
+ #[error(transparent)]
RefdbUpdate(#[from] repository::error::Update),
#[error(transparent)]
Resolve(#[from] repository::error::Resolve),
@@ -401,12 +403,18 @@ impl FetchState {
log::trace!(target: "fetch", "Identity delegates {delegates:?}");
+ // The threshold is only relevant to the default branch, if there is
+ // none we set the threshold to 0 so that validation succeeds.
+ let threshold: usize = anchor
+ .default_branch_rule()?
+ .map(|rule| (*rule.threshold()).into())
+ .unwrap_or(0);
// The local peer does not need to count towards the threshold
// since they must be valid already.
let threshold = if is_delegate {
- anchor.threshold() - 1
+ threshold.saturating_sub(1)
} else {
- anchor.threshold()
+ threshold
};
let signed_refs = self.run_special_refs(
handle,
diff --git a/radicle-node/src/worker/fetch.rs b/radicle-node/src/worker/fetch.rs
index bf9bb3bc7..9c2ebb990 100644
--- a/radicle-node/src/worker/fetch.rs
+++ b/radicle-node/src/worker/fetch.rs
@@ -9,12 +9,14 @@ use radicle::cob::TypedId;
use radicle::crypto::PublicKey;
use radicle::identity::DocAt;
use radicle::prelude::RepoId;
+use radicle::storage::git::Repository;
use radicle::storage::refs::RefsAt;
use radicle::storage::{
ReadRepository, ReadStorage as _, RefUpdate, RemoteRepository, RepositoryError,
WriteRepository as _,
};
use radicle::{cob, git, node, Storage};
+use radicle_fetch::git::refs::Applied;
use radicle_fetch::{Allowed, BlockList, FetchLimit};
use super::channels::ChannelsFlush;
@@ -153,6 +155,10 @@ impl Handle {
Err(e) => return Err(e.into()),
}
+ if let Err(e) = set_canonical_refs(&repo, &applied) {
+ log::warn!(target: "worker", "Failed to set canonical references: {e}");
+ }
+
// Notifications are only posted for pulls, not clones.
if let Some(mut store) = notifs {
// Only create notifications for repos that we have
@@ -376,3 +382,47 @@ where
Ok(())
}
+
+fn set_canonical_refs(repo: &Repository, applied: &Applied) -> Result<(), error::Canonical> {
+ let identity = repo.identity()?;
+ let rules = identity.rules();
+ if rules.is_empty() {
+ return Ok(());
+ }
+ let matches = applied.updated.iter().filter_map(|r| match r {
+ RefUpdate::Updated { name, .. } | RefUpdate::Created { name, .. } => {
+ let name = name.clone().into_qualified()?;
+ let name = name.to_namespaced()?.strip_namespace();
+ rules.matches(name)
+ }
+ RefUpdate::Deleted { .. } | RefUpdate::Skipped { .. } => None,
+ });
+ for matched in matches {
+ let Ok(canonical) = matched.canonical(repo) else {
+ log::warn!(target: "worker", "Failed to get canonical tips for {}", matched.refname());
+ continue;
+ };
+ let Ok(oid) = canonical.quorum(&repo.backend) else {
+ log::warn!(
+ target: "worker",
+ "Failed to calculate canonical tip for {}",
+ matched.refname()
+ );
+ continue;
+ };
+ if let Err(e) = repo.backend.reference(
+ matched.refname().as_str(),
+ *oid,
+ true,
+ "set-canonical-reference from fetch (radicle)",
+ ) {
+ log::warn!(
+ target: "worker",
+ "Failed to set canonical tip for {}->{}: {e}",
+ matched.refname(),
+ oid
+ );
+ }
+ }
+ Ok(())
+}
diff --git a/radicle-node/src/worker/fetch/error.rs b/radicle-node/src/worker/fetch/error.rs
index 406b1e234..473e026be 100644
--- a/radicle-node/src/worker/fetch/error.rs
+++ b/radicle-node/src/worker/fetch/error.rs
@@ -65,3 +65,11 @@ pub enum Handle {
#[error(transparent)]
Repository(#[from] radicle::storage::RepositoryError),
}
+
+#[derive(Debug, Error)]
+pub enum Canonical {
+ #[error(transparent)]
+ Identity(#[from] radicle::storage::RepositoryError),
+ #[error(transparent)]
+ Rules(#[from] git::canonical::rules::ValidationError),
+}
diff --git a/radicle-remote-helper/src/push.rs b/radicle-remote-helper/src/push.rs
index e02dff1d8..fb61aed0c 100644
--- a/radicle-remote-helper/src/push.rs
+++ b/radicle-remote-helper/src/push.rs
@@ -14,7 +14,6 @@ use radicle::cob::patch::cache::Patches as _;
use radicle::crypto::Signer;
use radicle::explorer::ExplorerResource;
use radicle::git::canonical;
-use radicle::git::canonical::Canonical;
use radicle::identity::Did;
use radicle::node;
use radicle::node::{Handle, NodeId};
@@ -202,6 +201,10 @@ pub fn run(
}
}
let delegates = stored.delegates()?;
+ let identity = stored.identity()?;
+ let project = identity.project()?;
+ let canonical_ref = git::refs::branch(project.default_branch());
+ let mut set_canonical_refs: Vec<(git::Qualified, git::Oid)> = Vec::with_capacity(specs.len());
// For each refspec, push a ref or delete a ref.
for spec in specs {
@@ -212,7 +215,6 @@ pub fn run(
Command::Delete(dst) => {
// Delete refs.
let refname = nid.to_namespace().join(dst);
- let (canonical_ref, _) = &stored.head()?;
if *dst == canonical_ref.to_ref_string() && delegates.contains(&Did::from(nid)) {
return Err(Error::DeleteForbidden(dst.clone()));
@@ -261,8 +263,7 @@ pub fn run(
)
} else {
let identity = stored.identity()?;
- let project = identity.project()?;
- let canonical_ref = git::refs::branch(project.default_branch());
+ let rules = identity.rules();
let me = Did::from(nid);
// If we're trying to update the canonical head, make sure
@@ -271,66 +272,69 @@ pub fn run(
//
// Note that we *do* allow rolling back to a previous commit on the
// canonical branch.
- if dst == canonical_ref && delegates.contains(&me) && delegates.len() > 1 {
- let head = working.find_reference(src.as_str())?;
- let head = head.peel_to_commit()?.id();
-
- let mut canonical = Canonical::default_branch(
- stored,
- &project,
- identity.delegates().as_ref(),
- identity.threshold(),
- )?;
- let converges = canonical::converges(
- canonical
- .tips()
- .filter_map(|(did, tip)| (*did != me).then_some(tip)),
- head.into(),
- &working,
- )?;
- if converges {
- canonical.modify_vote(me, head.into());
- }
- match canonical.quorum(&working) {
- Ok(canonical_oid) => {
- // Canonical head is an ancestor of head.
- let is_ff = head == *canonical_oid
- || working.graph_descendant_of(head, *canonical_oid)?;
-
- if !is_ff && !converges {
- if hints {
- hint(
- "you are attempting to push a commit that would cause \
- your upstream to diverge from the canonical head",
+ if let Some(rule) = rules.matches(dst.clone()) {
+ if rule.allowed().contains(&me) {
+ let mut canonical = rule.canonical(stored)?;
+ let head = working.find_reference(src.as_str())?;
+ let head = head.peel_to_commit()?.id();
+ let converges = canonical::converges(
+ canonical
+ .tips()
+ .filter_map(|(did, tip)| (*did != me).then_some(tip)),
+ head.into(),
+ &working,
+ )?;
+
+ // If `canonical` is empty then we're creating a new reference.
+ // If we're the only delegate then we need to modify our vote.
+ if converges || canonical.is_empty() || rule.allowed().is_only(&me)
+ {
+ canonical.modify_vote(me, head.into());
+ }
+
+ match canonical.quorum(&working) {
+ Ok(canonical_oid) => {
+ // Canonical head is an ancestor of head.
+ let is_ff = head == *canonical_oid
+ || working.graph_descendant_of(head, *canonical_oid)?;
+
+ if !is_ff && !converges {
+ if hints {
+ hint(
+ format!("you are attempting to push a commit that would cause \
+ your upstream to diverge from the canonical reference {dst}"),
);
- hint(
+ hint(
"to integrate the remote changes, run `git pull --rebase` \
and try again",
);
+ }
+ return Err(Error::HeadsDiverge(
+ head.into(),
+ canonical_oid,
+ ));
}
- return Err(Error::HeadsDiverge(
- head.into(),
- canonical_oid,
+ set_canonical_refs
+ .push((dst.clone().to_owned(), canonical_oid));
+ }
+ Err(canonical::QuorumError::Diverging(e)) => {
+ warn(format!(
+ "could not determine canonical tip for `{dst}`"
));
+ warn(e.to_string());
+ warn("it is recommended to find a commit to agree upon");
}
+ Err(canonical::QuorumError::NoCandidates(e)) => {
+ warn(format!(
+ "could not determine canonical tip for `{dst}`"
+ ));
+ warn(e.to_string());
+ warn("it is recommended to find a commit to agree upon");
+ }
+ Err(e) => return Err(e.into()),
}
- Err(canonical::QuorumError::Diverging(e)) => {
- warn(format!(
- "could not determine canonical tip for `{canonical_ref}`"
- ));
- warn(e.to_string());
- warn("it is recommended to find a commit to agree upon");
- }
- Err(canonical::QuorumError::NoCandidates(e)) => {
- warn(format!(
- "could not determine canonical tip for `{canonical_ref}`"
- ));
- warn(e.to_string());
- warn("it is recommended to find a commit to agree upon");
- }
- Err(e) => return Err(e.into()),
- };
+ }
}
push(src, &dst, *force, &nid, &working, stored, patches, &signer)
}
@@ -353,16 +357,50 @@ pub fn run(
if !ok.is_empty() {
let _ = stored.sign_refs(&signer)?;
- // N.b. if an error occurs then there may be no quorum
- if let Ok(head) = stored.set_head() {
- if head.is_updated() {
+ for (refname, oid) in &set_canonical_refs {
+ let print_update = || {
eprintln!(
- "{} Canonical head updated to {}",
+ "{} Canonical head for {} updated to {}",
term::format::positive("✓"),
- term::format::secondary(head.new),
- );
+ term::format::secondary(refname),
+ term::format::secondary(oid),
+ )
+ };
+
+ // N.b. special case for handling the canonical ref, since it
+ // creates a symlink to HEAD
+ if *refname == canonical_ref
+ && stored
+ .set_head()
+ .map(|head| head.is_updated())
+ .unwrap_or(false)
+ {
+ print_update();
+ continue;
}
- };
+
+ match stored.backend.refname_to_id(refname.as_str()) {
+ Ok(new) if new != **oid => {
+ stored.backend.reference(
+ refname.as_str(),
+ **oid,
+ true,
+ "set-canonical-reference from git-push (radicle)",
+ )?;
+ print_update();
+ }
+ Err(e) if e.code() == git::raw::ErrorCode::NotFound => {
+ stored.backend.reference(
+ refname.as_str(),
+ **oid,
+ true,
+ "set-canonical-reference from git-push (radicle)",
+ )?;
+ print_update();
+ }
+ _ => {}
+ }
+ }
if !opts.no_sync {
if profile.policies()?.is_seeding(&stored.id)? {
diff --git a/radicle/src/cob/identity.rs b/radicle/src/cob/identity.rs
index 73dc74e3a..4a5798ea4 100644
--- a/radicle/src/cob/identity.rs
+++ b/radicle/src/cob/identity.rs
@@ -1068,11 +1068,6 @@ mod test {
.unwrap_err();
assert_eq!(identity.current, r0);
- // Change threshold to `2`, even though there's only one delegate. This should
- // fail as it makes the master branch immutable.
- doc.threshold = 2;
- assert!(doc.clone().verified().is_err());
-
// Let's add another delegate.
doc.delegate(bob.public_key().into());
// The update should go through now.
@@ -1274,13 +1269,16 @@ mod test {
.unwrap();
bob.repo.fetch(alice);
- let a3 = alice_identity.redact(a2, &alice.signer).unwrap();
+ let a3 = cob::git::stable::with_advanced_timestamp(|| {
+ alice_identity.redact(a2, &alice.signer).unwrap()
+ });
assert!(alice_identity.revision(&a1).is_some());
assert_eq!(alice_identity.timeline, vec![a0, a1, a2, a3]);
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
- let b1 = bob_identity.accept(&a2, &bob.signer).unwrap();
-
+ let b1 = cob::git::stable::with_advanced_timestamp(|| {
+ bob_identity.accept(&a2, &bob.signer).unwrap()
+ });
assert_eq!(bob_identity.timeline, vec![a0, a1, a2, b1]);
assert_eq!(bob_identity.revision(&a2).unwrap().state, State::Accepted);
bob.repo.fetch(alice);
@@ -1442,7 +1440,7 @@ mod test {
eve.repo.fetch(bob);
eve_identity.reload().unwrap();
- assert_eq!(eve_identity.timeline, vec![a0, a1, a2, b1, e1, e2]);
+ assert_eq!(eve_identity.timeline, vec![a0, a1, a2, e1, e2, b1]);
// Her revision is there, although stale, since another revision was accepted since.
// However, it wasn't pruned, even though rejecting an accepted revision is an error.
@@ -1568,7 +1566,6 @@ mod test {
// Add Bob as a delegate, and sign it.
doc.delegate(bob.public_key().into());
- doc.threshold = 2;
identity
.update("Add bob", "", &doc.clone().verified().unwrap(), &alice)
.unwrap();
diff --git a/radicle/src/cob/patch.rs b/radicle/src/cob/patch.rs
index 1666c1d39..ff1b6c503 100644
--- a/radicle/src/cob/patch.rs
+++ b/radicle/src/cob/patch.rs
@@ -1078,8 +1078,14 @@ impl Patch {
acc
},
);
- // Discard revisions that weren't merged by a threshold of delegates.
- merges.retain(|_, count| *count >= identity.threshold());
+
+ {
+ if let Some(rule) = identity.default_branch_rule()? {
+ let threshold = (*rule.rule().threshold()).into();
+ // Discard revisions that weren't merged by a threshold of delegates.
+ merges.retain(|_, count| *count >= threshold);
+ }
+ }
match merges.into_keys().collect::<Vec<_>>().as_slice() {
[] => {
@@ -2814,6 +2820,7 @@ mod test {
use crate::cob::common::CodeRange;
use crate::cob::test::Actor;
use crate::crypto::test::signer::MockSigner;
+ use crate::git::canonical::rules::RawRules;
use crate::identity;
use crate::patch::cache::Patches as _;
use crate::profile::env;
@@ -3076,6 +3083,7 @@ mod test {
gen::<Project>(1),
vec![alice.did()],
1,
+ RawRules::default(),
identity::Visibility::Public,
)
.verified()
diff --git a/radicle/src/identity.rs b/radicle/src/identity.rs
index c149c83dc..0dffe5f94 100644
--- a/radicle/src/identity.rs
+++ b/radicle/src/identity.rs
@@ -5,7 +5,9 @@ pub mod project;
pub use crypto::PublicKey;
pub use did::Did;
-pub use doc::{Doc, DocAt, DocError, IdError, PayloadError, RawDoc, RepoId, Visibility};
+pub use doc::{
+ Doc, DocAt, DocError, IdError, PayloadError, RawDoc, RepoId, VersionedRawDoc, Visibility,
+};
pub use project::Project;
pub use crate::cob::identity::{Action, Error, Identity, IdentityMut, TYPENAME};
diff --git a/radicle/src/identity/doc.rs b/radicle/src/identity/doc.rs
index a3abb7303..ce444ddd5 100644
--- a/radicle/src/identity/doc.rs
+++ b/radicle/src/identity/doc.rs
@@ -1,8 +1,13 @@
mod id;
+mod v1;
+mod version;
+
+use version::VersionTwo;
+use version::KNOWN_VERSIONS;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
-use std::num::{NonZeroU32, NonZeroUsize};
+use std::num::NonZeroUsize;
use std::ops::{Deref, Not};
use std::path::Path;
use std::str::FromStr;
@@ -19,6 +24,9 @@ use crate::cob::identity;
use crate::crypto;
use crate::crypto::Signature;
use crate::git;
+use crate::git::canonical::rules;
+use crate::git::canonical::rules::{MatchedRule, RawRules, Rule};
+use crate::git::canonical::Rules;
use crate::identity::{project::Project, Did};
use crate::storage;
use crate::storage::{ReadRepository, RepositoryError};
@@ -32,12 +40,13 @@ pub static PATH: Lazy<&Path> = Lazy::new(|| Path::new("radicle.json"));
pub const MAX_STRING_LENGTH: usize = 255;
/// Maximum number of a delegates in the identity document.
pub const MAX_DELEGATES: usize = 255;
-/// The current, most recent version of the identity document.
-// SAFETY: identity version should never be 0, so we can use `unsafe` here
-pub const IDENTITY_VERSION: Version = Version(unsafe { NonZeroU32::new_unchecked(1) });
#[derive(Error, Debug)]
pub enum DocError {
+ #[error(transparent)]
+ Pattern(#[from] rules::PatternError),
+ #[error(transparent)]
+ CanonicalRefs(#[from] rules::ValidationError),
#[error("json: {0}")]
Json(#[from] serde_json::Error),
#[error(transparent)]
@@ -50,6 +59,8 @@ pub enum DocError {
Git(#[from] git2::Error),
#[error("missing identity document")]
Missing,
+ #[error("migration: {0}")]
+ MigrationError(#[from] MigrationError),
}
#[derive(Debug, Error)]
@@ -72,120 +83,6 @@ impl DocError {
}
}
-/// The version number of the identity document.
-///
-/// It is used to ensure compatibility when parsing identity documents.
-///
-/// If an invalid version is found – either the `0` version, or an unrecognized
-/// future version – the parsing of a version will fail.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
-pub struct Version(NonZeroU32);
-
-impl Version {
- /// Construct a [`Version`].
- ///
- /// # Errors
- ///
- /// - `n` is 0
- /// - `n` is greater than the latest version, specified by
- /// [`IDENTITY_VERSION`].
- pub fn new(n: u32) -> Result<Version, VersionError> {
- match NonZeroU32::new(n) {
- None => Err(VersionError::ZeroVersion),
- Some(n) if n > IDENTITY_VERSION.into() => Err(VersionError::UnkownVersion(n)),
- Some(n) => Ok(Version(n)),
- }
- }
-
- /// Return the underlying [`NonZeroU32`] number of the `Version`.
- pub fn number(&self) -> NonZeroU32 {
- self.0
- }
-
- /// Check if the provided version is part of the set of accepted versions.
- pub fn is_valid_version(v: &u32) -> bool {
- 0 < *v && *v <= IDENTITY_VERSION.into()
- }
-
- /// Helper for skipping the serialization of the version if `version <= 1`.
- ///
- /// Note that we shouldn't allow `version: 0`, but there is no harm in
- /// skipping it anyway.
- fn skip_serializing(&self) -> bool {
- u32::from(*self) <= 1
- }
-}
-
-impl From<Version> for NonZeroU32 {
- fn from(Version(n): Version) -> Self {
- n
- }
-}
-
-impl From<Version> for u32 {
- fn from(Version(n): Version) -> Self {
- n.into()
- }
-}
-
-#[derive(Debug, Error)]
-#[non_exhaustive]
-pub enum VersionError {
- #[error("the version 0 is not supported")]
- ZeroVersion,
- #[error("unknown identity document version {0}, only version {IDENTITY_VERSION} is supported")]
- UnkownVersion(NonZeroU32),
-}
-
-impl VersionError {
- /// Provide a verbose error.
- ///
- /// This will give a user more information on how to upgrade to a newer
- /// version of an identity document, if there is one.
- pub fn verbose(&self) -> String {
- const UNKOWN_VERSION_ERROR: &str = r#"
-Perhaps a new version of the identity document is released which is not supported by the current client.
-See https://radicle.xyz for the latest versions of Radicle.
-The CLI command `rad id migrate` will help to migrate to an up-to-date versions."#;
-
- match self {
- err @ Self::ZeroVersion => err.to_string(),
- err @ Self::UnkownVersion(_) => format!("{err}{UNKOWN_VERSION_ERROR}"),
- }
- }
-}
-
-impl TryFrom<u32> for Version {
- type Error = VersionError;
-
- fn try_from(n: u32) -> Result<Self, Self::Error> {
- Version::new(n)
- }
-}
-
-impl fmt::Display for Version {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}", self.0)
- }
-}
-
-impl<'de> Deserialize<'de> for Version {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- u32::deserialize(deserializer)
- .and_then(|v| Version::new(v).map_err(|e| de::Error::custom(e.to_string())))
- }
-}
-
-/// Used for [`Deserialize`] of a [`Version`] in [`RawDoc`], so that
-/// deserializing a missing version results in `Version(1)`.
-fn missing_version() -> Version {
- // N.B. the default version is `1` which is non-zero so unsafe is fine here
- unsafe { Version(NonZeroU32::new_unchecked(1)) }
-}
-
/// Identifies an identity document payload type.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
@@ -224,6 +121,15 @@ pub enum PayloadError {
NotFound(PayloadId),
}
+impl PayloadError {
+ pub fn is_not_found(&self) -> bool {
+ match self {
+ PayloadError::Json(_) => false,
+ PayloadError::NotFound(_) => true,
+ }
+ }
+}
+
/// A `Payload` is a free-form JSON value that can be associated with an
/// identity's [`Doc`].
/// The payload is identified in the [`Doc`] by its corresponding [`PayloadId`].
@@ -346,22 +252,115 @@ impl Visibility {
/// serializing an unverified document, while also making sure that any document
/// that is deserialized is verified.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct RawDoc {
- /// Version of the identity document.
- #[serde(default = "missing_version")]
- version: Version,
+ version: VersionTwo,
/// The payload section.
pub payload: BTreeMap<PayloadId, Payload>,
/// The delegates section.
pub delegates: Vec<Did>,
- /// The signature threshold.
- pub threshold: usize,
+ /// The canonical reference rules.
+ #[serde(default)]
+ pub canonical_refs: RawCanonicalRefs,
/// Repository visibility.
#[serde(default)]
pub visibility: Visibility,
}
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum VersionedRawDoc {
+ V2(RawDoc),
+ V1(v1::RawDoc),
+}
+
+impl<'de> Deserialize<'de> for VersionedRawDoc {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ use serde::de::Error;
+
+ // We could use the `serde-value` crate and its type
+ // [`serde_value::Value`] to achieve the same, without
+ // depending on a particular format, such as JSON.
+ // However, we do depend on `serde_json` already,
+ // and it is the actual format we use, so this is fine.
+ use serde_json::{from_value, Value};
+
+ fn default_version() -> u64 {
+ (*KNOWN_VERSIONS.start()).into()
+ }
+
+ /// The derived implementation of [`Deserialize`]
+ /// for this helper struct allows us to peek for a version
+ /// field in the contents we are deserializing via [`Helper::version`],
+ /// and at the same time collects all other fields in [`Helper::value`].
+ #[derive(Debug, Deserialize)]
+ struct Helper {
+ /// This is of type [`u64`] and not [`std::num::NonZeroU64`] or
+ /// [`version::Version`] so that we can handle zero with an explicit
+ /// error below.
+ #[serde(default = "default_version")]
+ version: u64,
+ #[serde(flatten)]
+ value: Value,
+ }
+
+ let Helper { version, mut value } = Helper::deserialize(deserializer)?;
+
+ if let Value::Object(ref mut map) = value {
+ const VERSION: &str = "version";
+ debug_assert!(!map.contains_key(&VERSION.to_string()));
+ // Since [`Helper`] has its own field [`Helper::version`],
+ // we have to copy it to [`Helper::value`].
+ map.insert(VERSION.to_string(), version.into());
+ }
+
+ match version {
+ 1 => Ok(Self::V1(from_value(value).map_err(Error::custom)?)),
+ 2 => Ok(Self::V2(from_value(value).map_err(Error::custom)?)),
+ v => Err(Error::custom(format!(
+ "invalid value for field version: {v}, expected a positive non-zero integer value in interval [{},{}]",
+ KNOWN_VERSIONS.start(),
+ KNOWN_VERSIONS.end(),
+ ))),
+ }
+ }
+}
+
+impl TryFrom<VersionedRawDoc> for Doc {
+ type Error = DocError;
+
+ fn try_from(value: VersionedRawDoc) -> Result<Doc, Self::Error> {
+ match value {
+ VersionedRawDoc::V1(raw) => {
+ let doc = raw.verified()?;
+ Ok(Doc::migrate_from(doc)?)
+ }
+ VersionedRawDoc::V2(raw) => raw.verified(),
+ }
+ }
+}
+
+/// Configuration for canonical references and their rules.
+///
+/// `RawCanonicalRefs` is used in [`RawDoc`], and is verified into
+/// [`CanonicalRefs`].
+///
+/// Note that it must implement `Default` for `Deserialize` for compatibility –
+/// any fields being added must be able to implement default too.
+#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct RawCanonicalRefs {
+ pub rules: RawRules,
+}
+
+impl RawCanonicalRefs {
+ pub fn new(rules: RawRules) -> Self {
+ Self { rules }
+ }
+}
+
impl TryFrom<RawDoc> for Doc {
type Error = DocError;
@@ -377,26 +376,21 @@ impl RawDoc {
pub fn new(
project: Project,
delegates: Vec<Did>,
- threshold: usize,
+ _threshold: usize,
+ rules: RawRules,
visibility: Visibility,
) -> Self {
let project =
serde_json::to_value(project).expect("Doc::initial: payload must be serializable");
-
Self {
- version: IDENTITY_VERSION,
+ version: VersionTwo,
payload: BTreeMap::from_iter([(PayloadId::project(), Payload::from(project))]),
delegates,
- threshold,
+ canonical_refs: RawCanonicalRefs::new(rules),
visibility,
}
}
- /// Get the version of the document.
- pub fn version(&self) -> &Version {
- &self.version
- }
-
/// Get the project payload, if it exists and is valid, out of this document.
pub fn project(&self) -> Result<Project, PayloadError> {
let value = self
@@ -442,22 +436,29 @@ impl RawDoc {
/// - [`RawDoc::delegates`]: any duplicates are removed, and for the
/// remaining set ensure that it is non-empty and does not exceed a
/// length of [`MAX_DELEGATES`].
- /// - [`RawDoc::threshold`]: ensure that it is in the range `[1, delegates.len()]`.
pub fn verified(self) -> Result<Doc, DocError> {
let RawDoc {
version,
payload,
delegates,
- threshold,
+ canonical_refs,
visibility,
+ ..
} = self;
+
let delegates = Delegates::new(delegates)?;
- let threshold = Threshold::new(threshold, &delegates)?;
+ let rules = Rules::from_raw(canonical_refs.rules, &mut |ds| match ds {
+ rules::Allowed::Delegates => Ok(delegates.clone()),
+ rules::Allowed::Set(delegates) => {
+ Delegates::new(delegates).map_err(rules::ValidationError::from)
+ }
+ })?;
+
Ok(Doc {
version,
payload,
delegates,
- threshold,
+ canonical_refs: CanonicalRefs { rules },
visibility,
})
}
@@ -535,6 +536,11 @@ impl Delegates {
self.0.contains(did)
}
+ /// Check if the `did` is the only delegate of the repository.
+ pub fn is_only(&self, did: &Did) -> bool {
+ self.0.tail.is_empty() && &self.0.head == did
+ }
+
/// Get the number of delegates in the set.
pub fn len(&self) -> usize {
self.0.len()
@@ -612,6 +618,17 @@ impl Threshold {
}
}
+#[derive(Debug, Error)]
+pub enum MigrationError {
+ #[error("failed to migrate from v1: {0}")]
+ V1(#[from] v1::MigrationError),
+ #[error("failed to parse previous version {version}: {err}")]
+ Unknown {
+ version: u32,
+ err: serde_json::Error,
+ },
+}
+
/// `Doc` is a valid identity document.
///
/// To ensure that only valid documents are used, this type is restricted to be
@@ -622,45 +639,84 @@ impl Threshold {
/// 1. [`Doc::initial`]: a safe way to construct the initial document for an identity.
/// 2. [`RawDoc::verified`]: validates a [`RawDoc`]'s fields and converts it
/// into a `Doc`
-/// 3. [`Deserialize`]: will deserialize a `Doc` by first deserializing a
-/// [`RawDoc`] and use [`RawDoc::verified`] to construct the `Doc`.
-/// 4. [`Doc::from_blob`]: construct a `Doc` from a Git blob by deserializing
+/// 3. [`Doc::from_blob`]: construct a `Doc` from a Git blob by deserializing
/// its contents.
-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
-#[serde(try_from = "RawDoc")]
pub struct Doc {
- #[serde(skip_serializing_if = "Version::skip_serializing")]
- version: Version,
+ version: VersionTwo,
payload: BTreeMap<PayloadId, Payload>,
delegates: Delegates,
- threshold: Threshold,
+ #[serde(skip_serializing_if = "CanonicalRefs::is_empty")]
+ canonical_refs: CanonicalRefs,
#[serde(default, skip_serializing_if = "Visibility::is_public")]
visibility: Visibility,
}
+/// Configuration for canonical references and their [`Rules`].
+///
+/// `CanonicalRefs` is used in [`Doc`], and the [`Rules`] are accessed via
+/// [`Doc::rules`].
+// Note that it must implement [`CanonicalRefs::is_empty`] for skipping
+// serialization, for compatibility – any fields being added must be accounted
+// for in this method.
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CanonicalRefs {
+ rules: Rules,
+}
+
+impl CanonicalRefs {
+ pub fn new(rules: Rules) -> Self {
+ CanonicalRefs { rules }
+ }
+
+ /// Check if the data structure empty. Used in the [`Serialize`]
+ /// implementation.
+ fn is_empty(&self) -> bool {
+ // N.b. account for any new fields when adding them.
+ self.rules.is_empty()
+ }
+}
+
+impl From<CanonicalRefs> for RawCanonicalRefs {
+ fn from(crefs: CanonicalRefs) -> Self {
+ Self {
+ rules: crefs.rules.into(),
+ }
+ }
+}
+
impl Doc {
/// Construct the initial [`Doc`] for an identity.
///
/// It will begin with the provided `project` in the [`Doc::payload`], the
/// `delegate` as the sole delegate, a threshold of 1, and the given
/// `visibility`.
- pub fn initial(project: Project, delegate: Did, visibility: Visibility) -> Self {
+ pub fn initial(
+ project: Project,
+ delegate: Did,
+ visibility: Visibility,
+ ) -> Result<Self, rules::PatternError> {
+ let rules = [Rule::default_branch(delegate, project.default_branch())?]
+ .into_iter()
+ .collect();
let project =
serde_json::to_value(project).expect("Doc::initial: payload must be serializable");
- Self {
- version: IDENTITY_VERSION,
+ Ok(Self {
+ version: VersionTwo,
payload: BTreeMap::from_iter([(PayloadId::project(), Payload::from(project))]),
delegates: Delegates(NonEmpty::new(delegate)),
- threshold: Threshold(NonZeroUsize::MIN),
+ canonical_refs: CanonicalRefs::new(rules),
visibility,
- }
+ })
}
/// Construct a [`Doc`] contained in the provided Git blob.
pub fn from_blob(blob: &git2::Blob) -> Result<Self, DocError> {
- RawDoc::from_json(blob.content())?.verified()
+ let raw = serde_json::from_slice::<VersionedRawDoc>(blob.content())?;
+ Self::try_from(raw)
}
/// Convert the [`Doc`] into a [`RawDoc`] for changing the field values and
@@ -670,14 +726,14 @@ impl Doc {
version,
payload,
delegates,
- threshold,
+ canonical_refs,
visibility,
} = self;
RawDoc {
version,
payload,
delegates: delegates.into(),
- threshold: threshold.into(),
+ canonical_refs: canonical_refs.into(),
visibility,
}
}
@@ -693,11 +749,6 @@ impl Doc {
raw.verified()
}
- /// Get the version of the document.
- pub fn version(&self) -> &Version {
- &self.version
- }
-
/// Return the associated payloads for this [`Doc`].
pub fn payload(&self) -> &BTreeMap<PayloadId, Payload> {
&self.payload
@@ -729,16 +780,6 @@ impl Doc {
self.visibility.is_private()
}
- /// Return the associated threshold of this document.
- pub fn threshold(&self) -> usize {
- self.threshold.into()
- }
-
- /// Return the associated threshold of this document in its non-zero format.
- pub fn threshold_nonzero(&self) -> &NonZeroUsize {
- &self.threshold.0
- }
-
/// Return the associated delegates of this document.
pub fn delegates(&self) -> &Delegates {
&self.delegates
@@ -749,6 +790,27 @@ impl Doc {
self.delegates.contains(did)
}
+ /// Return the canonical reference rules of this document.
+ pub fn rules(&self) -> &Rules {
+ &self.canonical_refs.rules
+ }
+
+ /// Return the canonical reference rule for the identity's default branch,
+ /// if the repository has a [`Doc::project`] associated with it.
+ pub fn default_branch_rule(&self) -> Result<Option<MatchedRule>, PayloadError> {
+ match self.project() {
+ Ok(project) => Ok(self.rule_of(git::refs::branch(project.default_branch()).clone())),
+ Err(e) if e.is_not_found() => Ok(None),
+ Err(e) => Err(e),
+ }
+ }
+
+ /// Get the matching rule, if present, from the identity's canonical
+ /// reference rules.
+ pub fn rule_of<'a>(&self, refname: git::Qualified<'a>) -> Option<MatchedRule<'a>> {
+ self.rules().matches(refname)
+ }
+
/// Check whether this document and the associated repository is visible to
/// the given peer.
pub fn is_visible_to(&self, did: &Did) -> bool {
@@ -839,6 +901,16 @@ impl Doc {
})
}
+ /// Load the identity document as raw JSON from the provided `commit`.
+ pub fn load_json<R: ReadRepository>(
+ commit: Oid,
+ repo: &R,
+ ) -> Result<serde_json::Value, DocError> {
+ let blob = Self::blob_at(commit, repo)?;
+ let value = serde_json::from_slice::<serde_json::Value>(blob.content())?;
+ Ok(value)
+ }
+
/// Initialize an [`identity::Identity`] with this [`Doc`] as the associated
/// document.
pub fn init<G: crypto::Signer>(
@@ -863,6 +935,10 @@ impl Doc {
Ok(*cob.id)
}
+
+ pub fn migrate_from(doc: v1::Doc) -> Result<Self, MigrationError> {
+ doc.migrate().map_err(MigrationError::from)
+ }
}
#[cfg(test)]
@@ -888,7 +964,13 @@ mod test {
fn test_duplicate_dids() {
let delegate = MockSigner::from_seed([0xff; 32]);
let did = Did::from(delegate.public_key());
- let mut doc = RawDoc::new(gen::<Project>(1), vec![did], 1, Visibility::Public);
+ let mut doc = RawDoc::new(
+ gen::<Project>(1),
+ vec![did],
+ 1,
+ RawRules::default(),
+ Visibility::Public,
+ );
doc.delegate(did);
let doc = doc.verified().unwrap();
assert!(doc.delegates().len() == 1, "Duplicate DID was not removed");
@@ -905,44 +987,74 @@ mod test {
gen::<Project>(1),
delegates[0..MAX_DELEGATES].into(),
1,
+ RawRules::default(),
Visibility::Public,
);
assert_matches!(doc.verified(), Ok(_));
// A document that exceeds max delegates should fail
- let doc = RawDoc::new(gen::<Project>(1), delegates, 1, Visibility::Public);
+ let doc = RawDoc::new(
+ gen::<Project>(1),
+ delegates,
+ 1,
+ RawRules::default(),
+ Visibility::Public,
+ );
assert_matches!(doc.verified(), Err(DocError::Delegates(DelegatesError(_))));
}
#[test]
- fn test_is_valid_version() {
- // 0 is not a valid version
- assert!(!Version::is_valid_version(&0));
-
- // Ensures that the latest version is always valid
- let current = IDENTITY_VERSION.number();
- assert!(Version::is_valid_version(¤t.into()));
-
- // Ensures that the next version is not valid because we have not
- // defined it yet
- let next = current.checked_add(1).unwrap();
- assert!(!Version::is_valid_version(&next.into()));
- }
-
- #[test]
- fn test_future_version_error() {
- let v = Version(NonZeroU32::MAX).to_string();
- assert_eq!(
- serde_json::from_str::<Version>(&v)
- .expect_err("should fail to deserialize")
- .to_string(),
- VersionError::UnkownVersion(NonZeroU32::MAX).to_string(),
- )
+ fn test_version_out_of_range() {
+ let zero = json!({
+ "version": 0,
+ "payload": {
+ "xyz.radicle.project": {
+ "defaultBranch": "main",
+ "description": "Example project",
+ "name": "example"
+ }
+ },
+ "delegates": [
+ "did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
+ ],
+ });
+ assert_matches!(serde_json::from_value::<VersionedRawDoc>(zero), Err(_));
+
+ let next = json!({
+ "version": Into::<u64>::into(*KNOWN_VERSIONS.end()).saturating_add(1),
+ "payload": {
+ "xyz.radicle.project": {
+ "defaultBranch": "main",
+ "description": "Example project",
+ "name": "example"
+ }
+ },
+ "delegates": [
+ "did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
+ ],
+ });
+ assert_matches!(serde_json::from_value::<VersionedRawDoc>(next), Err(_));
+
+ let max = json!({
+ "version": u64::MAX,
+ "payload": {
+ "xyz.radicle.project": {
+ "defaultBranch": "main",
+ "description": "Example project",
+ "name": "example"
+ }
+ },
+ "delegates": [
+ "did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
+ ],
+ });
+ assert_matches!(serde_json::from_value::<VersionedRawDoc>(max), Err(_));
}
#[test]
- fn test_parse_version() {
- // Original document before introducing the version field
+ fn test_migrate_v1() {
+ // Harcoded version 1 of the identity document. We expect that parsing
+ // this into the latest document should pass.
let v1 = json!(
{
"payload": {
@@ -961,9 +1073,6 @@ mod test {
}
);
- // Deserializing the `RawDoc` should not fail and should include the
- // `IDENTITY_VERSION`.
- let doc = serde_json::from_str::<RawDoc>(&v1.to_string()).unwrap();
let payload = [(
PayloadId::project(),
Payload {
@@ -987,31 +1096,51 @@ mod test {
.parse::<Did>()
.unwrap(),
];
- // And this is the expected outcome of the deserialization
+
+ let doc = serde_json::from_str::<v1::RawDoc>(&v1.to_string()).unwrap();
+
assert_eq!(
doc,
- RawDoc {
- version: IDENTITY_VERSION,
+ v1::RawDoc {
+ version: version::VersionOne,
payload: payload.clone(),
delegates: delegates.clone(),
- threshold: 1,
visibility: Visibility::Public,
+ threshold: 1,
}
);
- // Deserializing into `Doc` should also succeed.
- let verified = serde_json::from_str::<Doc>(&v1.to_string()).unwrap();
- let delegates = Delegates(NonEmpty::from_vec(delegates).unwrap());
+ // Verifying into `Doc` should also succeed.
+ let verified = doc.verified().unwrap();
+
+ let delegates = NonEmpty::from_vec(delegates).unwrap();
+ // Note that v1 -> v2 upgrade introduces adding a rule for the default
+ // branch when it comes to project repositories.
+ let rules = Rules::from_raw(
+ [(
+ rules::Pattern::try_from(git::refspec::qualified_pattern!("refs/heads/master"))
+ .unwrap(),
+ Rule::new(rules::Allowed::Delegates, 1),
+ )],
+ &mut |ds| match ds {
+ rules::Allowed::Delegates => Ok(Delegates(delegates.clone())),
+ rules::Allowed::Set(_) => Ok(Delegates(delegates.clone())),
+ },
+ )
+ .unwrap();
+
+ // Test that we can migrate from the raw JSON value into a verified
+ // latest version of the document
assert_eq!(
- verified,
+ Doc::migrate_from(verified).unwrap(),
Doc {
- version: IDENTITY_VERSION,
- threshold: Threshold::new(1, &delegates).unwrap(),
- payload: payload.clone(),
- delegates,
+ version: VersionTwo,
+ payload,
+ delegates: Delegates(delegates),
+ canonical_refs: CanonicalRefs::new(rules),
visibility: Visibility::Public,
}
- );
+ )
}
#[test]
@@ -1040,9 +1169,9 @@ mod test {
);
assert_eq!(
(*id).to_string(),
- "d96f425412c9f8ad5d9a9a05c9831d0728e2338d"
+ "b38d81ee99d880461a3b7b3502e5d1556e440ef3"
);
- assert_eq!(id.urn(), String::from("rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji"));
+ assert_eq!(id.urn(), String::from("rad:z3W5xAVWJ9Gc4LbN16mE3tjWX92t2"));
}
#[test]
diff --git a/radicle/src/identity/doc/v1.rs b/radicle/src/identity/doc/v1.rs
new file mode 100644
index 000000000..cbdc29416
--- /dev/null
+++ b/radicle/src/identity/doc/v1.rs
@@ -0,0 +1,266 @@
+//! We keep track of the previous versions of the identity documents for testing
+//! and migrating.
+//!
+//! This [`Doc`] is the Version 1 of the identity document.
+
+use std::collections::BTreeMap;
+
+use serde::{Deserialize, Serialize};
+use thiserror::Error;
+
+use crate::git;
+use crate::git::canonical::rules;
+use crate::git::canonical::rules::{RawRules, Rule, Rules, ValidationError};
+use crate::prelude::{Did, Project};
+
+use super::{
+ version::VersionOne, CanonicalRefs, Delegates, DocError, Payload, PayloadError, PayloadId,
+ Threshold, Visibility,
+};
+
+/// `RawDoc` is similar to the [`Doc`] type, however, it can be edited and may
+/// not be valid.
+///
+/// It is expected that any changes to a [`Doc`] are made via [`RawDoc`], and
+/// then verified by using [`RawDoc::verified`].
+///
+/// Note that `RawDoc` only implements [`Deserialize`]. This prevents us from
+/// serializing an unverified document, while also making sure that any document
+/// that is deserialized is verified.
+#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
+#[serde(deny_unknown_fields, rename_all = "camelCase")]
+pub struct RawDoc {
+ #[serde(default)]
+ pub version: VersionOne,
+ /// The payload section.
+ pub payload: BTreeMap<PayloadId, Payload>,
+ /// The delegates section.
+ pub delegates: Vec<Did>,
+ /// The signature threshold.
+ pub threshold: usize,
+ /// Repository visibility.
+ #[serde(default)]
+ pub visibility: Visibility,
+}
+
+impl RawDoc {
+ /// Verify the `RawDoc`'s values, converting it into a valid [`Doc`].
+ ///
+ /// The verifications are as follows:
+ ///
+ /// - [`RawDoc::delegates`]: any duplicates are removed, and for the
+ /// remaining set ensure that it is non-empty and does not exceed a
+ /// length of [`MAX_DELEGATES`].
+ /// - [`RawDoc::threshold`]: ensure that it is in the range `[1, delegates.len()]`.
+ pub fn verified(self) -> Result<Doc, DocError> {
+ let RawDoc {
+ payload,
+ delegates,
+ threshold,
+ visibility,
+ version,
+ } = self;
+ let delegates = Delegates::new(delegates)?;
+ let threshold = Threshold::new(threshold, &delegates)?;
+ Ok(Doc {
+ payload,
+ delegates,
+ threshold,
+ visibility,
+ version,
+ })
+ }
+}
+
+/// `Doc` is a valid identity document.
+///
+/// To ensure that only valid documents are used, this type is restricted to be
+/// read-only. For mutating the document use [`Doc::edit`].
+///
+/// A valid `Doc` can be constructed in four ways:
+///
+/// 1. [`Doc::initial`]: a safe way to construct the initial document for an identity.
+/// 2. [`RawDoc::verified`]: validates a [`RawDoc`]'s fields and converts it
+/// into a `Doc`
+/// 3. [`Doc::from_blob`]: construct a `Doc` from a Git blob by deserializing
+/// its contents.
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Doc {
+ #[serde(skip_serializing)]
+ version: VersionOne,
+ payload: BTreeMap<PayloadId, Payload>,
+ delegates: Delegates,
+ threshold: Threshold,
+ #[serde(default, skip_serializing_if = "Visibility::is_public")]
+ visibility: Visibility,
+}
+
+impl TryFrom<RawDoc> for Doc {
+ type Error = DocError;
+
+ fn try_from(doc: RawDoc) -> Result<Self, Self::Error> {
+ doc.verified()
+ }
+}
+
+#[derive(Debug, Error)]
+pub enum MigrationError {
+ #[error(transparent)]
+ Pattern(#[from] rules::PatternError),
+ #[error(transparent)]
+ Payload(#[from] PayloadError),
+ #[error(transparent)]
+ ValidationError(#[from] ValidationError),
+}
+
+impl Doc {
+ /// Automatically migrate the `v1` `Doc` to the latest [`super::Doc`] version.
+ ///
+ /// This can be used to get the latest version in a verified state for
+ /// proposing as identity document change.
+ ///
+ /// This migrations handles:
+ ///
+ /// * v1 -> v2:
+ /// - Add a canonical reference rule for the default branch.
+ /// - Add the [`Version`] field
+ /// - Removes the `threshold`, but uses it in the above rule.
+ pub fn migrate(self) -> Result<super::Doc, MigrationError> {
+ let project = self.project()?;
+ let Doc {
+ version: _version,
+ payload,
+ delegates,
+ threshold,
+ visibility,
+ } = self;
+
+ let rules = match project {
+ Some(project) => {
+ let refname = git::refspec::QualifiedPattern::from(git::refs::branch(
+ project.default_branch(),
+ ))
+ .to_owned();
+ let rules = [(
+ rules::Pattern::try_from(refname)?,
+ Rule::new(rules::Allowed::Delegates, threshold.into()),
+ )]
+ .into_iter()
+ .collect::<RawRules>();
+ // N.b. we always return the `delegates`, since we know we're
+ // using the `Identity` marker.
+ Rules::from_raw(rules, &mut |_| Ok(Delegates::new(delegates.clone())?))?
+ }
+ None => Rules::default(),
+ };
+
+ Ok(super::Doc {
+ version: super::VersionTwo,
+ payload,
+ delegates,
+ canonical_refs: CanonicalRefs::new(rules),
+ visibility,
+ })
+ }
+
+ /// Get the project payload, if it exists and is valid, out of this document.
+ fn project(&self) -> Result<Option<Project>, PayloadError> {
+ match self.payload.get(&PayloadId::project()) {
+ Some(value) => serde_json::from_value((**value).clone())
+ .map_err(PayloadError::from)
+ .map(Some),
+ None => Ok(None),
+ }
+ }
+}
+
+#[cfg(test)]
+#[allow(clippy::unwrap_used)]
+mod test {
+ use crate::identity::VersionedRawDoc;
+
+ use super::*;
+
+ use nonempty::NonEmpty;
+ use serde_json::json;
+
+ #[test]
+ fn test_parse_version() {
+ // Harcoded version 1 of the identity document. We expect that parsing
+ // this will include the version.
+ let v1 = json!(
+ {
+ "payload": {
+ "xyz.radicle.project": {
+ "defaultBranch": "master",
+ "description": "Radicle Heartwood Protocol & Stack",
+ "name": "heartwood"
+ }
+ },
+ "delegates": [
+ "did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
+ "did:key:z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW",
+ "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM"
+ ],
+ "threshold": 1
+ }
+ );
+
+ // Deserializing the v1 document to the current version of the mutable
+ // document should not fail.
+ let doc = serde_json::from_str::<RawDoc>(&v1.to_string()).unwrap();
+ let payload = [(
+ PayloadId::project(),
+ Payload {
+ value: json!({
+ "name": "heartwood",
+ "description": "Radicle Heartwood Protocol & Stack",
+ "defaultBranch": "master",
+ }),
+ },
+ )]
+ .into_iter()
+ .collect::<BTreeMap<_, _>>();
+ let delegates = vec![
+ "did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT"
+ .parse::<Did>()
+ .unwrap(),
+ "did:key:z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW"
+ .parse::<Did>()
+ .unwrap(),
+ "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM"
+ .parse::<Did>()
+ .unwrap(),
+ ];
+
+ let expected_doc = RawDoc {
+ version: VersionOne,
+ payload: payload.clone(),
+ delegates: delegates.clone(),
+ threshold: 1,
+ visibility: Visibility::Public,
+ };
+
+ // And this is the expected outcome of the deserialization
+ assert_eq!(doc, expected_doc);
+
+ // Deserializing into the verified document should also succeed.
+ let doc = serde_json::from_str::<RawDoc>(&v1.to_string()).unwrap();
+ let verified = doc.verified().unwrap();
+ let delegates = Delegates(NonEmpty::from_vec(delegates).unwrap());
+ assert_eq!(
+ verified,
+ Doc {
+ version: VersionOne,
+ threshold: Threshold::new(1, &delegates).unwrap(),
+ payload: payload.clone(),
+ delegates,
+ visibility: Visibility::Public,
+ }
+ );
+
+ let versioned = serde_json::from_str::<VersionedRawDoc>(&v1.to_string()).unwrap();
+ assert_eq!(versioned, VersionedRawDoc::V1(expected_doc));
+ }
+}
diff --git a/radicle/src/identity/doc/version.rs b/radicle/src/identity/doc/version.rs
new file mode 100644
index 000000000..73d3e8bc7
--- /dev/null
+++ b/radicle/src/identity/doc/version.rs
@@ -0,0 +1,109 @@
+use std::num::NonZeroU64;
+use std::ops::RangeInclusive;
+
+use serde::{Deserialize, Serialize};
+use std::fmt::Display;
+use thiserror::Error;
+
+/// A [`RangeInclusive`] of all valid identity document versions, known to this
+/// release of the protocol.
+pub const KNOWN_VERSIONS: RangeInclusive<Version> = Version::MIN..=Version::LATEST;
+
+/// The version number of the repository identity documents.
+///
+/// The number cannot be zero and will range from `1` to the maximum known
+/// version number for this given release. This range can be retrieved from the
+/// [`KNOWN`] range.
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Deserialize, Serialize)]
+pub struct Version(NonZeroU64);
+
+impl Version {
+ /// The minimum `Version` is version `1`.
+ pub const MIN: Version = Version(unsafe { NonZeroU64::new_unchecked(1) });
+
+ /// The current latest `Version` is version `2`.
+ pub const LATEST: Version = Version(unsafe { NonZeroU64::new_unchecked(2) });
+
+ /// Get the latest known `Version`.
+ pub const fn latest() -> Version {
+ Self::LATEST
+ }
+}
+
+impl Display for Version {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl From<Version> for u64 {
+ fn from(version: Version) -> Self {
+ version.0.into()
+ }
+}
+
+#[derive(Debug, Error)]
+pub enum VersionError {
+ #[error("encountered unexpected identity document version {actual}, expected {expected}")]
+ Unexpected { expected: Version, actual: Version },
+}
+
+impl VersionError {
+ /// Provide a verbose error.
+ ///
+ /// This will give a user more information on how to upgrade to a newer
+ /// version of an identity document, if there is one.
+ pub fn verbose(&self) -> String {
+ const UNKOWN_VERSION_ERROR: &str = r#"
+Perhaps a new version of the identity document is released which is not supported by the current client.
+See https://radicle.xyz for the latest versions of Radicle.
+The CLI command `rad id migrate` will help to migrate to an up-to-date versions."#;
+
+ format!("{self}{UNKOWN_VERSION_ERROR}")
+ }
+}
+
+macro_rules! version {
+ ($def: expr, $name: ident) => {
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+ #[serde(try_from = "Version", into = "Version")]
+ pub struct $name;
+
+ impl TryFrom<Version> for $name {
+ type Error = VersionError;
+
+ fn try_from(value: Version) -> Result<Self, Self::Error> {
+ if value == $def {
+ Ok(Self)
+ } else {
+ Err(VersionError::Unexpected {
+ expected: $def,
+ actual: value,
+ })
+ }
+ }
+ }
+
+ impl From<$name> for Version {
+ fn from(_: $name) -> Self {
+ $def
+ }
+ }
+
+ impl std::fmt::Display for $name {
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ $def.fmt(formatter)
+ }
+ }
+ };
+}
+
+version!(*KNOWN_VERSIONS.start(), VersionOne);
+version!(*KNOWN_VERSIONS.end(), VersionTwo);
+
+impl Default for VersionOne {
+ fn default() -> Self {
+ debug_assert_eq!(Into::<Version>::into(Self), *KNOWN_VERSIONS.start());
+ Self
+ }
+}
diff --git a/radicle/src/rad.rs b/radicle/src/rad.rs
index ac4b18959..0609323c3 100644
--- a/radicle/src/rad.rs
+++ b/radicle/src/rad.rs
@@ -9,6 +9,7 @@ use thiserror::Error;
use crate::cob::ObjectId;
use crate::crypto::{Signer, Verified};
use crate::git;
+use crate::git::canonical::rules;
use crate::identity::doc;
use crate::identity::doc::{DocError, RepoId, Visibility};
use crate::identity::project::{Project, ProjectName};
@@ -35,6 +36,8 @@ pub enum InitError {
BareRepository { path: PathBuf },
#[error("doc: {0}")]
Doc(#[from] DocError),
+ #[error("rule pattern: {0}")]
+ Pattern(#[from] rules::PatternError),
#[error("repository: {0}")]
Repository(#[from] RepositoryError),
#[error("project payload: {0}")]
@@ -72,7 +75,7 @@ pub fn init<G: Signer, S: WriteStorage>(
.join(", "),
)
})?;
- let doc = identity::Doc::initial(proj, delegate, visibility);
+ let doc = identity::Doc::initial(proj, delegate, visibility)?;
let (project, identity) = Repository::init(&doc, &storage, signer)?;
let url = git::Url::from(project.id);
diff --git a/radicle/src/storage.rs b/radicle/src/storage.rs
index 17d2fcdeb..247e17a41 100644
--- a/radicle/src/storage.rs
+++ b/radicle/src/storage.rs
@@ -119,6 +119,8 @@ pub enum RepositoryError {
Quorum(#[from] canonical::QuorumError),
#[error(transparent)]
Refs(#[from] refs::Error),
+ #[error("missing canonical reference rule for default branch")]
+ MissingBranchRule,
}
impl RepositoryError {
diff --git a/radicle/src/storage/git.rs b/radicle/src/storage/git.rs
index b51f12aed..bab494712 100644
--- a/radicle/src/storage/git.rs
+++ b/radicle/src/storage/git.rs
@@ -11,7 +11,6 @@ use crypto::{Signer, Verified};
use once_cell::sync::Lazy;
use tempfile::TempDir;
-use crate::git::canonical::Canonical;
use crate::identity::doc::DocError;
use crate::identity::{Doc, DocAt, RepoId};
use crate::identity::{Identity, Project};
@@ -744,13 +743,11 @@ impl ReadRepository for Repository {
fn canonical_head(&self) -> Result<(Qualified, Oid), RepositoryError> {
let doc = self.identity_doc()?;
- let project = doc.project()?;
- let branch_ref = git::refs::branch(project.default_branch());
- let raw = self.raw();
- let oid =
- Canonical::default_branch(self, &project, doc.delegates().as_ref(), doc.threshold())?
- .quorum(raw)?;
- Ok((branch_ref, oid))
+ let rule = doc
+ .default_branch_rule()?
+ .ok_or(RepositoryError::MissingBranchRule)?;
+ let oid = rule.canonical(self)?.quorum(self.raw())?;
+ Ok((rule.refname().clone().to_owned(), oid))
}
fn identity_head(&self) -> Result<Oid, RepositoryError> {
diff --git a/radicle/src/test/arbitrary.rs b/radicle/src/test/arbitrary.rs
index a6dd07708..612b1bb8d 100644
--- a/radicle/src/test/arbitrary.rs
+++ b/radicle/src/test/arbitrary.rs
@@ -11,10 +11,11 @@ use cyphernet::EcPk;
use qcheck::Arbitrary;
use crate::collections::RandomMap;
+use crate::git::canonical::rules::RawRules;
use crate::identity::doc::Visibility;
use crate::identity::project::ProjectName;
use crate::identity::{
- doc::{Doc, DocAt, RawDoc, RepoId},
+ doc::{Doc, DocAt, RawDoc, RepoId, VersionedRawDoc},
project::Project,
Did,
};
@@ -138,13 +139,19 @@ impl Arbitrary for Visibility {
}
}
-impl Arbitrary for RawDoc {
+impl Arbitrary for VersionedRawDoc {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
let proj = Project::arbitrary(g);
let delegate = Did::arbitrary(g);
let visibility = Visibility::arbitrary(g);
- Self::new(proj, vec![delegate], 1, visibility)
+ VersionedRawDoc::V2(RawDoc::new(
+ proj,
+ vec![delegate],
+ 1,
+ RawRules::default(),
+ visibility,
+ ))
}
}
@@ -157,8 +164,13 @@ impl Arbitrary for Doc {
.collect::<Vec<_>>();
let threshold = delegates.len() / 2 + 1;
let visibility = Visibility::arbitrary(g);
- let doc = RawDoc::new(project, delegates, threshold, visibility);
-
+ let doc = RawDoc::new(
+ project,
+ delegates,
+ threshold,
+ RawRules::default(),
+ visibility,
+ );
doc.verified().unwrap()
}
}
commit bbe019c478fc1f5c1f6417efe780a4b703284932
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Tue Jul 9 09:02:22 2024 +0100
radicle: canonical reference rules
Introduce the `rules` module beneath `canonical`. The implementation follows
the Canonical References RIP[^0].
A `Rule` defines the DIDs and threshold required to get the canonical tip
for a reference (or set of references). Since a `Rule` has some specific
validation rules, there can be two versions of a `Rule`. The `RawRule` is one
that can be easily constructed, serialized, and deserialized. It must then be
validated into a `ValidRule`, which validates any constraints, and can be used
in the computing of a canonical tip.
For convenience, a set of `ValidRule`s is wrapped into the `Rules` type, which
also has a further constraint that each rule refers to a specific `refspec`.
[0]: https://app.radicle.xyz/nodes/seed.radicle.garden/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/patches/1d1ce874f7c39ecdcd8c318bbae46ffd02fe1ea8
diff --git a/Cargo.lock b/Cargo.lock
index d3e82220d..1492f4e63 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -692,6 +692,12 @@ dependencies = [
"serde_json",
]
+[[package]]
+name = "fast-glob"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3afcf4effa2c44390b9912544582d5af29e10dc4c816c5dbebf748e1c7416faa"
+
[[package]]
name = "faster-hex"
version = "0.9.0"
@@ -2188,6 +2194,7 @@ dependencies = [
"crossbeam-channel",
"cyphernet",
"emojis",
+ "fast-glob",
"fastrand",
"git2",
"libc",
diff --git a/radicle-cli/examples/rad-cref.md b/radicle-cli/examples/rad-cref.md
new file mode 100644
index 000000000..d7eab7691
--- /dev/null
+++ b/radicle-cli/examples/rad-cref.md
@@ -0,0 +1,156 @@
+The `cref` command helps manage canonical reference rules associated
+with a Radicle repository.
+
+We can list the rules by calling `rad cref` (the `list` subcommand is
+the default):
+
+```
+$ rad cref
+{
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+}
+```
+
+Here we can see that we have a single rule for `refs/heads/master`. Under the
+`allow` key it's using the `delegates` value, which means that it will use the
+delegates of the repository identity. The `threshold` is `1`, so a single vote
+by a delegate is required to make the reference canonical, if there is no
+divergence.
+
+So, let's add another rule through the `add` subcommand:
+
+```
+$ rad cref add refs/heads/dev --delegate did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2 --title "Add canonical reference rule for refs/heads/dev"
+✓ Rule for refs/heads/dev has been added
+✓ Identity revision a92caeb23f9df6f2136c583ef6f01cb81fea853f created
+```
+
+Notice the output for the `Identity revision`. This is because when we update
+the canonical reference rules, the identity is updated since that's where they
+are stored. In this case, there is a single delegate so these updates will be
+accepted automatically. If there were more delegates then they would require a
+majority to agree to the changes.
+
+We can also note that we added the delegate
+`did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk` to the rule. This
+peer is not a delegate of the repository, but their commit state for
+`refs/heads/dev` is taken into account when computing the canonical reference.
+This means that we can collaborate with peers that we trust on certain
+references but may not want to trust them on all changes made to the project.
+
+Let's check that our rule was successfully added:
+
+```
+$ rad cref
+{
+ "refs/heads/dev": {
+ "allow": [
+ "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
+ "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
+ ],
+ "threshold": 2
+ },
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+}
+```
+
+The reference passed to the command can also use the `*` path component to match
+multiple references. The value must follow the format of [git-check-ref-format].
+So, let's add a rule for `refs/tags/releases/*`:
+
+```
+$ rad cref add refs/tags/releases/* --title "Add canonical reference rule for refs/tags/releases/*"
+✓ Rule for refs/tags/releases/* has been added
+✓ Identity revision a880b05441f00cc90bc7bae76e0f1ef16b73daf1 created
+```
+
+Here, we didn't specify the `--delegate` or `--threshold` options. This means
+the defaults of `delegates` and `1` will be used. So let's check that this is
+true:
+
+```
+$ rad cref
+{
+ "refs/tags/releases/*": {
+ "allow": "delegates",
+ "threshold": 1
+ },
+ "refs/heads/dev": {
+ "allow": [
+ "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
+ "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
+ ],
+ "threshold": 2
+ },
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+}
+```
+
+We can also remove rules using the `remove` subcommand and specifying the
+`refspec` we would like to remove:
+
+```
+$ rad cref remove refs/heads/dev --title "Remove rule for refs/heads/dev"
+✓ Rule for refs/heads/dev has been removed
+✓ Identity revision db01c9c642c6cb9c870269b1f2863a5bed624df0 created
+```
+
+Let's check that we only have 2 rules now:
+
+```
+$ rad cref
+{
+ "refs/tags/releases/*": {
+ "allow": "delegates",
+ "threshold": 1
+ },
+ "refs/heads/master": {
+ "allow": "delegates",
+ "threshold": 1
+ }
+}
+```
+
+The `refspec` value must be a fully-qualified reference name. That is, it must
+start with `refs`, followed by the category (e.g. `refs/tags`), and the suffix.
+Here we show that it will fail if this is not the case:
+
+``` (fails)
+$ rad cref add tags/v1.0 --title "Add tags/v1.0"
+✗ Error: rad cref: 'tags/v1.0' is a not a qualified refspec string
+```
+
+When we try to make changes that do not change the rule set, then we report that
+nothing happened. Let's try remove a rule that doesn't exist:
+
+```
+$ rad cref remove refs/heads/main --title "Remove refs/heads/main"
+Nothing to do. The rules are up to date. See `rad cref list`.
+```
+
+Finally, rules are verified according to the RIP. In this case we'll add a rule
+with a single delegate but a threshold of 2:
+
+``` (fails)
+$ rad cref add refs/tags/* --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2 --title "Add canonical reference rule for refs/tags/*"
+✗ Error: invalid threshold `2`: threshold cannot exceed number of delegates
+```
+
+Finally, we refuse to add the rule for `refs/rad/id` since this is a special
+reference:
+
+``` (fails)
+$ rad cref add refs/rad/id --title "Try add refs/rad/id"
+✗ Error: rad cref: cannot create rule for 'refs/rad/id' since references under 'refs/rad' are reserved
+```
+
+[git-check-ref-format]: https://git-scm.com/docs/git-check-ref-format
diff --git a/radicle/Cargo.toml b/radicle/Cargo.toml
index 2eb35264f..be523a006 100644
--- a/radicle/Cargo.toml
+++ b/radicle/Cargo.toml
@@ -34,6 +34,7 @@ sqlite = { version = "0.32.0", features = ["bundled"] }
tempfile = { version = "3.3.0" }
thiserror = { version = "1" }
unicode-normalization = { version = "0.1" }
+fast-glob = { version = "0.3.2" }
[dependencies.chrono]
version = "0.4.0"
diff --git a/radicle/src/git/canonical.rs b/radicle/src/git/canonical.rs
index e3f8a5711..2b40b5052 100644
--- a/radicle/src/git/canonical.rs
+++ b/radicle/src/git/canonical.rs
@@ -1,3 +1,6 @@
+pub mod rules;
+pub use rules::{MatchedRule, RawRule, Rules, ValidRule};
+
use std::collections::BTreeMap;
use std::fmt;
diff --git a/radicle/src/git/canonical/rules.rs b/radicle/src/git/canonical/rules.rs
new file mode 100644
index 000000000..4cd612de6
--- /dev/null
+++ b/radicle/src/git/canonical/rules.rs
@@ -0,0 +1,1211 @@
+//! Implementation of RIP-0004 Canonical References
+//!
+//! [`RawRules`] is intended to be deserialized and then validated into a set of
+//! [`Rules`]. These can then be used to see if a [`Qualified`] reference
+//! matches any of the rules. Using [`Rules::canonical`] will construct a
+//! [`Canonical`] with the first matched rule, and this can be used to calculate
+//! the [`Canonical::quorum`].
+
+use core::fmt;
+use std::cmp::Ordering;
+use std::collections::BTreeMap;
+
+use nonempty::NonEmpty;
+use once_cell::sync::Lazy;
+use serde::{Deserialize, Serialize};
+use serde_json as json;
+use thiserror::Error;
+
+use crate::git;
+use crate::git::canonical::Canonical;
+use crate::git::fmt::{refname, RefString};
+use crate::git::refspec::QualifiedPattern;
+use crate::git::Qualified;
+use crate::identity::{doc, Did};
+use crate::storage::git::Repository;
+
+const ASTERISK: char = '*';
+
+static REFS_RAD: Lazy<RefString> = Lazy::new(|| refname!("refs/rad"));
+
+/// Private trait to ensure that not any `Rule` can be deserialized.
+/// Implementations are provided for `Allowed` and `usize` so that `RawRule`s
+/// can be deserialized, while `ValidRule`s cannot – preventing deserialization
+/// bugs for that type.
+trait Sealed {}
+impl Sealed for Allowed {}
+impl Sealed for usize {}
+
+/// A `Pattern` is a `QualifiedPattern` reference, however, it disallows any
+/// references under the `refs/rad` hierarchy.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(into = "QualifiedPattern", try_from = "QualifiedPattern")]
+pub struct Pattern(QualifiedPattern<'static>);
+
+impl fmt::Display for Pattern {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(self.0.as_str())
+ }
+}
+
+impl From<Pattern> for QualifiedPattern<'static> {
+ fn from(Pattern(pattern): Pattern) -> Self {
+ pattern
+ }
+}
+
+impl<'a> TryFrom<QualifiedPattern<'a>> for Pattern {
+ type Error = PatternError;
+
+ fn try_from(pattern: QualifiedPattern<'a>) -> Result<Self, Self::Error> {
+ if pattern.starts_with(REFS_RAD.as_str()) {
+ Err(PatternError::ProtectedRef {
+ prefix: (*REFS_RAD).clone(),
+ pattern: pattern.to_owned(),
+ })
+ } else {
+ Ok(Self(pattern.to_owned()))
+ }
+ }
+}
+
+impl<'a> TryFrom<Qualified<'a>> for Pattern {
+ type Error = PatternError;
+
+ fn try_from(name: Qualified<'a>) -> Result<Self, Self::Error> {
+ Self::try_from(QualifiedPattern::from(name))
+ }
+}
+
+impl Pattern {
+ /// Check if the `refname` matches the rule's `refspec`.
+ pub fn matches(&self, refname: &Qualified) -> bool {
+ // N.b. Git's refspecs do not quite match with glob-star semantics. A
+ // single `*` in a refspec is expected to match all references under
+ // that namespace, even if they are further down the hierarchy.
+ // Thus, the following rules are applied:
+ //
+ // - a trailing `*` changes to `**/*`
+ // - a `*` in between path components changes to `**`
+ let spec = match self.0.as_str().split_once(ASTERISK) {
+ None => self.0.to_string(),
+ // Expand `refs/tags/*` to `refs/tags/**/*`
+ Some((prefix, "")) => {
+ let mut spec = prefix.to_string();
+ spec.push_str("**/*");
+ spec
+ }
+ // Expand `refs/tags/*/v1.0` to `refs/tags/**/v1.0`
+ Some((prefix, suffix)) => {
+ let mut spec = prefix.to_string();
+ spec.push_str("**");
+ spec.push_str(suffix);
+ spec
+ }
+ };
+ fast_glob::glob_match(&spec, refname.as_str())
+ }
+}
+
+impl AsRef<QualifiedPattern<'static>> for Pattern {
+ fn as_ref(&self) -> &QualifiedPattern<'static> {
+ &self.0
+ }
+}
+
+impl PartialOrd for Pattern {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+/// Patterns are ordered by their specificity.
+///
+/// This is heavily influenced by the evaluation priority of Rules. For a
+/// candidate reference name, we want the rule associated with the most specific
+/// pattern to apply, i.e. to take priority over all other rules with less
+/// specific patterns.
+///
+/// For two patterns `φ` and `ψ`, we say that "`φ` is more specific than `ψ`", denoted
+/// `φ < ψ` if:
+///
+/// 1. The number of components in `φ` is larger than the number of components
+/// in `ψ`. (Note that the number of components is equal to the number of
+/// occurrences of the symbol '/' in the pattern, plus 1).
+/// The justification is, that refnames might be interpreted as a hierarchy
+/// where a match on more components would mean a match at a lower level in
+/// the hierarchy, thus being more specific.
+/// Imagine a refname hierarchy that maps to a corporate hierarchy.
+/// The pattern "department-1" matches all refnames that are administered
+/// by a particular department, and thus is not very specific.
+/// To contrast, the pattern "department-1/team-a/project-i/nice-feature"
+/// is very specific as it matches all refnames that relate to the
+/// development of a particular feature for a particular project by a
+/// particular team.
+/// Note that this would also apply when the connection between the `φ` and `ψ`
+/// is not as obvious, e.g. also `a/b/c/d/* < */x`.
+///
+/// (Note that for the following items, one may assume that `φ` and `ψ` have the
+/// same number of components.)
+///
+/// 2. If path component i of `φ`, denoted `φ[i]`, is more specific than path
+/// component i of `ψ`, denoted `ψ[i]`. This is the case if:
+/// a. `φ[i]` does not contain an asterisk and `ψ[i]` contains an asterisk,
+/// i.e. the symbol `*`, e.g. `a < * and abc < a*`.
+/// Note that this is important to capture specificity accross
+/// components, i.e. to conclude that `a/b/* < a/*/c`.
+/// b. Both `φ[i]` and `ψ[i]` contain an asterisk.
+/// A. The asterisk in `φ[i]` is further right than the asterisk in `φ[i]`,
+/// e.g. `aa* < a*`.
+/// B. The asterisk in `φ[i]` and `ψ[i]` is equally far to the right,
+/// and `φ[i]` is longer than `ψ[i]`, e.g. `a*b < a*`.
+///
+/// 3. Otherwise, fall back to a lexicographic ordering.
+///
+/// Some examples (justification in parentheses):
+///
+/// ```text, no_run
+/// refs/tags/release/candidates/* <(1.) refs/tags/release/* <(1.) refs/tags/*
+/// refs/tags/v1.0 <(2.a.) refs/tags/*
+/// refs/heads/* <(3.) refs/tags/*
+/// refs/heads/main <(3.) refs/tags/v1.0
+/// ```
+impl Ord for Pattern {
+ fn cmp(&self, other: &Self) -> Ordering {
+ #[derive(Debug, Clone, Copy)]
+ #[repr(i8)]
+ enum ComponentOrdering {
+ MatchLength(Ordering),
+ Lexicographic(Ordering),
+ }
+
+ impl ComponentOrdering {
+ fn merge(&mut self, other: Self) {
+ *self = match (*self, other) {
+ (Self::Lexicographic(Ordering::Equal), Self::Lexicographic(other)) => {
+ Self::Lexicographic(other)
+ }
+ (Self::Lexicographic(_), Self::MatchLength(other)) => Self::MatchLength(other),
+ (Self::MatchLength(Ordering::Equal), Self::MatchLength(other)) => {
+ Self::MatchLength(other)
+ }
+ (clone, _) => clone,
+ }
+ }
+ }
+
+ impl From<ComponentOrdering> for Ordering {
+ fn from(value: ComponentOrdering) -> Self {
+ match value {
+ ComponentOrdering::MatchLength(ordering) => ordering,
+ ComponentOrdering::Lexicographic(ordering) => ordering,
+ }
+ }
+ }
+
+ impl Default for ComponentOrdering {
+ /// The weakest value of Self, which will be absorbed by any
+ /// other in [`ComponentOrdering::merge`].
+ fn default() -> Self {
+ Self::Lexicographic(Ordering::Equal)
+ }
+ }
+
+ use git::refspec::Component;
+
+ fn cmp_component(lhs: Component<'_>, rhs: Component<'_>) -> ComponentOrdering {
+ let (l, r) = (lhs.as_str(), rhs.as_str());
+ match (l.find(ASTERISK), r.find(ASTERISK)) {
+ // (2.a.)
+ (Some(_), None) => ComponentOrdering::MatchLength(Ordering::Greater),
+ // (2.a.)
+ (None, Some(_)) => ComponentOrdering::MatchLength(Ordering::Less),
+ (Some(li), Some(ri)) => {
+ if li != ri {
+ // (2.b.A)
+ ComponentOrdering::MatchLength(li.cmp(&ri).reverse())
+ } else if l.len() != r.len() {
+ // (2.b.B)
+ ComponentOrdering::MatchLength(l.len().cmp(&r.len()).reverse())
+ } else {
+ // (3.)
+ ComponentOrdering::Lexicographic(l.cmp(r))
+ }
+ }
+ // (3.)
+ (None, None) => ComponentOrdering::Lexicographic(l.cmp(r)),
+ }
+ }
+
+ let mut result = ComponentOrdering::default();
+ let mut lhs = self.0.components();
+ let mut rhs = other.0.components();
+ loop {
+ match (lhs.next(), rhs.next()) {
+ (None, Some(_)) => return Ordering::Greater, // (1.)
+ (Some(_), None) => return Ordering::Less, // (1.)
+ (Some(lhs), Some(rhs)) => {
+ result.merge(cmp_component(lhs, rhs));
+ }
+ (None, None) => return result.into(),
+ }
+ }
+ }
+}
+
+/// A [`Rule`] that can be serialized and deserialized safely.
+///
+/// Should be converted to a [`ValidRule`] via [`Rule::validate`].
+pub type RawRule = Rule<Allowed, usize>;
+
+impl RawRule {
+ /// Validate the `Rule` into a form that can be used for calculating
+ /// canonical references.
+ ///
+ /// The `resolve` function is used to get the set of DIDs by inspecting the
+ /// [`Allowed`] value. In most cases, if it is [`Allowed::Delegates`] then
+ /// the closure will resolve the DIDs from the identity document, and if it
+ /// is [`Allowed::Set`] it will validate the set.
+ pub fn validate<R>(self, resolve: &mut R) -> Result<ValidRule, ValidationError>
+ where
+ R: Fn(Allowed) -> Result<doc::Delegates, ValidationError>,
+ {
+ let Self {
+ allow: delegates,
+ threshold,
+ ..
+ } = self;
+ let allow = match &delegates {
+ Allowed::Delegates => ResolvedDelegates::Delegates(resolve(delegates)?),
+ Allowed::Set(_) => ResolvedDelegates::Set(resolve(delegates)?),
+ };
+ let threshold = doc::Threshold::new(threshold, &allow)?;
+ Ok(Rule {
+ allow,
+ threshold,
+ extensions: self.extensions,
+ })
+ }
+}
+
+/// A set of `RawRule`s that can be serialized and deserialized.
+#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub struct RawRules {
+ /// The reference pattern that this rule applies to.
+ ///
+ /// Note that this can be a fully-qualified pattern, e.g. `refs/heads/qa`,
+ /// as well as a wild-card pattern, e.g. `refs/tags/*`.
+ #[serde(flatten)]
+ pub rules: BTreeMap<Pattern, RawRule>,
+}
+
+impl RawRules {
+ /// Returns an iterator over the [`Pattern`] and [`RawRule`] in the set of
+ /// rules.
+ pub fn iter(&self) -> impl Iterator<Item = (&Pattern, &RawRule)> {
+ self.rules.iter()
+ }
+
+ /// Add a new [`RawRule`] to the set of rules.
+ ///
+ /// Returns the replaced rule, if it existed.
+ pub fn insert(&mut self, pattern: Pattern, rule: RawRule) -> Option<RawRule> {
+ self.rules.insert(pattern, rule)
+ }
+
+ /// Remove the rule that matches the `pattern` parameter.
+ ///
+ /// Returns the rule if it existed.
+ pub fn remove(&mut self, pattern: &Pattern) -> Option<RawRule> {
+ self.rules.remove(pattern)
+ }
+
+ /// Check to see if there is an exact match for `refname` in the rules.
+ pub fn exact_match(&self, refname: &Qualified) -> bool {
+ let refname = refname.as_str();
+ self.rules
+ .iter()
+ .any(|(pattern, _)| pattern.0.as_str() == refname)
+ }
+
+ /// Check if the `refname` matches any existing rules, including glob
+ /// matches.
+ pub fn matches(&self, refname: &Qualified) -> bool {
+ self.rules
+ .iter()
+ .any(|(pattern, _)| pattern.matches(refname))
+ }
+}
+
+impl Extend<(Pattern, RawRule)> for RawRules {
+ fn extend<T: IntoIterator<Item = (Pattern, RawRule)>>(&mut self, iter: T) {
+ self.rules.extend(iter)
+ }
+}
+
+impl From<BTreeMap<Pattern, RawRule>> for RawRules {
+ fn from(rules: BTreeMap<Pattern, RawRule>) -> Self {
+ RawRules { rules }
+ }
+}
+
+impl FromIterator<(Pattern, RawRule)> for RawRules {
+ fn from_iter<T: IntoIterator<Item = (Pattern, RawRule)>>(iter: T) -> Self {
+ iter.into_iter().collect::<BTreeMap<_, _>>().into()
+ }
+}
+
+impl IntoIterator for RawRules {
+ type Item = (Pattern, RawRule);
+ type IntoIter = std::collections::btree_map::IntoIter<Pattern, RawRule>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.rules.into_iter()
+ }
+}
+
+/// A [`Rule`] that has been validated. See [`Rules`] and [`Rules::matches`] for
+/// its main usage.
+///
+/// N.b. a `ValidRule` can be serialized, however, it cannot be deserialized.
+/// This is due to the fact that the `allow` field may have a value of
+/// `delegates`. In those cases the value needs to be looked up via the identity
+/// document and validated.
+pub type ValidRule = Rule<ResolvedDelegates, doc::Threshold>;
+
+impl ValidRule {
+ /// Initialize a `ValidRule` for the default branch, given by `name`. The
+ /// rule will contain the single `did` as the allowed DID, and use a
+ /// threshold of `1`.
+ ///
+ /// Note that the serialization of the rule will use the `delegates` token
+ /// for the rule. E.g.
+ /// ```json, no_run
+ /// {
+ /// "pattern": "refs/heads/main",
+ /// "allow": ["did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"],
+ /// "threshold": 1
+ /// }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// If the `name` reference begins with `refs/rad`.
+ pub fn default_branch(did: Did, name: &git::RefStr) -> Result<(Pattern, Self), PatternError> {
+ let pattern = Pattern::try_from(git::refs::branch(name).to_owned())?;
+ let rule = Self {
+ allow: ResolvedDelegates::Delegates(doc::Delegates::from(did)),
+ // N.B. this needs to be the minimum since we only have one
+ // delegate.
+ threshold: doc::Threshold::MIN,
+ extensions: json::Map::new(),
+ };
+ Ok((pattern, rule))
+ }
+}
+
+impl From<ValidRule> for RawRule {
+ fn from(rule: ValidRule) -> Self {
+ let Rule {
+ allow,
+ threshold,
+ extensions,
+ } = rule;
+ Self {
+ allow: allow.into(),
+ threshold: threshold.into(),
+ extensions,
+ }
+ }
+}
+
+/// A representation of a set of allowed DIDs.
+///
+/// `Allowed` is used in a `RawRule`.
+#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub enum Allowed {
+ /// Pointer to the identity document's set of delegates.
+ #[serde(rename = "delegates")]
+ #[default]
+ Delegates,
+ /// Explicit set of allowed DIDs.
+ ///
+ /// # Validation
+ ///
+ /// The set of allowed DIDs must be:
+ /// - Unique
+ /// - `1 <= delegates.len() <= 255`
+ #[serde(untagged)]
+ Set(NonEmpty<Did>),
+}
+
+impl From<NonEmpty<Did>> for Allowed {
+ fn from(dids: NonEmpty<Did>) -> Self {
+ Self::Set(dids)
+ }
+}
+
+impl From<Did> for Allowed {
+ fn from(did: Did) -> Self {
+ Self::Set(NonEmpty::new(did))
+ }
+}
+
+/// A marker `enum` that is used in a [`ValidRule`].
+///
+/// It ensures that a rule that has been deserialized, resolving the `delegates`
+/// token to a set of DIDs, is still serialized back to the `delegates` token –
+/// as opposed to serializing it to the set of DIDs.
+///
+/// The variants mirror the [`Allowed::Delegates`] and [`Allowed::Set`]
+/// variants.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+#[serde(into = "Allowed")]
+pub enum ResolvedDelegates {
+ Delegates(doc::Delegates),
+ Set(doc::Delegates),
+}
+
+impl From<ResolvedDelegates> for Allowed {
+ fn from(ds: ResolvedDelegates) -> Self {
+ match ds {
+ ResolvedDelegates::Delegates(_) => Self::Delegates,
+ ResolvedDelegates::Set(ds) => Self::Set(ds.into()),
+ }
+ }
+}
+
+impl std::ops::Deref for ResolvedDelegates {
+ type Target = doc::Delegates;
+
+ fn deref(&self) -> &Self::Target {
+ match self {
+ ResolvedDelegates::Delegates(ds) => ds,
+ ResolvedDelegates::Set(ds) => ds,
+ }
+ }
+}
+
+/// A reference that has been matched against a [`ValidRule`].
+///
+/// Can be constructed by using [`Rules::matches`].
+#[derive(Debug)]
+pub struct MatchedRule<'a> {
+ refname: Qualified<'a>,
+ rule: ValidRule,
+}
+
+impl MatchedRule<'_> {
+ /// Return the reference name that was used for checking if it was a match.
+ pub fn refname(&self) -> &Qualified {
+ &self.refname
+ }
+
+ /// Return the rule that was matched.
+ pub fn rule(&self) -> &ValidRule {
+ &self.rule
+ }
+
+ /// Return the allowed DIDs for the matched rule.
+ pub fn allowed(&self) -> &doc::Delegates {
+ self.rule().allowed()
+ }
+
+ /// Return the [`doc::Threshold`] for the matched rule.
+ pub fn threshold(&self) -> &doc::Threshold {
+ self.rule().threshold()
+ }
+
+ /// Return the [`Canonical`] representation for the matched rule.
+ pub fn canonical(&self, repo: &Repository) -> Result<Canonical, git::raw::Error> {
+ Canonical::reference(
+ repo,
+ &self.refname,
+ self.rule.allow.as_ref(),
+ self.rule.threshold.into(),
+ )
+ }
+}
+
+/// A set of valid [`Rule`]s, where the set of DIDs and threshold are fully
+/// resolved and valid. Since the rules are constructed via a `BTreeMap`, they
+/// cannot be duplicated.
+///
+/// To construct the set of rules, use [`Rules::from_raw`], which validates a
+/// set of [`RawRule`]s, and their [`Pattern`] references, into a set of
+/// [`ValidRule`]s.
+///
+/// The `Rules` can then be used to construct a [`Canonical`] by providing a
+/// [`Qualified`] reference to [`Rules::canonical`], returning the [`Canonical`]
+/// for the first matched rule.
+#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
+pub struct Rules {
+ #[serde(flatten)]
+ rules: BTreeMap<Pattern, ValidRule>,
+}
+
+impl FromIterator<(Pattern, ValidRule)> for Rules {
+ fn from_iter<T: IntoIterator<Item = (Pattern, ValidRule)>>(iter: T) -> Self {
+ Self {
+ rules: iter.into_iter().collect(),
+ }
+ }
+}
+
+impl<'a> IntoIterator for &'a Rules {
+ type Item = (&'a Pattern, &'a ValidRule);
+ type IntoIter = std::collections::btree_map::Iter<'a, Pattern, ValidRule>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.rules.iter()
+ }
+}
+
+impl IntoIterator for Rules {
+ type Item = (Pattern, ValidRule);
+ type IntoIter = std::collections::btree_map::IntoIter<Pattern, ValidRule>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.rules.into_iter()
+ }
+}
+
+impl From<Rules> for RawRules {
+ fn from(Rules { rules }: Rules) -> Self {
+ Self {
+ rules: rules
+ .into_iter()
+ .map(|(pattern, rule)| (pattern, rule.into()))
+ .collect(),
+ }
+ }
+}
+
+impl Rules {
+ /// Returns an iterator over the [`Pattern`] and [`ValidRule`] in the set of
+ /// rules.
+ pub fn iter(&self) -> impl Iterator<Item = (&Pattern, &ValidRule)> {
+ self.rules.iter()
+ }
+
+ /// Returns `true` is the set of rules is empty.
+ pub fn is_empty(&self) -> bool {
+ self.rules.is_empty()
+ }
+
+ /// Construct a set of `Rules` given a set of `RawRule`s.
+ pub fn from_raw<R>(
+ rules: impl IntoIterator<Item = (Pattern, RawRule)>,
+ resolve: &mut R,
+ ) -> Result<Self, ValidationError>
+ where
+ R: Fn(Allowed) -> Result<doc::Delegates, ValidationError>,
+ {
+ let valid = rules
+ .into_iter()
+ .map(|(pattern, rule)| rule.validate(resolve).map(|rule| (pattern, rule)))
+ .collect::<Result<_, _>>()?;
+ Ok(Self { rules: valid })
+ }
+
+ /// Return the first matching rule for the given `refname`, if there is any.
+ ///
+ /// N.b. it will find the first rule that is most specific for the given
+ /// `refname`.
+ pub fn matches<'a>(&self, refname: Qualified<'a>) -> Option<MatchedRule<'a>> {
+ self.rules
+ .iter()
+ .find(|(pattern, _)| pattern.matches(&refname))
+ .map(|(_, rule)| MatchedRule {
+ refname,
+ rule: rule.clone(),
+ })
+ }
+}
+
+/// A `Rule` defines how a reference or set of references can be made canonical,
+/// i.e. have a top-level `refs/*` entry – see [`Pattern`].
+///
+/// The [`Rule::allowed`] type is generic to allow for [`Allowed`] to be used
+/// for serialization and deserialization, however, the use of
+/// [`Rule::validate`] should be used to get a valid rule.
+///
+/// The [`Rule::threshold`], similarly, allows for [`doc::Threshold`] to be used, and
+/// [`Rule::validate`] should be used to get a valid rule.
+// N.b. it's safe to derive `Serialize` since we only allow constructing a
+// `Rule` via `Rule::validate`, and we seal `Deserialize` by ensuring that only
+// `RawRule` can be deserialized.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(bound(deserialize = "D: Sealed + Deserialize<'de>, T: Sealed + Deserialize<'de>"))]
+pub struct Rule<D, T> {
+ /// The set of delegates that are considered for voting for this rule.
+ allow: D,
+ /// The threshold the votes must pass for the reference(s) to be considered
+ /// canonical.
+ threshold: T,
+
+ /// Optional extensions in rules. This is intended to preserve backwards and
+ /// forward-compatibility
+ #[serde(skip_serializing_if = "json::Map::is_empty")]
+ #[serde(flatten)]
+ extensions: json::Map<String, json::Value>,
+}
+
+impl<D, T> Rule<D, T> {
+ /// Construct a new `Rule` with the given `refspec`, `delegates`, and
+ /// `threshold`.
+ pub fn new(allow: D, threshold: T) -> Self {
+ Self {
+ allow,
+ threshold,
+ extensions: json::Map::new(),
+ }
+ }
+
+ /// Get the set of DIDs this `Rule` was created with.
+ pub fn allowed(&self) -> &D {
+ &self.allow
+ }
+
+ /// Get the set of threshold this `Rule` was created with.
+ pub fn threshold(&self) -> &T {
+ &self.threshold
+ }
+
+ /// Get the extensions that may have been added to this `Rule`.
+ pub fn extensions(&self) -> &json::Map<String, json::Value> {
+ &self.extensions
+ }
+
+ /// If the [`Rule::extensions`] is not set, the provided `extensions` will
+ /// be used.
+ ///
+ /// Otherwise, it expects that the JSON value is a `Map` and the
+ /// `extensions` are merged. If the existing value is any other kind of JSON
+ /// value, this is a no-op.
+ pub fn add_extensions(&mut self, extensions: impl Into<json::Map<String, json::Value>>) {
+ self.extensions.extend(extensions.into());
+ }
+}
+
+#[derive(Debug, Error)]
+pub enum PatternError {
+ #[error("cannot create rule for '{pattern}' since references under '{prefix}' are protected")]
+ ProtectedRef {
+ prefix: RefString,
+ pattern: QualifiedPattern<'static>,
+ },
+}
+
+#[derive(Debug, Error)]
+pub enum ValidationError {
+ #[error(transparent)]
+ Threshold(#[from] doc::ThresholdError),
+ #[error(transparent)]
+ Delegates(#[from] doc::DelegatesError),
+ #[error("cannot create rule for reserved `rad` references '{pattern}'")]
+ RadRef { pattern: QualifiedPattern<'static> },
+}
+
+#[derive(Debug, Error)]
+pub enum CanonicalError {
+ #[error(transparent)]
+ Git(#[from] git::raw::Error),
+ #[error(transparent)]
+ References(#[from] git::ext::Error),
+}
+
+#[cfg(test)]
+#[allow(clippy::unwrap_used)]
+mod tests {
+ use std::collections::BTreeMap;
+
+ use nonempty::nonempty;
+
+ use crate::crypto::{test::signer::MockSigner, Signer};
+ use crate::git;
+ use crate::git::refspec::qualified_pattern;
+ use crate::git::RefString;
+ use crate::identity::doc::Doc;
+ use crate::identity::Visibility;
+ use crate::rad;
+ use crate::storage::refs::{IDENTITY_BRANCH, IDENTITY_ROOT, SIGREFS_BRANCH};
+ use crate::storage::{git::transport, ReadStorage};
+ use crate::test::{arbitrary, fixtures};
+ use crate::Storage;
+
+ use super::*;
+
+ fn roundtrip(rule: &Rule<Allowed, usize>) {
+ let json = serde_json::to_string(rule).unwrap();
+ assert_eq!(
+ *rule,
+ serde_json::from_str(&json).unwrap(),
+ "failed to roundtrip: {json}"
+ )
+ }
+
+ fn did(s: &str) -> Did {
+ s.parse().unwrap()
+ }
+
+ fn pattern(qp: QualifiedPattern<'static>) -> Pattern {
+ Pattern::try_from(qp).unwrap()
+ }
+
+ fn resolve_from_doc(delegate: Allowed, doc: &Doc) -> Result<doc::Delegates, ValidationError> {
+ match delegate {
+ Allowed::Delegates => Ok(doc.delegates().clone()),
+ Allowed::Set(delegates) => {
+ doc::Delegates::new(delegates).map_err(ValidationError::from)
+ }
+ }
+ }
+
+ fn tag(name: RefString, head: git2::Oid, repo: &git2::Repository) -> git::Oid {
+ let commit = fixtures::commit(name.as_str(), &[head], repo);
+ let target = repo.find_object(*commit, None).unwrap();
+ let tagger = repo.signature().unwrap();
+ repo.tag(name.as_str(), &target, &tagger, name.as_str(), false)
+ .unwrap()
+ .into()
+ }
+
+ #[test]
+ fn test_roundtrip() {
+ let rule1 = Rule::new(Allowed::Delegates, 1);
+ let rule2 = Rule::new(Allowed::Delegates, 1);
+ let rule3 = Rule::new(Allowed::Delegates, 1);
+ let mut rule4 = Rule::new(
+ Allowed::Set(nonempty![
+ did("did:key:z6MkpQTLwr8QyADGmBGAMsGttvWzP4PojUMs4hREZW5T5E3K"),
+ did("did:key:z6MknG1nYDftMYUQ7eTBSGgqB2PL1xK5Pif33J3sRym3e8ye"),
+ ]),
+ 2,
+ );
+ rule4.add_extensions(
+ serde_json::json!({
+ "foo": "bar",
+ "quux": 5,
+ })
+ .as_object()
+ .cloned()
+ .unwrap(),
+ );
+ roundtrip(&rule1);
+ roundtrip(&rule2);
+ roundtrip(&rule3);
+ roundtrip(&rule4);
+ }
+
+ #[test]
+ fn test_deserialization() {
+ let examples = r#"
+{
+ "refs/heads/main": {
+ "threshold": 2,
+ "allow": [
+ "did:key:z6MkpQTLwr8QyADGmBGAMsGttvWzP4PojUMs4hREZW5T5E3K",
+ "did:key:z6MknG1nYDftMYUQ7eTBSGgqB2PL1xK5Pif33J3sRym3e8ye"
+ ]
+ },
+ "refs/tags/releases/*": {
+ "threshold": 2,
+ "allow": [
+ "did:key:z6MknLWe8A7UJxvTfY36JcB8XrP1KTLb5HFTX38hEmdY3b56",
+ "did:key:z6Mkq2E5Se5H9gk1DsL1EMwR2t4CqSg3GFkNN2UeG4FNqXoP",
+ "did:key:z6MkqRmXW5fbP9hJ1Y8j2N4CgVdJ2XJ6TsyXYf3FQ2NJgXax"
+ ]
+ },
+ "refs/heads/development": {
+ "threshold": 1,
+ "allow": [
+ "did:key:z6MkhH7ENYE62JAjTiRZPU71MGZ6xCwnbyHHWfrBu3fr6PVG"
+ ]
+ },
+ "refs/heads/release/*": {
+ "threshold": 1,
+ "allow": "delegates"
+ }
+}
+ "#;
+ let expected = [
+ (
+ pattern(qualified_pattern!("refs/heads/main")),
+ Rule::new(
+ Allowed::Set(nonempty![
+ did("did:key:z6MkpQTLwr8QyADGmBGAMsGttvWzP4PojUMs4hREZW5T5E3K"),
+ did("did:key:z6MknG1nYDftMYUQ7eTBSGgqB2PL1xK5Pif33J3sRym3e8ye"),
+ ]),
+ 2,
+ ),
+ ),
+ (
+ pattern(qualified_pattern!("refs/tags/releases/*")),
+ Rule::new(
+ Allowed::Set(nonempty![
+ did("did:key:z6MknLWe8A7UJxvTfY36JcB8XrP1KTLb5HFTX38hEmdY3b56"),
+ did("did:key:z6Mkq2E5Se5H9gk1DsL1EMwR2t4CqSg3GFkNN2UeG4FNqXoP"),
+ did("did:key:z6MkqRmXW5fbP9hJ1Y8j2N4CgVdJ2XJ6TsyXYf3FQ2NJgXax")
+ ]),
+ 2,
+ ),
+ ),
+ (
+ pattern(qualified_pattern!("refs/heads/development")),
+ Rule::new(
+ Allowed::Set(nonempty![did(
+ "did:key:z6MkhH7ENYE62JAjTiRZPU71MGZ6xCwnbyHHWfrBu3fr6PVG"
+ )]),
+ 1,
+ ),
+ ),
+ (
+ pattern(qualified_pattern!("refs/heads/release/*")),
+ Rule::new(Allowed::Delegates, 1),
+ ),
+ ]
+ .into_iter()
+ .collect::<RawRules>();
+ let rules = serde_json::from_str::<BTreeMap<Pattern, RawRule>>(examples)
+ .unwrap()
+ .into();
+ eprintln!(
+ "RULES: {}",
+ serde_json::to_string_pretty(&expected).unwrap()
+ );
+ assert_eq!(expected, rules)
+ }
+
+ #[test]
+ fn test_order() {
+ assert!(
+ pattern(qualified_pattern!("a/b/c/d/*")) < pattern(qualified_pattern!("*/x")),
+ "example 1"
+ );
+ assert!(
+ pattern(qualified_pattern!("a")) < pattern(qualified_pattern!("*")),
+ "example 2.a"
+ );
+ assert!(
+ pattern(qualified_pattern!("abc")) < pattern(qualified_pattern!("a*")),
+ "example 2.a"
+ );
+ assert!(
+ pattern(qualified_pattern!("a/b/*")) < pattern(qualified_pattern!("a/*/c")),
+ "example 2.a"
+ );
+ assert!(
+ pattern(qualified_pattern!("aa*")) < pattern(qualified_pattern!("a*")),
+ "example 2.b.A"
+ );
+ assert!(
+ pattern(qualified_pattern!("a*b")) < pattern(qualified_pattern!("a*")),
+ "example 2.b.B"
+ );
+
+ let pattern01 = pattern(qualified_pattern!("refs/tags/*"));
+ let pattern02 = pattern(qualified_pattern!("refs/tags/v1"));
+ let pattern04 = pattern(qualified_pattern!("refs/tags/v1.0.0"));
+ let pattern05 = pattern(qualified_pattern!("refs/tags/release/v1.0.0"));
+ let pattern03 = pattern(qualified_pattern!("refs/heads/main"));
+ let pattern06 = pattern(qualified_pattern!("refs/tags/*/v1.0.0"));
+
+ let pattern07 = pattern(qualified_pattern!("refs/tags/x*"));
+ let pattern08 = pattern(qualified_pattern!("refs/tags/xx*"));
+
+ let pattern09 = pattern(qualified_pattern!("refs/foos/*"));
+
+ let pattern10 = pattern(qualified_pattern!("a"));
+ let pattern11 = pattern(qualified_pattern!("b"));
+
+ let pattern12 = pattern(qualified_pattern!("a/*"));
+ let pattern13 = pattern(qualified_pattern!("b/*"));
+
+ let pattern14 = pattern(qualified_pattern!("a/*/ab"));
+ let pattern15 = pattern(qualified_pattern!("a/*/a"));
+
+ let pattern16 = pattern(qualified_pattern!("a/*/b"));
+ let pattern17 = pattern(qualified_pattern!("a/*/a"));
+
+ // Test priority for path specificity
+ assert!(
+ pattern06 < pattern02,
+ "match for 06 is always more specific since it has more components"
+ );
+ assert!(pattern02 < pattern01, "match for 02 is also match for 01");
+ assert!(pattern08 < pattern07, "match for 08 is also match for 07");
+ // Test equality
+ assert!(pattern02 == pattern02);
+ // Test lexicographical fallback when paths are equally specific
+ assert!(pattern02 < pattern04);
+ assert!(pattern03 < pattern01);
+ assert!(pattern09 < pattern01);
+ assert!(pattern10 < pattern11);
+ assert!(pattern12 < pattern13);
+ assert!(pattern15 < pattern14);
+ assert!(
+ pattern17 < pattern16,
+ "matches have same length, but lexicographically, 'a' < 'b'"
+ );
+
+ // Test example from docs
+ let pattern18 = pattern(qualified_pattern!("refs/tags/release/candidates/*"));
+ let pattern19 = pattern(qualified_pattern!("refs/tags/release/*"));
+ let pattern20 = pattern(qualified_pattern!("refs/tags/*"));
+
+ assert!(pattern18 < pattern19);
+ assert!(pattern19 < pattern20);
+
+ let pattern21 = pattern(qualified_pattern!("refs/heads/dev"));
+
+ assert!(pattern21 < pattern03);
+
+ let mut patterns = [
+ pattern01.clone(),
+ pattern02.clone(),
+ pattern03.clone(),
+ pattern04.clone(),
+ pattern05.clone(),
+ pattern06.clone(),
+ ];
+ patterns.sort();
+
+ assert_eq!(
+ patterns,
+ [pattern05, pattern06, pattern03, pattern02, pattern04, pattern01]
+ );
+ }
+
+ #[test]
+ fn test_deserialize_extensions() {
+ let example = r#"
+{
+ "threshold": 2,
+ "allow": [
+ "did:key:z6MkpQTLwr8QyADGmBGAMsGttvWzP4PojUMs4hREZW5T5E3K",
+ "did:key:z6MknG1nYDftMYUQ7eTBSGgqB2PL1xK5Pif33J3sRym3e8ye"
+ ],
+ "foo": "bar",
+ "quux": 5
+}
+"#;
+ let rule = serde_json::from_str::<Rule<Allowed, usize>>(example).unwrap();
+ assert!(!rule.extensions().is_empty());
+ let extensions = rule.extensions();
+ assert_eq!(
+ extensions.get("foo"),
+ Some(serde_json::Value::String("bar".to_string())).as_ref()
+ );
+ assert_eq!(
+ extensions.get("quux"),
+ Some(serde_json::Value::Number(5.into())).as_ref()
+ );
+ }
+
+ #[test]
+ fn test_rule_validate_success() {
+ let doc = arbitrary::gen::<Doc>(1);
+ let delegates = Allowed::Set(doc.delegates().as_ref().clone());
+ let threshold = doc.majority();
+
+ let rule = Rule::new(delegates, threshold);
+ let result = rule.validate(&mut |delegate| resolve_from_doc(delegate, &doc));
+ assert!(result.is_ok(), "failed to validate doc: {result:?}");
+
+ let rule = Rule::new(Allowed::Delegates, 1);
+ let result = rule.validate(&mut |delegate| resolve_from_doc(delegate, &doc));
+ assert!(result.is_ok(), "failed to validate doc: {result:?}");
+ }
+
+ #[test]
+ fn test_rule_validate_failures() {
+ let doc = arbitrary::gen::<Doc>(1);
+ let pattern = pattern(qualified_pattern!("refs/heads/main"));
+
+ assert!(matches!(
+ Rule::new(Allowed::Delegates, 256)
+ .validate(&mut |delegate| resolve_from_doc(delegate, &doc)),
+ Err(ValidationError::Threshold(_))
+ ));
+
+ let threshold = doc.delegates().len().saturating_add(1);
+ assert!(matches!(
+ Rule::new(Allowed::Delegates, threshold)
+ .validate(&mut |delegate| resolve_from_doc(delegate, &doc)),
+ Err(ValidationError::Threshold(_))
+ ));
+
+ let delegates = NonEmpty::from_vec(arbitrary::vec::<Did>(256)).unwrap();
+ assert!(matches!(
+ Rule::new(delegates.into(), 1)
+ .validate(&mut |delegate| resolve_from_doc(delegate, &doc)),
+ Err(ValidationError::Delegates(_))
+ ));
+
+ let delegates = nonempty![
+ did("did:key:z6MknLWe8A7UJxvTfY36JcB8XrP1KTLb5HFTX38hEmdY3b56"),
+ did("did:key:z6MknLWe8A7UJxvTfY36JcB8XrP1KTLb5HFTX38hEmdY3b56")
+ ];
+ let expected = Rule {
+ allow: ResolvedDelegates::Set(
+ doc::Delegates::new(nonempty![did(
+ "did:key:z6MknLWe8A7UJxvTfY36JcB8XrP1KTLb5HFTX38hEmdY3b56"
+ )])
+ .unwrap(),
+ ),
+ threshold: doc::Threshold::MIN,
+ extensions: json::Map::new(),
+ };
+ assert_eq!(
+ Rule::new(delegates.into(), 1)
+ .validate(&mut |delegate| resolve_from_doc(delegate, &doc))
+ .unwrap(),
+ expected,
+ );
+
+ // Duplicate rules are overwritten
+ let rules = vec![
+ (pattern.clone(), Rule::new(Allowed::Delegates, 1)),
+ (
+ pattern.clone(),
+ Rule::new(doc.delegates().as_ref().clone().into(), 1),
+ ),
+ ];
+ let expected = [(
+ pattern,
+ Rule::new(
+ ResolvedDelegates::Set(doc.delegates().clone()),
+ doc::Threshold::MIN,
+ ),
+ )]
+ .into_iter()
+ .collect::<Rules>();
+ assert_eq!(
+ Rules::from_raw(rules, &mut |delegate| resolve_from_doc(delegate, &doc)).unwrap(),
+ expected
+ );
+ }
+
+ #[test]
+ fn test_canonical() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let storage = Storage::open(tempdir.path().join("storage"), fixtures::user()).unwrap();
+
+ transport::local::register(storage.clone());
+
+ let delegate = MockSigner::from_seed([0xff; 32]);
+ let contributor = MockSigner::from_seed([0xfe; 32]);
+ let (repo, head) = fixtures::repository(tempdir.path().join("working"));
+ let (rid, doc, _) = rad::init(
+ &repo,
+ "heartwood".try_into().unwrap(),
+ "Radicle Heartwood Protocol & Stack",
+ git::refname!("master"),
+ Visibility::default(),
+ &delegate,
+ &storage,
+ )
+ .unwrap();
+
+ let mut doc = doc.edit();
+ // Ensure there is a second delegate for testing overlapping rules
+ doc.delegate(contributor.public_key().into());
+
+ // Create tags and keep track of their OIDs
+ //
+ // follows the `refs/tags/release/candidates/*` rule
+ let failing_tag = git::refname!("release/candidates/v1.0");
+ let tags = [
+ // follows the `refs/tags/*` rule
+ git::refname!("v1.0"),
+ // follows the `refs/tags/release/*` rule
+ git::refname!("release/v1.0"),
+ failing_tag.clone(),
+ // follows the `refs/tags/*` rule
+ git::refname!("qa/v1.0"),
+ ]
+ .into_iter()
+ .map(|name| {
+ (
+ git::lit::refs_tags(name.clone()).into(),
+ tag(name, head, &repo),
+ )
+ })
+ .collect::<BTreeMap<Qualified, _>>();
+
+ git::push(
+ &repo,
+ &rad::REMOTE_NAME,
+ [
+ (
+ &git::qualified!("refs/tags/v1.0"),
+ &git::qualified!("refs/tags/v1.0"),
+ ),
+ (
+ &git::qualified!("refs/tags/release/v1.0"),
+ &git::qualified!("refs/tags/release/v1.0"),
+ ),
+ (
+ &git::qualified!("refs/tags/release/candidates/v1.0"),
+ &git::qualified!("refs/tags/release/candidates/v1.0"),
+ ),
+ (
+ &git::qualified!("refs/tags/qa/v1.0"),
+ &git::qualified!("refs/tags/qa/v1.0"),
+ ),
+ ],
+ )
+ .unwrap();
+
+ let rules = Rules::from_raw(
+ [
+ (
+ pattern(qualified_pattern!("refs/tags/*")),
+ Rule::new(Allowed::Delegates, 1),
+ ),
+ (
+ pattern(qualified_pattern!("refs/tags/release/*")),
+ Rule::new(Allowed::Delegates, 1),
+ ),
+ // Ensure that none of the other rules apply by ensuring we need
+ // both delegates to get the quorum of the
+ // `refs/tags/release/candidates/v1.0` reference
+ (
+ pattern(qualified_pattern!("refs/tags/release/candidates/*")),
+ Rule::new(Allowed::Delegates, 2),
+ ),
+ ],
+ &mut |delegate| resolve_from_doc(delegate, &doc.clone().verified().unwrap()),
+ )
+ .unwrap();
+
+ // All tags should succeed at getting their canonical tip other than the
+ // candidates tag.
+ let stored = storage.repository(rid).unwrap();
+ let failing = git::Qualified::from(git::lit::refs_tags(failing_tag));
+ for (refname, oid) in tags.iter() {
+ let matched = rules.matches(refname.clone()).unwrap_or_else(|| {
+ panic!("there should be a matching rule for {refname}, rules: {rules:#?}")
+ });
+ let canonical = matched.canonical(&stored).unwrap();
+ if *refname == failing {
+ assert!(canonical.quorum(&repo).is_err());
+ } else {
+ assert_eq!(
+ canonical
+ .quorum(&repo)
+ .unwrap_or_else(|e| panic!("quorum error for {refname}: {e}")),
+ *oid,
+ )
+ }
+ }
+ }
+
+ #[test]
+ fn test_special_branches() {
+ assert!(Pattern::try_from((*IDENTITY_BRANCH).clone()).is_err());
+ assert!(Pattern::try_from((*SIGREFS_BRANCH).clone()).is_err());
+ assert!(Pattern::try_from((*IDENTITY_ROOT).clone()).is_err());
+ }
+}
diff --git a/radicle/src/identity/doc.rs b/radicle/src/identity/doc.rs
index 1beb36007..a3abb7303 100644
--- a/radicle/src/identity/doc.rs
+++ b/radicle/src/identity/doc.rs
@@ -476,6 +476,12 @@ impl AsRef<NonEmpty<Did>> for Delegates {
}
}
+impl From<Did> for Delegates {
+ fn from(did: Did) -> Self {
+ Self(NonEmpty::new(did))
+ }
+}
+
impl TryFrom<Vec<Did>> for Delegates {
type Error = DelegatesError;
commit b3ad6f2d5ce0f68896aee1472acbc03ebb2c2b2f
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Wed Aug 14 13:26:51 2024 +0100
radicle: refactor Canonical
Changes for the `Canonical` type:
- Reorder arguments so that `delegates` and `threshold` are grouped
after `refname`
- Add `Canonical::is_empty` to detect when `Canonical` was constructed
with no tips
- Consume `Canonical` when calling `Canonical::quorum` to indicate
that it should be the final step
diff --git a/radicle/src/git/canonical.rs b/radicle/src/git/canonical.rs
index 9e916c985..e3f8a5711 100644
--- a/radicle/src/git/canonical.rs
+++ b/radicle/src/git/canonical.rs
@@ -99,8 +99,8 @@ impl Canonical {
{
Self::reference(
repo,
- delegates,
&lit::refs_heads(project.default_branch()).into(),
+ delegates,
threshold,
)
}
@@ -109,8 +109,8 @@ impl Canonical {
/// the reference `name`.
pub fn reference<S>(
repo: &S,
+ refname: &Qualified,
delegates: &NonEmpty<Did>,
- name: &Qualified,
threshold: usize,
) -> Result<Self, raw::Error>
where
@@ -118,15 +118,14 @@ impl Canonical {
{
let mut tips = BTreeMap::new();
for delegate in delegates.iter() {
- match repo.reference_oid(delegate, name) {
+ match repo.reference_oid(delegate, refname) {
Ok(tip) => {
tips.insert(*delegate, tip);
}
Err(e) if super::ext::is_not_found_err(&e) => {
log::warn!(
target: "radicle",
- "Missing `refs/namespaces/{}/{name}` while calculating the canonical reference",
- delegate.as_key()
+ "Missing `refs/namespaces/{delegate}/{refname}` while calculating the canonical reference",
);
}
Err(e) => return Err(e),
@@ -139,6 +138,14 @@ impl Canonical {
pub fn tips(&self) -> impl Iterator<Item = (&Did, &Oid)> {
self.tips.iter()
}
+
+ /// Returns `true` is there were no tips found for any of the delegates for
+ /// the given reference.
+ ///
+ /// N.b. this may be the case when a new reference is being created.
+ pub fn is_empty(&self) -> bool {
+ self.tips.is_empty()
+ }
}
/// Check that a given `target` converges with any of the provided `tips`.
@@ -176,7 +183,7 @@ impl Canonical {
///
/// Also returns an error if `heads` is empty or `threshold` cannot be
/// satisified with the number of heads given.
- pub fn quorum(&self, repo: &raw::Repository) -> Result<Oid, QuorumError> {
+ pub fn quorum(self, repo: &raw::Repository) -> Result<Oid, QuorumError> {
let mut candidates = BTreeMap::<_, usize>::new();
// Build a list of candidate commits and count how many "votes" each of them has.
commit 04277b4b3012c7e7a3cb56db176bebe9cc898a25
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Fri Jul 12 10:52:44 2024 +0100
radicle: store threshold in Canonical
The Canonical type is generally constructed with the threshold, needed
for the quorum computation, in scope. To make it easier to use and
encapsulate its functionality, it now holds the threshold as an
additional field.
diff --git a/radicle-remote-helper/src/push.rs b/radicle-remote-helper/src/push.rs
index 2ccdd8508..e02dff1d8 100644
--- a/radicle-remote-helper/src/push.rs
+++ b/radicle-remote-helper/src/push.rs
@@ -279,6 +279,7 @@ pub fn run(
stored,
&project,
identity.delegates().as_ref(),
+ identity.threshold(),
)?;
let converges = canonical::converges(
canonical
@@ -291,7 +292,7 @@ pub fn run(
canonical.modify_vote(me, head.into());
}
- match canonical.quorum(identity.threshold(), &working) {
+ match canonical.quorum(&working) {
Ok(canonical_oid) => {
// Canonical head is an ancestor of head.
let is_ff = head == *canonical_oid
diff --git a/radicle/src/git/canonical.rs b/radicle/src/git/canonical.rs
index cb794bd66..9e916c985 100644
--- a/radicle/src/git/canonical.rs
+++ b/radicle/src/git/canonical.rs
@@ -21,8 +21,10 @@ use super::{lit, Oid, Qualified};
///
/// `Canonical` can then be used for performing calculations about the
/// canonicity of the reference, most importantly the [`Canonical::quorum`].
+#[derive(Debug)]
pub struct Canonical {
tips: BTreeMap<Did, Oid>,
+ threshold: usize,
}
/// Error that can occur when calculation the [`Canonical::quorum`].
@@ -90,6 +92,7 @@ impl Canonical {
repo: &S,
project: &Project,
delegates: &NonEmpty<Did>,
+ threshold: usize,
) -> Result<Self, raw::Error>
where
S: ReadRepository,
@@ -98,6 +101,7 @@ impl Canonical {
repo,
delegates,
&lit::refs_heads(project.default_branch()).into(),
+ threshold,
)
}
@@ -107,6 +111,7 @@ impl Canonical {
repo: &S,
delegates: &NonEmpty<Did>,
name: &Qualified,
+ threshold: usize,
) -> Result<Self, raw::Error>
where
S: ReadRepository,
@@ -127,7 +132,7 @@ impl Canonical {
Err(e) => return Err(e),
}
}
- Ok(Canonical { tips })
+ Ok(Canonical { tips, threshold })
}
/// Return the set of [`Did`]s and their [`Oid`] tip.
@@ -171,7 +176,7 @@ impl Canonical {
///
/// Also returns an error if `heads` is empty or `threshold` cannot be
/// satisified with the number of heads given.
- pub fn quorum(&self, threshold: usize, repo: &raw::Repository) -> Result<Oid, QuorumError> {
+ pub fn quorum(&self, repo: &raw::Repository) -> Result<Oid, QuorumError> {
let mut candidates = BTreeMap::<_, usize>::new();
// Build a list of candidate commits and count how many "votes" each of them has.
@@ -196,11 +201,14 @@ impl Canonical {
}
}
// Keep commits which pass the threshold.
- candidates.retain(|_, votes| *votes >= threshold);
+ candidates.retain(|_, votes| *votes >= self.threshold);
- let (mut longest, _) = candidates
- .pop_first()
- .ok_or(QuorumError::NoCandidates(NoCandidates { threshold }))?;
+ let (mut longest, _) =
+ candidates
+ .pop_first()
+ .ok_or(QuorumError::NoCandidates(NoCandidates {
+ threshold: self.threshold,
+ }))?;
// Now that all scores are calculated, figure out what is the longest branch
// that passes the threshold. In case of divergence, return an error.
@@ -235,7 +243,7 @@ impl Canonical {
// |
//
return Err(QuorumError::Diverging(Diverging {
- threshold,
+ threshold: self.threshold,
base: base.into(),
longest,
head: *head,
@@ -272,7 +280,7 @@ mod tests {
(did, (*head).into())
})
.collect();
- Canonical { tips }.quorum(threshold, repo)
+ Canonical { tips, threshold }.quorum(repo)
}
#[test]
diff --git a/radicle/src/storage/git.rs b/radicle/src/storage/git.rs
index 758874be4..b51f12aed 100644
--- a/radicle/src/storage/git.rs
+++ b/radicle/src/storage/git.rs
@@ -747,8 +747,9 @@ impl ReadRepository for Repository {
let project = doc.project()?;
let branch_ref = git::refs::branch(project.default_branch());
let raw = self.raw();
- let oid = Canonical::default_branch(self, &project, doc.delegates().into())?
- .quorum(doc.threshold(), raw)?;
+ let oid =
+ Canonical::default_branch(self, &project, doc.delegates().as_ref(), doc.threshold())?
+ .quorum(raw)?;
Ok((branch_ref, oid))
}
commit 312c6a4fb6de9caf7ec0e052ea8592a65607a425
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Tue Sep 3 09:10:41 2024 +0100
meta: relax radicle-git dependencies
Relax the set of radicle-git depedencies to allow for patches. In
particular, the dependencies introduced some small improvements with
`0.*.1` updates.
diff --git a/Cargo.lock b/Cargo.lock
index 981c3d3bc..d3e82220d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -791,9 +791,9 @@ dependencies = [
[[package]]
name = "git-ref-format"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "137adab7111fcb575db6f07dae3a7d37f3c2630878954c9931f7135dfa33eeef"
+checksum = "7428e0d6e549a9a613d6f019b839a0f5142c331295b79e119ca8f4faac145da1"
dependencies = [
"git-ref-format-core",
"git-ref-format-macro",
@@ -801,9 +801,9 @@ dependencies = [
[[package]]
name = "git-ref-format-core"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebb6549ddc63ba5722acb98c823b0eccb7f8b979407bd2a8fd616f581ae50982"
+checksum = "bbaeb9672a55e9e32cb6d3ef781e7526b25ab97d499fae71615649340b143424"
dependencies = [
"bstr",
"serde",
@@ -812,14 +812,14 @@ dependencies = [
[[package]]
name = "git-ref-format-macro"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18ffd0101a3bd9a3aba39602b8b20751ddb7ee11596debb58be3074209dad2ae"
+checksum = "3b6ca5353accc201f6324dff744ba4660099546d4daf187ba868f07562e36ca4"
dependencies = [
"git-ref-format-core",
"proc-macro-error",
"quote",
- "syn 1.0.109",
+ "syn 2.0.89",
]
[[package]]
@@ -2351,9 +2351,9 @@ dependencies = [
[[package]]
name = "radicle-git-ext"
-version = "0.8.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16d2e8a6292811e44388e6068fcaf1040401e1f6a7a58cf48cc121cf7453c19d"
+checksum = "4b78c26e67d1712ad5a0c602ae3b236609461372ac04e200bda359fe4a1c6650"
dependencies = [
"git-ref-format",
"git2",
diff --git a/radicle-cli/Cargo.toml b/radicle-cli/Cargo.toml
index e8091d8c6..f3b3a3e89 100644
--- a/radicle-cli/Cargo.toml
+++ b/radicle-cli/Cargo.toml
@@ -15,15 +15,15 @@ path = "src/main.rs"
[dependencies]
anyhow = { version = "1" }
chrono = { version = "0.4.26", default-features = false, features = ["clock", "std"] }
-git-ref-format = { version = "0.3.0", features = ["macro"] }
+git-ref-format = { version = "0.3.1", features = ["macro"] }
lexopt = { version = "0.3.0" }
localtime = { version = "1.2.0" }
log = { version = "0.4", features = ["std"] }
nonempty = { version = "0.9.0" }
# N.b. this is required to use macros, even though it's re-exported
# through radicle
-radicle-git-ext = { version = "0.8.0", features = ["serde"] }
-radicle-surf = { version = "0.22.0" }
+radicle-git-ext = { version = "0.8", features = ["serde"] }
+radicle-surf = { version = "0.22" }
serde = { version = "1.0" }
serde_json = { version = "1" }
shlex = { version = "1.1.0" }
diff --git a/radicle-cob/Cargo.toml b/radicle-cob/Cargo.toml
index f262e849f..21ee02401 100644
--- a/radicle-cob/Cargo.toml
+++ b/radicle-cob/Cargo.toml
@@ -22,7 +22,7 @@ fastrand = { version = "2.0.0" }
log = { version = "0.4.17" }
nonempty = { version = "0.9.0", features = ["serialize"] }
once_cell = { version = "1.13" }
-radicle-git-ext = { version = "0.8.0", features = ["serde"] }
+radicle-git-ext = { version = "0.8", features = ["serde"] }
serde_json = { version = "1.0" }
thiserror = { version = "1.0" }
diff --git a/radicle-crypto/Cargo.toml b/radicle-crypto/Cargo.toml
index 4f7beb828..7b4c5954f 100644
--- a/radicle-crypto/Cargo.toml
+++ b/radicle-crypto/Cargo.toml
@@ -27,7 +27,7 @@ thiserror = { version = "1" }
zeroize = { version = "1.5.7" }
[dependencies.radicle-git-ext]
-version = "0.8.0"
+version = "0.8"
default-features = false
optional = true
diff --git a/radicle-fetch/Cargo.toml b/radicle-fetch/Cargo.toml
index 171dd25a6..fab6ad9d2 100644
--- a/radicle-fetch/Cargo.toml
+++ b/radicle-fetch/Cargo.toml
@@ -18,7 +18,7 @@ gix-protocol = { version = "0.47.0", features = ["blocking-client"] }
gix-transport = { version = "0.44.0", features = ["blocking-client"] }
log = { version = "0.4.17", features = ["std"] }
nonempty = { version = "0.9.0" }
-radicle-git-ext = { version = "0.8.0", features = ["bstr"] }
+radicle-git-ext = { version = "0.8", features = ["bstr"] }
thiserror = { version = "1" }
[dependencies.radicle]
diff --git a/radicle-node/Cargo.toml b/radicle-node/Cargo.toml
index 35f6d502f..e94f028ba 100644
--- a/radicle-node/Cargo.toml
+++ b/radicle-node/Cargo.toml
@@ -34,7 +34,7 @@ once_cell = { version = "1.13" }
qcheck = { version = "1", default-features = false, optional = true }
# N.b. this is required to use macros, even though it's re-exported
# through radicle
-radicle-git-ext = { version = "0.8.0", features = ["serde"] }
+radicle-git-ext = { version = "0.8", features = ["serde"] }
sqlite = { version = "0.32.0", features = ["bundled"] }
scrypt = { version = "0.11.0", default-features = false }
serde = { version = "1", features = ["derive"] }
diff --git a/radicle-remote-helper/Cargo.toml b/radicle-remote-helper/Cargo.toml
index 91251fb45..4768bf81b 100644
--- a/radicle-remote-helper/Cargo.toml
+++ b/radicle-remote-helper/Cargo.toml
@@ -11,7 +11,7 @@ build = "build.rs"
[dependencies]
thiserror = { version = "1" }
log = { version = "0.4.17" }
-radicle-git-ext = { version = "0.8.0" }
+radicle-git-ext = { version = "0.8" }
[dependencies.radicle]
path = "../radicle"
diff --git a/radicle-tools/Cargo.toml b/radicle-tools/Cargo.toml
index 85b53bff4..4612d2391 100644
--- a/radicle-tools/Cargo.toml
+++ b/radicle-tools/Cargo.toml
@@ -9,7 +9,7 @@ edition = "2021"
anyhow = { version = "1" }
# N.b. this is required to use macros, even though it's re-exported
# through radicle
-radicle-git-ext = { version = "0.8.0", features = ["serde"] }
+radicle-git-ext = { version = "0.8", features = ["serde"] }
[dependencies.radicle]
version = "0"
diff --git a/radicle/Cargo.toml b/radicle/Cargo.toml
index 9282579b8..2eb35264f 100644
--- a/radicle/Cargo.toml
+++ b/radicle/Cargo.toml
@@ -29,7 +29,7 @@ once_cell = { version = "1.13" }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
siphasher = { version = "1.0.0" }
-radicle-git-ext = { version = "0.8.0", features = ["serde"] }
+radicle-git-ext = { version = "0.8", features = ["serde"] }
sqlite = { version = "0.32.0", features = ["bundled"] }
tempfile = { version = "3.3.0" }
thiserror = { version = "1" }
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 26e38854-c259-4494-8c47-30a5dd6aea1a -v /opt/radcis/ci.rad.levitte.org/cci/state/26e38854-c259-4494-8c47-30a5dd6aea1a/s:/26e38854-c259-4494-8c47-30a5dd6aea1a/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/26e38854-c259-4494-8c47-30a5dd6aea1a/w:/26e38854-c259-4494-8c47-30a5dd6aea1a/w -w /26e38854-c259-4494-8c47-30a5dd6aea1a/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:trixie bash /26e38854-c259-4494-8c47-30a5dd6aea1a/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 adler2 v2.0.0
Downloaded aes v0.8.4
Downloaded rand_chacha v0.3.1
Downloaded amplify_derive v4.0.0
Downloaded gix-hash v0.15.1
Downloaded aes-gcm v0.10.3
Downloaded generic-array v0.14.7
Downloaded rand_core v0.6.4
Downloaded siphasher v0.3.11
Downloaded unicode-display-width v0.3.0
Downloaded gix-chunk v0.4.11
Downloaded tinyvec_macros v0.1.1
Downloaded utf8parse v0.2.1
Downloaded write16 v1.0.0
Downloaded utf16_iter v1.0.5
Downloaded universal-hash v0.5.1
Downloaded sha1_smol v1.0.0
Downloaded sem_safe v0.2.0
Downloaded gix-command v0.4.1
Downloaded qcheck-macros v1.0.0
Downloaded shell-words v1.1.0
Downloaded zerofrom v0.1.6
Downloaded walkdir v2.5.0
Downloaded gix-validate v0.9.4
Downloaded zeroize v1.7.0
Downloaded thiserror v1.0.69
Downloaded gix-trace v0.1.12
Downloaded zerofrom-derive v0.1.6
Downloaded radicle-git-ext v0.8.1
Downloaded percent-encoding v2.3.1
Downloaded pbkdf2 v0.12.2
Downloaded gix-sec v0.10.12
Downloaded group v0.13.0
Downloaded gix-prompt v0.9.1
Downloaded keccak v0.1.5
Downloaded yoke v0.7.5
Downloaded pem-rfc7468 v0.7.0
Downloaded parking_lot_core v0.9.9
Downloaded nonempty v0.5.0
Downloaded icu_locid_transform v1.5.0
Downloaded zerovec-derive v0.10.3
Downloaded phf_shared v0.11.3
Downloaded gix-refspec v0.27.0
Downloaded inout v0.1.3
Downloaded gix-revision v0.31.1
Downloaded num-integer v0.1.46
Downloaded tar v0.4.40
Downloaded lazy_static v1.5.0
Downloaded gix-odb v0.66.0
Downloaded gix-ref v0.49.1
Downloaded ppv-lite86 v0.2.17
Downloaded tree-sitter-go v0.23.4
Downloaded proc-macro-error v1.0.4
Downloaded der v0.7.9
Downloaded litemap v0.7.5
Downloaded pkg-config v0.3.30
Downloaded libm v0.2.8
Downloaded primeorder v0.13.6
Downloaded idna v1.0.3
Downloaded p256 v0.13.2
Downloaded icu_locid v1.5.0
Downloaded pretty_assertions v1.4.0
Downloaded tree-sitter-c v0.23.2
Downloaded indexmap v2.2.6
Downloaded memchr v2.7.2
Downloaded regex v1.11.1
Downloaded inquire v0.7.5
Downloaded num-bigint-dig v0.8.4
Downloaded hashbrown v0.14.3
Downloaded regex-syntax v0.8.5
Downloaded chrono v0.4.38
Downloaded icu_properties_data v1.5.1
Downloaded p384 v0.13.0
Downloaded tree-sitter-bash v0.23.3
Downloaded bloomy v1.2.0
Downloaded tree-sitter-ruby v0.23.1
Downloaded tree-sitter-rust v0.23.2
Downloaded rustix v0.38.34
Downloaded git2 v0.19.0
Downloaded p521 v0.13.3
Downloaded prodash v29.0.2
Downloaded syn v1.0.109
Downloaded emojis v0.6.4
Downloaded vcpkg v0.2.15
Downloaded cyphernet v0.5.2
Downloaded regex-automata v0.4.9
Downloaded cc v1.2.2
Downloaded icu_collections v1.5.0
Downloaded tree-sitter-md v0.3.2
Downloaded zerovec v0.10.4
Downloaded gix-pack v0.56.0
Downloaded winnow v0.6.26
Downloaded tree-sitter v0.24.4
Downloaded miniz_oxide v0.8.8
Downloaded jiff v0.2.1
Downloaded tree-sitter-typescript v0.23.2
Downloaded arc-swap v1.7.1
Downloaded sha3 v0.10.8
Downloaded libc v0.2.155
Downloaded unicode-normalization v0.1.23
Downloaded serde_json v1.0.116
Downloaded pkcs1 v0.7.5
Downloaded once_cell v1.19.0
Downloaded icu_provider v1.5.0
Downloaded unicode-segmentation v1.11.0
Downloaded crypto-bigint v0.5.5
Downloaded crossbeam-channel v0.5.15
Downloaded base64 v0.21.7
Downloaded lexopt v0.3.0
Downloaded jobserver v0.1.31
Downloaded io-reactor v0.5.2
Downloaded qcheck v1.0.0
Downloaded icu_normalizer v1.5.0
Downloaded ssh-key v0.6.6
Downloaded serde v1.0.219
Downloaded base64 v0.13.1
Downloaded polyval v0.6.2
Downloaded poly1305 v0.8.0
Downloaded pkcs8 v0.10.2
Downloaded num-traits v0.2.18
Downloaded gix-transport v0.44.0
Downloaded url v2.5.4
Downloaded elliptic-curve v0.13.8
Downloaded gix-protocol v0.47.0
Downloaded icu_locid_transform_data v1.5.1
Downloaded maybe-async v0.2.10
Downloaded spin v0.9.8
Downloaded rsa v0.9.6
Downloaded proc-macro2 v1.0.92
Downloaded linux-raw-sys v0.4.13
Downloaded parking_lot v0.12.1
Downloaded quote v1.0.36
Downloaded socket2 v0.5.7
Downloaded itoa v1.0.11
Downloaded icu_properties v1.5.1
Downloaded icu_normalizer_data v1.5.1
Downloaded gix-packetline v0.18.4
Downloaded tree-sitter-css v0.23.1
Downloaded log v0.4.21
Downloaded hmac v0.12.1
Downloaded similar v2.5.0
Downloaded proc-macro-error-attr v1.0.4
Downloaded popol v3.0.0
Downloaded gix-tempfile v15.0.0
Downloaded signals_receipts v0.2.0
Downloaded serde_derive v1.0.219
Downloaded nonempty v0.9.0
Downloaded phf v0.11.3
Downloaded opaque-debug v0.3.1
Downloaded numtoa v0.1.0
Downloaded gix-quote v0.4.15
Downloaded gix-path v0.10.15
Downloaded tinyvec v1.6.0
Downloaded sha2 v0.10.8
Downloaded snapbox v0.4.17
Downloaded iana-time-zone v0.1.60
Downloaded netservices v0.8.0
Downloaded lock_api v0.4.11
Downloaded sec1 v0.7.3
Downloaded tree-sitter-html v0.23.2
Downloaded termion v3.0.0
Downloaded libgit2-sys v0.17.0+1.8.1
Downloaded idna_adapter v1.2.0
Downloaded num-iter v0.1.44
Downloaded normalize-line-endings v0.3.0
Downloaded icu_provider_macros v1.5.0
Downloaded gix-shallow v0.1.0
Downloaded memmap2 v0.9.4
Downloaded unicode-width v0.1.11
Downloaded typenum v1.17.0
Downloaded streaming-iterator v0.1.9
Downloaded shlex v1.3.0
Downloaded ryu v1.0.17
Downloaded yansi v0.5.1
Downloaded unicode-ident v1.0.12
Downloaded thiserror-impl v2.0.12
Downloaded thiserror v2.0.12
Downloaded smallvec v1.13.2
Downloaded salsa20 v0.10.2
Downloaded diff v0.1.13
Downloaded synstructure v0.13.1
Downloaded spki v0.7.3
Downloaded const-oid v0.9.6
Downloaded home v0.5.9
Downloaded gix-traverse v0.43.1
Downloaded tinystr v0.7.6
Downloaded noise-framework v0.4.0
Downloaded multibase v0.9.1
Downloaded rfc6979 v0.4.0
Downloaded getrandom v0.2.15
Downloaded crossbeam-utils v0.8.19
Downloaded amplify_syn v2.0.1
Downloaded radicle-std-ext v0.1.0
Downloaded newline-converter v0.3.0
Downloaded localtime v1.3.1
Downloaded yoke-derive v0.7.5
Downloaded xattr v1.3.1
Downloaded version_check v0.9.4
Downloaded tree-sitter-python v0.23.4
Downloaded tempfile v3.10.1
Downloaded subtle v2.5.0
Downloaded stable_deref_trait v1.2.0
Downloaded ssh-encoding v0.2.0
Downloaded ssh-cipher v0.2.0
Downloaded sqlite3-sys v0.15.2
Downloaded sqlite v0.32.0
Downloaded signature v1.6.4
Downloaded ecdsa v0.16.9
Downloaded data-encoding v2.5.0
Downloaded ctr v0.9.2
Downloaded crc32fast v1.4.0
Downloaded writeable v0.5.5
Downloaded gix-credentials v0.26.0
Downloaded gix-utils v0.1.14
Downloaded gix-url v0.28.2
Downloaded gix-revwalk v0.17.0
Downloaded tree-sitter-json v0.24.8
Downloaded signature v2.2.0
Downloaded ascii v1.1.0
Downloaded anstream v0.6.13
Downloaded scopeguard v1.2.0
Downloaded ed25519 v1.5.3
Downloaded utf8_iter v1.0.4
Downloaded same-file v1.0.6
Downloaded base64ct v1.6.0
Downloaded base16ct v0.2.0
Downloaded anstyle-parse v0.2.3
Downloaded dyn-clone v1.0.17
Downloaded displaydoc v0.2.5
Downloaded cypheraddr v0.4.0
Downloaded bstr v1.9.1
Downloaded anyhow v1.0.82
Downloaded anstyle-query v1.0.2
Downloaded anstyle v1.0.6
Downloaded tree-sitter-toml-ng v0.6.0
Downloaded tree-sitter-language v0.1.2
Downloaded timeago v0.4.2
Downloaded termion v2.0.3
Downloaded syn v2.0.89
Downloaded snapbox-macros v0.3.8
Downloaded siphasher v1.0.1
Downloaded gix-lock v15.0.1
Downloaded gix-hashtable v0.6.0
Downloaded gix-diff v0.49.0
Downloaded gix-date v0.9.4
Downloaded gix-commitgraph v0.25.1
Downloaded git-ref-format-macro v0.3.1
Downloaded git-ref-format-core v0.3.1
Downloaded ghash v0.5.1
Downloaded form_urlencoded v1.2.1
Downloaded fastrand v2.1.0
Downloaded fast-glob v0.3.3
Downloaded escargot v0.5.10
Downloaded errno v0.3.9
Downloaded either v1.11.0
Downloaded ec25519 v0.1.0
Downloaded cyphergraphy v0.3.0
Downloaded crypto-common v0.1.6
Downloaded cpufeatures v0.2.12
Downloaded gix-actor v0.33.2
Downloaded ff v0.13.0
Downloaded autocfg v1.2.0
Downloaded equivalent v1.0.1
Downloaded ct-codecs v1.1.1
Downloaded colorchoice v1.0.0
Downloaded bcrypt-pbkdf v0.10.0
Downloaded base-x v0.2.11
Downloaded tree-sitter-highlight v0.24.4
Downloaded thiserror-impl v1.0.69
Downloaded socks5-client v0.4.1
Downloaded scrypt v0.11.0
Downloaded gix-features v0.39.1
Downloaded gix-config-value v0.14.12
Downloaded block-padding v0.3.3
Downloaded radicle-surf v0.22.0
Downloaded gix-negotiate v0.17.0
Downloaded gix-fs v0.12.1
Downloaded git-ref-format v0.3.1
Downloaded faster-hex v0.9.0
Downloaded digest v0.10.7
Downloaded data-encoding-macro v0.1.14
Downloaded colored v2.1.0
Downloaded cfg-if v1.0.0
Downloaded rand v0.8.5
Downloaded gix-object v0.46.1
Downloaded chacha20 v0.9.1
Downloaded fxhash v0.2.1
Downloaded flate2 v1.1.1
Downloaded filetime v0.2.23
Downloaded data-encoding-macro-internal v0.1.12
Downloaded cipher v0.4.4
Downloaded chacha20poly1305 v0.10.1
Downloaded cbc v0.1.2
Downloaded blowfish v0.9.1
Downloaded block-buffer v0.10.4
Downloaded base64 v0.22.1
Downloaded bytesize v2.0.1
Downloaded byteorder v1.5.0
Downloaded bitflags v2.5.0
Downloaded base32 v0.4.0
Downloaded amplify_num v0.5.2
Downloaded amplify v4.6.0
Downloaded aho-corasick v1.1.3
Downloaded aead v0.5.2
Downloaded sqlite3-src v0.5.1
Downloaded libz-sys v1.1.16
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
Compiling quote v1.0.36
Checking memchr v2.7.2
Compiling syn v2.0.89
Checking getrandom v0.2.15
Compiling generic-array v0.14.7
Compiling jobserver v0.1.31
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 log v0.4.21
Checking fastrand v2.1.0
Compiling autocfg v1.2.0
Checking bitflags v2.5.0
Checking regex-automata v0.4.9
Checking subtle v2.5.0
Compiling syn v1.0.109
Checking once_cell v1.19.0
Checking stable_deref_trait v1.2.0
Checking zeroize v1.7.0
Checking cpufeatures v0.2.12
Checking tinyvec_macros v0.1.1
Compiling synstructure v0.13.1
Checking tinyvec v1.6.0
Compiling serde_derive v1.0.219
Compiling zerofrom-derive v0.1.6
Checking bstr v1.9.1
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 crc32fast v1.4.0
Checking unicode-normalization v0.1.23
Checking block-padding v0.3.3
Checking writeable v0.5.5
Compiling icu_locid_transform_data v1.5.1
Checking litemap v0.7.5
Checking inout v0.1.3
Compiling icu_provider_macros v1.5.0
Checking block-buffer v0.10.4
Checking zerovec v0.10.4
Compiling icu_properties_data v1.5.1
Compiling pkg-config v0.3.30
Checking digest v0.10.7
Checking cipher v0.4.4
Checking tinystr v0.7.6
Compiling icu_normalizer_data v1.5.1
Checking icu_collections v1.5.0
Checking icu_locid v1.5.0
Compiling thiserror v1.0.69
Compiling thiserror-impl v1.0.69
Checking utf8_iter v1.0.4
Checking write16 v1.0.0
Checking icu_provider v1.5.0
Checking utf16_iter v1.0.5
Compiling rustix v0.38.34
Checking icu_locid_transform v1.5.0
Checking sha2 v0.10.8
Checking hashbrown v0.14.3
Checking icu_properties v1.5.1
Checking percent-encoding v2.3.1
Checking linux-raw-sys v0.4.13
Checking itoa v1.0.11
Checking form_urlencoded v1.2.1
Checking universal-hash v0.5.1
Checking opaque-debug v0.3.1
Compiling vcpkg v0.2.15
Checking icu_normalizer v1.5.0
Compiling amplify_syn v2.0.1
Checking idna_adapter v1.2.0
Checking idna v1.0.3
Compiling libz-sys v1.1.16
Checking url v2.5.4
Checking byteorder v1.5.0
Compiling data-encoding v2.5.0
Compiling amplify_derive v4.0.0
Checking signature v1.6.4
Checking amplify_num v0.5.2
Checking ascii v1.1.0
Compiling data-encoding-macro-internal v0.1.12
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 data-encoding-macro v0.1.14
Checking ct-codecs v1.1.1
Checking base-x v0.2.11
Checking ec25519 v0.1.0
Checking multibase v0.9.1
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.1
Checking polyval v0.6.2
Compiling sqlite3-src v0.5.1
Checking cyphergraphy v0.3.0
Checking hmac v0.12.1
Compiling thiserror v2.0.12
Checking base64ct v1.6.0
Checking base32 v0.4.0
Checking ppv-lite86 v0.2.17
Checking pem-rfc7468 v0.7.0
Checking cypheraddr v0.4.0
Checking rand_chacha v0.3.1
Checking pbkdf2 v0.12.2
Compiling git-ref-format-macro v0.3.1
Checking ghash v0.5.1
Checking chacha20poly1305 v0.10.1
Checking ctr v0.9.2
Checking aes v0.8.4
Compiling thiserror-impl v2.0.12
Compiling crossbeam-utils v0.8.19
Checking aes-gcm v0.10.3
Checking git-ref-format v0.3.1
Checking noise-framework v0.4.0
Checking rand v0.8.5
Checking socks5-client v0.4.1
Checking ssh-encoding v0.2.0
Checking blowfish v0.9.1
Checking cbc v0.1.2
Checking radicle-std-ext v0.1.0
Checking ssh-cipher v0.2.0
Checking bcrypt-pbkdf v0.10.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 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-ssh)
Compiling num-traits v0.2.18
Checking equivalent v1.0.1
Compiling serde_json v1.0.116
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 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-dag)
Checking lazy_static v1.5.0
Compiling anyhow v1.0.82
Compiling parking_lot_core v0.9.9
Checking iana-time-zone v0.1.60
Checking gix-trace v0.1.12
Checking anstyle-query v1.0.2
Checking scopeguard v1.2.0
Checking chrono v0.4.38
Checking colored v2.1.0
Checking localtime v1.3.1
Checking bytesize v2.0.1
Checking utf8parse v0.2.1
Checking fast-glob v0.3.3
Checking base64 v0.21.7
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 colorchoice v1.0.0
Checking anstyle v1.0.6
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 miniz_oxide v0.8.8
Checking gix-hash v0.15.1
Checking same-file v1.0.6
Checking similar v2.5.0
Checking normalize-line-endings v0.3.0
Checking walkdir v2.5.0
Checking snapbox v0.4.17
Checking flate2 v1.1.1
Checking radicle-signals v0.11.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/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 yansi v0.5.1
Checking unicode-segmentation v1.11.0
Checking numtoa v0.1.0
Checking diff v0.1.13
Checking pretty_assertions v1.4.0
Compiling xattr v1.3.1
Compiling filetime v0.2.23
Checking lexopt v0.3.0
Checking home v0.5.9
Compiling escargot v0.5.10
Checking gix-path v0.10.15
Compiling tar v0.4.40
Checking newline-converter v0.3.0
Checking termion v2.0.3
Checking fxhash v0.2.1
Checking regex v1.11.1
Checking streaming-iterator v0.1.9
Checking unicode-width v0.1.11
Checking dyn-clone v1.0.17
Checking inquire v0.7.5
Compiling radicle-surf v0.22.0
Checking unicode-display-width v0.3.0
Checking termion v3.0.0
Compiling tree-sitter-toml-ng v0.6.0
Compiling tree-sitter-md v0.3.2
Compiling tree-sitter-html v0.23.2
Compiling tree-sitter-python v0.23.4
Compiling tree-sitter-go v0.23.4
Compiling tree-sitter-ruby v0.23.1
Compiling tree-sitter-bash v0.23.3
Compiling tree-sitter-c v0.23.2
Compiling tree-sitter-typescript v0.23.2
Compiling tree-sitter-json v0.24.8
Compiling tree-sitter-css v0.23.1
Compiling tree-sitter-rust v0.23.2
Checking sqlite3-sys v0.15.2
Checking sqlite v0.32.0
Checking base64 v0.13.1
Checking nonempty v0.5.0
Compiling radicle-cli v0.12.1 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-cli)
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-prompt v0.9.1
Checking gix-packetline v0.18.4
Checking gix-credentials v0.26.0
Checking gix-transport v0.44.0
Checking gix-shallow v0.1.0
Compiling maybe-async v0.2.10
Checking arc-swap v1.7.1
Checking popol v3.0.0
Checking gix-date v0.9.4
Checking either v1.11.0
Checking gix-actor v0.33.2
Checking io-reactor v0.5.2
Checking salsa20 v0.10.2
Checking gix-object v0.46.1
Checking socket2 v0.5.7
Checking siphasher v0.3.11
Compiling radicle-node v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-node)
Checking bloomy v1.2.0
Checking netservices v0.8.0
Checking scrypt v0.11.0
Checking radicle-systemd v0.9.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-systemd)
Compiling qcheck-macros v1.0.0
Compiling radicle-remote-helper v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-remote-helper)
Checking gix-revwalk v0.17.0
Checking gix-traverse v0.43.1
Checking gix-revision v0.31.1
Checking gix-diff v0.49.0
Checking gix-refspec v0.27.0
Checking gix-pack v0.56.0
Checking gix-negotiate v0.17.0
Checking gix-ref v0.49.1
Checking phf_shared v0.11.3
Checking phf v0.11.3
Checking emojis v0.6.4
Checking gix-odb v0.66.0
Checking gix-protocol v0.47.0
Checking git2 v0.19.0
Checking radicle-git-ext v0.8.1
Checking radicle-term v0.12.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-term)
Checking radicle-crypto v0.11.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-crypto)
Checking radicle-cob v0.13.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-cob)
Checking radicle-crdt v0.1.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-crdt)
Checking radicle v0.14.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle)
Checking radicle-cli-test v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-cli-test)
Checking radicle-fetch v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-fetch)
Checking radicle-tools v0.9.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-tools)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 44.70s
+ 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 getrandom v0.2.15
Compiling cc v1.2.2
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 fastrand v2.1.0
Compiling log v0.4.21
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 cpufeatures v0.2.12
Compiling tinyvec_macros v0.1.1
Compiling tinyvec v1.6.0
Compiling zerovec v0.10.4
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 icu_provider v1.5.0
Compiling digest v0.10.7
Compiling cipher v0.4.4
Compiling icu_locid_transform_data v1.5.1
Compiling crc32fast v1.4.0
Compiling icu_locid_transform v1.5.0
Compiling icu_properties_data v1.5.1
Compiling icu_collections v1.5.0
Compiling icu_normalizer_data v1.5.1
Compiling utf16_iter v1.0.5
Compiling utf8_iter v1.0.4
Compiling write16 v1.0.0
Compiling icu_properties v1.5.1
Compiling thiserror v1.0.69
Compiling sha2 v0.10.8
Compiling linux-raw-sys v0.4.13
Compiling hashbrown v0.14.3
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 icu_normalizer v1.5.0
Compiling data-encoding v2.5.0
Compiling idna_adapter v1.2.0
Compiling idna v1.0.3
Compiling url v2.5.4
Compiling amplify_num v0.5.2
Compiling signature v1.6.4
Compiling ed25519 v1.5.3
Compiling tempfile v3.10.1
Compiling data-encoding-macro v0.1.14
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 amplify v4.6.0
Compiling chacha20 v0.9.1
Compiling keccak v0.1.5
Compiling cyphergraphy v0.3.0
Compiling sha3 v0.10.8
Compiling polyval v0.6.2
Compiling git-ref-format-core v0.3.1
Compiling hmac v0.12.1
Compiling sqlite3-src v0.5.1
Compiling base32 v0.4.0
Compiling base64ct v1.6.0
Compiling ppv-lite86 v0.2.17
Compiling pem-rfc7468 v0.7.0
Compiling rand_chacha v0.3.1
Compiling git-ref-format-macro v0.3.1
Compiling cypheraddr v0.4.0
Compiling pbkdf2 v0.12.2
Compiling ghash v0.5.1
Compiling chacha20poly1305 v0.10.1
Compiling aes v0.8.4
Compiling ctr v0.9.2
Compiling thiserror v2.0.12
Compiling aes-gcm v0.10.3
Compiling git-ref-format v0.3.1
Compiling noise-framework v0.4.0
Compiling socks5-client v0.4.1
Compiling rand v0.8.5
Compiling ssh-encoding v0.2.0
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 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-ssh)
Compiling equivalent v1.0.1
Compiling indexmap v2.2.6
Compiling ryu v1.0.17
Compiling tree-sitter-language v0.1.2
Compiling serde_json v1.0.116
Compiling num-traits v0.2.18
Compiling siphasher v1.0.1
Compiling nonempty v0.9.0
Compiling radicle-dag v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-dag)
Compiling lazy_static v1.5.0
Compiling iana-time-zone v0.1.60
Compiling gix-trace v0.1.12
Compiling scopeguard v1.2.0
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 fast-glob v0.3.3
Compiling anstyle-parse v0.2.3
Compiling parking_lot v0.12.1
Compiling anyhow v1.0.82
Compiling faster-hex v0.9.0
Compiling errno v0.3.9
Compiling sem_safe v0.2.0
Compiling anstyle v1.0.6
Compiling colorchoice v1.0.0
Compiling anstream v0.6.13
Compiling signals_receipts v0.2.0
Compiling gix-utils v0.1.14
Compiling snapbox-macros v0.3.8
Compiling gix-hash v0.15.1
Compiling same-file v1.0.6
Compiling normalize-line-endings v0.3.0
Compiling similar v2.5.0
Compiling walkdir v2.5.0
Compiling radicle-signals v0.11.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-signals)
Compiling snapbox v0.4.17
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 diff v0.1.13
Compiling unicode-segmentation v1.11.0
Compiling numtoa v0.1.0
Compiling xattr v1.3.1
Compiling pretty_assertions v1.4.0
Compiling filetime v0.2.23
Compiling lexopt v0.3.0
Compiling home v0.5.9
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 streaming-iterator v0.1.9
Compiling unicode-width v0.1.11
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-css v0.23.1
Compiling tree-sitter-go v0.23.4
Compiling tree-sitter-html v0.23.2
Compiling tree-sitter-c v0.23.2
Compiling tree-sitter-ruby v0.23.1
Compiling tree-sitter-bash v0.23.3
Compiling tree-sitter-json v0.24.8
Compiling tree-sitter-md v0.3.2
Compiling tree-sitter-python v0.23.4
Compiling tree-sitter-toml-ng v0.6.0
Compiling tree-sitter-rust v0.23.2
Compiling tree-sitter-typescript v0.23.2
Compiling sqlite3-sys v0.15.2
Compiling sqlite v0.32.0
Compiling radicle-cli v0.12.1 (/26e38854-c259-4494-8c47-30a5dd6aea1a/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 gix-tempfile v15.0.0
Compiling shell-words v1.1.0
Compiling gix-command v0.4.1
Compiling gix-lock v15.0.1
Compiling gix-config-value v0.14.12
Compiling gix-url v0.28.2
Compiling gix-quote v0.4.15
Compiling gix-sec v0.10.12
Compiling gix-prompt v0.9.1
Compiling gix-packetline v0.18.4
Compiling gix-date v0.9.4
Compiling gix-transport v0.44.0
Compiling gix-actor v0.33.2
Compiling gix-object v0.46.1
Compiling gix-credentials v0.26.0
Compiling gix-shallow v0.1.0
Compiling gix-revwalk v0.17.0
Compiling gix-traverse v0.43.1
Compiling gix-revision v0.31.1
Compiling gix-diff v0.49.0
Compiling gix-refspec v0.27.0
Compiling gix-pack v0.56.0
Compiling gix-negotiate v0.17.0
Compiling gix-ref v0.49.1
Compiling arc-swap v1.7.1
Compiling popol v3.0.0
Compiling either v1.11.0
Compiling io-reactor v0.5.2
Compiling gix-odb v0.66.0
Compiling gix-protocol v0.47.0
Compiling salsa20 v0.10.2
Compiling socket2 v0.5.7
Compiling siphasher v0.3.11
Compiling radicle-node v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-node)
Compiling netservices v0.8.0
Compiling bloomy v1.2.0
Compiling scrypt v0.11.0
Compiling radicle-systemd v0.9.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-systemd)
Compiling radicle-remote-helper v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/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.1
Compiling radicle-term v0.12.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-term)
Compiling radicle-crypto v0.11.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-crypto)
Compiling radicle-cob v0.13.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-cob)
Compiling radicle-crdt v0.1.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-crdt)
Compiling radicle v0.14.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle)
Compiling radicle-cli-test v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-cli-test)
Compiling radicle-fetch v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-fetch)
Compiling radicle-tools v0.9.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-tools)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 09s
+ cargo doc --workspace --no-deps
Compiling syn v1.0.109
Checking radicle-ssh v0.9.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-ssh)
Checking radicle-dag v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-dag)
Checking radicle-signals v0.11.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-signals)
Compiling radicle-cli v0.12.1 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-cli)
Checking radicle-term v0.12.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-term)
Compiling radicle-node v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-node)
Checking radicle-systemd v0.9.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-systemd)
Documenting radicle-term v0.12.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-term)
Documenting radicle-signals v0.11.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-signals)
Documenting radicle-ssh v0.9.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-ssh)
Documenting radicle-dag v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-dag)
Documenting radicle-systemd v0.9.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/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.1
Compiling amplify_derive v4.0.0
Checking data-encoding-macro v0.1.14
Checking multibase v0.9.1
Checking git-ref-format v0.3.1
Checking radicle-git-ext v0.8.1
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 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-crypto)
Checking netservices v0.8.0
Documenting radicle-crypto v0.11.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-crypto)
Checking radicle-cob v0.13.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-cob)
Documenting radicle-crdt v0.1.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-crdt)
Documenting radicle-cob v0.13.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-cob)
Checking radicle v0.14.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle)
Documenting radicle v0.14.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle)
Checking radicle-cli-test v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-cli-test)
Checking radicle-fetch v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-fetch)
Documenting radicle-fetch v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-fetch)
Documenting radicle-cli-test v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-cli-test)
Documenting radicle-cli v0.12.1 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-cli)
Documenting radicle-node v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-node)
Checking radicle-remote-helper v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-remote-helper)
Documenting radicle-remote-helper v0.10.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-remote-helper)
Documenting radicle-tools v0.9.0 (/26e38854-c259-4494-8c47-30a5dd6aea1a/w/radicle-tools)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 10.33s
Generated /26e38854-c259-4494-8c47-30a5dd6aea1a/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.15s
Running unittests src/lib.rs (target/debug/deps/radicle-03699755fa65dbd9)
running 179 tests
test canonical::formatter::test::ascii_control_characters ... ok
test canonical::formatter::test::ordered_nested_object ... ok
test canonical::formatter::test::securesystemslib_asserts ... ok
test cob::cache::migrations::_2::tests::test_migration_2 ... ok
test cob::common::test::test_color ... ok
test cob::cache::tests::test_check_version ... ok
test cob::cache::migrations::_2::tests::test_patch_json_deserialization ... ok
test cob::common::test::test_emojis ... ok
test cob::cache::tests::test_migrate_to ... 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_remove_delegate_concurrent ... ok
test cob::identity::test::test_identity_reject_concurrent ... ok
test cob::identity::test::test_identity_updates ... 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::issue::test::test_embeds ... ok
test cob::identity::test::test_identity_updates_concurrent ... ok
test cob::identity::test::test_identity_updates_concurrent_outdated ... ok
test cob::issue::test::test_embeds_edit ... ok
test cob::issue::test::test_invalid_actions ... ok
test cob::issue::test::test_concurrency ... ok
test cob::issue::test::test_invalid_tx_reference ... ok
test cob::issue::test::test_invalid_cob ... ok
test cob::issue::test::test_invalid_tx ... ok
test cob::issue::test::test_issue_all ... ok
test cob::issue::test::test_issue_comment ... ok
test cob::issue::test::test_issue_create_and_assign ... ok
test cob::issue::test::test_issue_comment_redact ... ok
test cob::issue::test::test_issue_create_and_change_state ... ok
test cob::issue::test::test_issue_create_and_get ... ok
test cob::issue::test::test_issue_create_and_reassign ... ok
test cob::issue::test::test_issue_create_and_unassign ... ok
test cob::issue::test::test_issue_edit_description ... ok
test cob::issue::test::test_issue_multilines ... ok
test cob::issue::test::test_issue_edit ... ok
test cob::issue::test::test_issue_state_serde ... ok
test cob::issue::test::test_ordering ... ok
test cob::issue::test::test_issue_label ... ok
test cob::patch::cache::tests::test_find_by_revision ... ok
test cob::issue::test::test_issue_react ... ok
test cob::patch::cache::tests::test_get ... ok
test cob::patch::cache::tests::test_is_empty ... ok
test cob::issue::test::test_issue_reply ... ok
test cob::patch::cache::tests::test_list ... ok
test cob::patch::test::test_json ... ok
test cob::patch::test::test_json_serialization ... ok
test cob::patch::cache::tests::test_remove ... 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::cache::tests::test_counts ... 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_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_remove_summary ... 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 git::canonical::rules::tests::test_canonical ... FAILED
test git::canonical::rules::tests::test_deserialization ... ok
test git::canonical::rules::tests::test_deserialize_extensions ... ok
test git::canonical::rules::tests::test_order ... ok
test git::canonical::rules::tests::test_roundtrip ... ok
test cob::patch::test::test_patch_review_revision_redact ... ok
test cob::patch::test::test_patch_update ... ok
test git::canonical::rules::tests::test_special_branches ... ok
test git::canonical::rules::tests::test_rule_validate_success ... 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 git::canonical::rules::tests::test_rule_validate_failures ... 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 identity::doc::test::test_canonical_doc ... ok
test git::canonical::tests::test_quorum_properties ... ok
test identity::doc::test::test_canonical_example ... ok
test identity::doc::test::test_duplicate_dids ... ok
test identity::doc::test::test_migrate_v1 ... ok
test identity::doc::test::test_not_found ... ok
test identity::doc::test::test_version_out_of_range ... ok
test identity::doc::test::test_visibility_json ... ok
test identity::doc::v1::test::test_parse_version ... ok
test identity::project::test::test_project_name ... ok
test node::address::store::test::test_alias ... ok
test node::address::store::test::test_disconnected ... ok
test node::address::store::test::test_disconnected_ban ... ok
test node::address::store::test::test_empty ... 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::policy::store::test::test_repo_policy ... 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::refs::store::test::test_set_and_get ... ok
test node::routing::test::test_count ... ok
test node::routing::test::test_entries ... ok
test node::routing::test::test_insert_and_get ... ok
test node::routing::test::test_insert_and_get_resources ... ok
test identity::doc::test::test_max_delegates ... 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_insert_and_remove ... ok
test node::routing::test::test_remove_many ... ok
test node::routing::test::test_remove_redundant ... ok
test node::routing::test::test_update_existing_multi ... ok
test node::routing::test::test_prune ... ok
test node::test::test_alias ... ok
test node::test::test_command_result ... ok
test node::timestamp::tests::test_timestamp_max ... ok
test node::test::test_user_agent ... ok
test profile::test::canonicalize_home ... ok
test profile::test::test_config ... ok
test cob::thread::tests::prop_ordering ... ok
test rad::tests::test_init ... ok
test rad::tests::test_checkout ... ok
test rad::tests::test_fork ... ok
test serde_ext::test::test_localtime ... ok
test serde_ext::test::test_localtime_ext ... ok
test storage::git::tests::test_sign_refs ... ok
test storage::git::transport::local::url::test::test_url_parse ... 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::refs::tests::prop_canonical_roundtrip ... ok
test storage::git::tests::test_references_of ... 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
failures:
---- git::canonical::rules::tests::test_canonical stdout ----
thread 'git::canonical::rules::tests::test_canonical' panicked at radicle/src/git/canonical/rules.rs:741:39:
called `Result::unwrap()` on an `Err` value: Error { code: -3, klass: 7, message: "config value 'user.name' was not found" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
git::canonical::rules::tests::test_canonical
test result: FAILED. 178 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.28s
error: test failed, to rerun pass `-p radicle --lib`
Running unittests src/lib.rs (target/debug/deps/radicle_cli-9663eed14d5ceed2)
running 14 tests
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 commands::rad_patch::review::builder::tests::test_review_comments_basic ... ok
test commands::rad_patch::review::builder::tests::test_review_comments_multiline ... 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_strip_comments ... ok
test terminal::format::test::test_bytes ... 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-eee4d28fa81fea6b)
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-bc05ac72fe2992e9)
running 98 tests
test framework_home ... ok
test git_push_amend ... ok
test git_push_and_fetch ... ok
test git_push_canonical_tags ... ok
test git_push_diverge ... ok
test git_push_rollback ... ok
test rad_auth_errors ... ok
test rad_block ... ok
test rad_auth ... ok
test rad_checkout ... ok
test git_tag ... ok
test git_push_converge ... ok
test rad_clone ... 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_cob_show ... ok
test rad_clone_partial_fail ... ok
test rad_config ... ok
test rad_cob_update_identity ... ok
test rad_cob_update ... ok
test rad_diff ... ok
test rad_cref ... ok
test rad_id_collaboration ... ignored, slow
test rad_id ... ok
test rad_id_conflict ... ok
test rad_id_private ... ok
test rad_id_threshold ... ignored, not implemented
test rad_id_threshold_soft_fork ... ok
test rad_id_unknown_field ... ok
test rad_id_update_delete_field ... ok
test rad_id_multi_delegate ... 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_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_fetch ... ok
test rad_fork ... ok
test rad_init_with_existing_remote ... ok
test rad_inspect ... ok
test rad_issue ... ok
test rad_job ... ok
test rad_merge_no_ff ... ok
test rad_merge_after_update ... ok
test rad_merge_via_push ... ok
test rad_node_connect ... ok
test rad_patch ... ok
test rad_node ... ok
test rad_patch_ahead_behind ... 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_and_clone ... ok
test rad_patch_draft ... ok
test rad_patch_edit ... ok
test rad_init_sync_timeout ... ok
test rad_patch_fetch_2 ... ok
test rad_patch_merge_draft ... ok
test rad_patch_fetch_1 ... ok
test rad_patch_delete ... ok
test rad_patch_revert_merge ... ok
test rad_patch_update ... ok
test rad_patch_open_explore ... ok
test rad_publish ... ok
test rad_patch_via_push ... ok
test rad_review_by_hunk ... ok
test rad_seed_and_follow ... ok
test rad_remote ... ok
test rad_seed_many ... ok
test rad_self ... ok
test rad_sync_without_node ... ok
test rad_unseed ... ok
test rad_push_and_pull_patches ... ok
test rad_unseed_many ... ok
test rad_watch ... ok
test test_clone_without_seeds ... ok
test rad_sync ... 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. 96 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 72.87s
Running unittests src/lib.rs (target/debug/deps/radicle_cli_test-6c82b19cf1b96249)
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.01s
Running unittests src/lib.rs (target/debug/deps/radicle_cob-64b7462b16429754)
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::list_cobs ... ok
test tests::update_cob ... ok
test tests::traverse_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-867e4df59c602376)
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 gset::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 lwwmap::tests::prop_semilattice ... ok
test gmap::tests::prop_semilattice ... ok
test lwwset::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-b68221251e92334a)
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::prop_key_equality ... ok
test tests::test_encode_decode ... ok
test tests::test_e25519_dh ... 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.80s
Running unittests src/lib.rs (target/debug/deps/radicle_dag-569b0a6854e86d64)
running 20 tests
test tests::test_dependencies ... ok
test tests::test_contains ... ok
test tests::test_cycle ... ok
test tests::test_complex ... ok
test tests::test_diamond ... ok
test tests::test_fold_multiple_roots ... ok
test tests::test_fold_diamond ... ok
test tests::test_fold_reject ... ok
test tests::test_fold_sorting_1 ... ok
test tests::test_fold_sorting_2 ... ok
test tests::test_get ... ok
test tests::test_is_empty ... ok
test tests::test_len ... ok
test tests::test_merge_1 ... ok
test tests::test_merge_2 ... ok
test tests::test_prune_1 ... ok
test tests::test_prune_2 ... ok
test tests::test_remove ... ok
test tests::test_siblings ... ok
test tests::test_prune_by_sorting ... 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-7e208e447a4cca13)
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-fcd5b77142f56bb9)
running 113 tests
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 control::tests::test_control_socket ... ok
test service::limiter::test::test_limitter_different_rates ... ok
test service::limiter::test::test_limitter_multi ... ok
test service::limiter::test::test_limitter_refill ... ok
test service::gossip::store::test::test_announced ... 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_concurrent_fetches ... ok
test tests::e2e::test_fetch_unseeded ... ok
test tests::e2e::test_fetch_up_to_date ... ok
test tests::e2e::test_inventory_sync_basic ... 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::e2e::test_inventory_sync_star ... ok
test tests::test_announcement_rebroadcast ... ok
test tests::test_announcement_rebroadcast_duplicates ... ok
test tests::test_announcement_relay ... ok
test tests::test_announcement_rebroadcast_timestamp_filtered ... ok
test tests::test_disconnecting_unresponsive_peer ... ok
test tests::test_connection_kept_alive ... 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_persistent_peer_reconnect_attempt ... ok
test tests::test_inventory_pruning ... ok
test tests::test_persistent_peer_reconnect_success ... 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::tests::prop_pubkey ... ok
test wire::tests::prop_refs ... ok
test wire::tests::prop_signature ... ok
test wire::tests::prop_signed_refs ... 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_vec ... 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::message::tests::test_refs_ann_max_size ... 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.80s
Running unittests src/main.rs (target/debug/deps/radicle_node-3bf71b1e57be1eb7)
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-c5790cb1f0922f49)
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-eb1ddf4511a65396)
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_truncate ... ok
test element::test::test_width ... ok
test table::test::test_table_border ... ok
test table::test::test_table_border_maximized ... ok
test table::test::test_table ... 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_table_unicode_truncate ... ok
test table::test::test_truncate ... ok
test textarea::test::test_wrapping ... ok
test textarea::test::test_wrapping_code_block ... ok
test textarea::test::test_wrapping_fenced_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-784408275be8ee3b)
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-efda64a728ce57e9)
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-bb9f2c5eaa5ae0c9)
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-583ee6ad3ffdf49a)
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-7426bc5fdb0eaf67)
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-3e0e01f63c38e5e9)
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-06b4d9219da3b198)
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>::push (line 122) ... ok
test radicle-node/src/bounded.rs - bounded::BoundedVec<T,N>::with_capacity (line 66) ... ok
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.56s
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
error: 1 target failed:
`-p radicle --lib`
Exit code: 101
{
"response": "finished",
"result": "failure"
}