From 820122516675cd6a2151dc459610375161e40534 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Wed, 18 Jun 2025 14:36:19 +0200 Subject: [PATCH] cli: test canonical tags Add a test to exercise adding canonical reference rules and pushing new canonical tags. A change in `rad id` was required to allow adding a new payload to the document, i.e. one that does not already exist. Adding rules is also documented in the man page for `rad id`. --- .../examples/git/git-push-canonical-tags.md | 213 ++++++++++++++++++ crates/radicle-cli/tests/commands.rs | 42 ++++ rad-id.1.adoc | 48 +++- 3 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 crates/radicle-cli/examples/git/git-push-canonical-tags.md diff --git a/crates/radicle-cli/examples/git/git-push-canonical-tags.md b/crates/radicle-cli/examples/git/git-push-canonical-tags.md new file mode 100644 index 00000000..19e6697b --- /dev/null +++ b/crates/radicle-cli/examples/git/git-push-canonical-tags.md @@ -0,0 +1,213 @@ +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.crefs`. 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 id update --title "Add canonical reference rules" --payload xyz.radicle.crefs rules '{ "refs/tags/*": { "threshold": 1, "allow": "delegates" }, "refs/tags/qa/*": { "threshold": 1, "allow": "delegates" }}' +✓ Identity revision [..] created +╭────────────────────────────────────────────────────────────────────────╮ +│ Title Add canonical reference rules │ +│ Revision c3349f07bfe6a82bbeb2989d2de4a918408f9831 │ +│ Blob 85fa09e2de93b825d5231778dbb34143004a4bca │ +│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │ +│ State accepted │ +│ Quorum yes │ +├────────────────────────────────────────────────────────────────────────┤ +│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │ +╰────────────────────────────────────────────────────────────────────────╯ + +@@ -1,13 +1,25 @@ + { + "payload": { ++ "xyz.radicle.crefs": { ++ "rules": { ++ "refs/tags/*": { ++ "allow": "delegates", ++ "threshold": 1 ++ }, ++ "refs/tags/qa/*": { ++ "allow": "delegates", ++ "threshold": 1 ++ } ++ } ++ }, + "xyz.radicle.project": { + "defaultBranch": "master", + "description": "Radicle Heartwood Protocol & Stack", + "name": "heartwood" + } + }, + "delegates": [ + "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi" + ], + "threshold": 1 + } +``` + +Now, Alice will create a tag and push it: + +``` ~alice +$ git tag v1.0-hotfix +``` + +``` ~alice (stderr) +$ git push rad --tags +✓ Canonical reference refs/tags/v1.0-hotfix updated to f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 +✓ Synced with 1 seed(s) +To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/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://z42hL2jL4XNk6K8oHQaSWfMgCL7ji + * [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:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from the network, found 1 potential seed(s). +✓ Target met: 1 seed(s) +✓ 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:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from the network, found 1 potential seed(s). +✓ Target met: 1 seed(s) +✓ 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 +27ab0d77a95581c59ca9d30e679ceb06a9f758db +$ rad id update --title "Update canonical reference rules" --payload xyz.radicle.crefs rules '{ "refs/tags/*": { "threshold": 2, "allow": "delegates" }, "refs/tags/qa/*": { "threshold": 1, "allow": "delegates" } }' -q +dace164ba43fa51802697ec28d0b1965a9d7808b +``` + +**Note:** here we have to specify all the rules again to update the `threshold`. +In reality, you can use `rad id update --edit` and edit the payload in your +editor instead. + +``` ~bob +$ rad sync -f +Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from the network, found 1 potential seed(s). +✓ Target met: 1 seed(s) +🌱 Fetched from z6MknSL…StBU8Vi +$ rad id accept dace164ba43fa51802697ec28d0b1965a9d7808b -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 commit 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 seed(s) +To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/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://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/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 reference refs/tags/v2.0 updated to f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 +✓ Synced with 1 seed(s) +To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/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 reference refs/tags/qa/v2.1 updated to f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 +✓ Synced with 1 seed(s) +To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk + * [new tag] qa/v2.1 -> qa/v2.1 +``` diff --git a/crates/radicle-cli/tests/commands.rs b/crates/radicle-cli/tests/commands.rs index e629f254..60b13a42 100644 --- a/crates/radicle-cli/tests/commands.rs +++ b/crates/radicle-cli/tests/commands.rs @@ -2517,6 +2517,48 @@ fn git_tag() { .unwrap(); } +#[test] +fn git_push_canonical_tags() { + let mut environment = Environment::new(); + let alice = environment.node("alice"); + let bob = environment.node("bob"); + + let rid = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); + + fixtures::repository(environment.work(&alice)); + + test( + "examples/rad-init.md", + environment.work(&alice), + Some(&alice.home), + [], + ) + .unwrap(); + + let alice = alice.spawn(); + let mut bob = bob.spawn(); + + bob.connect(&alice).converge([&alice]); + bob.clone(rid, environment.work(&bob)).unwrap(); + formula( + &environment.tempdir(), + "examples/git/git-push-canonical-tags.md", + ) + .unwrap() + .home( + "alice", + environment.work(&alice), + [("RAD_HOME", alice.home.path().display())], + ) + .home( + "bob", + environment.work(&bob), + [("RAD_HOME", bob.home.path().display())], + ) + .run() + .unwrap(); +} + #[test] fn rad_workflow() { let mut environment = Environment::new(); diff --git a/rad-id.1.adoc b/rad-id.1.adoc index 91ab8643..73a57d2f 100644 --- a/rad-id.1.adoc +++ b/rad-id.1.adoc @@ -18,7 +18,7 @@ rad-id - Manage changes to a Radicle repository's identity. *rad id* _edit_ [--title ] [--description ] [