diff --git a/radicle-cli/examples/rad-cob-log.md b/radicle-cli/examples/rad-cob-log.md index e9a061ce..2fa0c056 100644 --- a/radicle-cli/examples/rad-cob-log.md +++ b/radicle-cli/examples/rad-cob-log.md @@ -7,7 +7,7 @@ $ rad issue open --title "flux capacitor underpowered" --description "Flux capac ╭─────────────────────────────────────────────────────────╮ │ Title flux capacitor underpowered │ │ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Status open │ │ │ │ Flux capacitor power requirements exceed current supply │ @@ -18,11 +18,11 @@ The issue is now listed under our project. ``` $ rad issue list -╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ● ID Title Author Labels Assignees Opened │ -├───────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ ● d87dcfe flux capacitor underpowered z6MknSL…StBU8Vi (you) now │ -╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭──────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Labels Assignees Opened │ +├──────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● d87dcfe flux capacitor underpowered alice (you) now │ +╰──────────────────────────────────────────────────────────────────────────────────────────╯ ``` Let's create a patch, too. @@ -42,11 +42,11 @@ Patch can be listed. ``` $ rad patch -╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ● ID Title Author Reviews Head + - Updated │ -├──────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ ● aa45913 Define power requirements z6MknSL…StBU8Vi (you) - 3e674d1 +0 -0 now │ -╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭─────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Reviews Head + - Updated │ +├─────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● aa45913 Define power requirements alice (you) - 3e674d1 +0 -0 now │ +╰─────────────────────────────────────────────────────────────────────────────────────────╯ ``` Both issue and patch COBs can be listed. diff --git a/radicle-cli/examples/rad-cob-show.md b/radicle-cli/examples/rad-cob-show.md index 12d035f4..29ebfd1e 100644 --- a/radicle-cli/examples/rad-cob-show.md +++ b/radicle-cli/examples/rad-cob-show.md @@ -10,7 +10,7 @@ $ rad issue open --title "spice harvester broken" --description "Fremen have att ╭──────────────────────────────────────────────────╮ │ Title spice harvester broken │ │ Issue 9de644864342d7a505eb8d58d1ef20e5bb05de2e │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Status open │ │ │ │ Fremen have attacked, maybe we went too far? │ @@ -21,11 +21,11 @@ The issue is now listed under our project. ``` $ rad issue list -╭──────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ● ID Title Author Labels Assignees Opened │ -├──────────────────────────────────────────────────────────────────────────────────────────────┤ -│ ● 9de6448 spice harvester broken z6MknSL…StBU8Vi (you) now │ -╰──────────────────────────────────────────────────────────────────────────────────────────────╯ +╭─────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Labels Assignees Opened │ +├─────────────────────────────────────────────────────────────────────────────────────┤ +│ ● 9de6448 spice harvester broken alice (you) now │ +╰─────────────────────────────────────────────────────────────────────────────────────╯ ``` Let's create a patch, too. @@ -45,11 +45,11 @@ Patch can be listed. ``` $ rad patch -╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ● ID Title Author Reviews Head + - Updated │ -├────────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ ● d1f7f86 Start drafting peace treaty z6MknSL…StBU8Vi (you) - 575ed68 +0 -0 now │ -╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭───────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Reviews Head + - Updated │ +├───────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● d1f7f86 Start drafting peace treaty alice (you) - 575ed68 +0 -0 now │ +╰───────────────────────────────────────────────────────────────────────────────────────────╯ ``` Both issue and patch COBs can be listed. diff --git a/radicle-cli/examples/rad-id-update-delete-field.md b/radicle-cli/examples/rad-id-update-delete-field.md index 968dd829..b34cbd4b 100644 --- a/radicle-cli/examples/rad-id-update-delete-field.md +++ b/radicle-cli/examples/rad-id-update-delete-field.md @@ -3,18 +3,18 @@ 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 -╭───────────────────────────────────────────────────────────────────╮ -│ Title Add field │ -│ Revision a8a9fee6c4f83578ab132d375f1da0c81282bef3 │ -│ Blob fbe268d13e60f1f3a1972e0ccd592f3cdecf08b5 │ -│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │ -│ State accepted │ -│ Quorum yes │ -│ │ -│ Add a new 'web' field │ -├───────────────────────────────────────────────────────────────────┤ -│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) │ -╰───────────────────────────────────────────────────────────────────╯ +╭────────────────────────────────────────────────────────────────────────╮ +│ Title Add field │ +│ Revision a8a9fee6c4f83578ab132d375f1da0c81282bef3 │ +│ Blob fbe268d13e60f1f3a1972e0ccd592f3cdecf08b5 │ +│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │ +│ State accepted │ +│ Quorum yes │ +│ │ +│ Add a new 'web' field │ +├────────────────────────────────────────────────────────────────────────┤ +│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │ +╰────────────────────────────────────────────────────────────────────────╯ @@ -1,13 +1,14 @@ { @@ -39,18 +39,18 @@ 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 -╭───────────────────────────────────────────────────────────────────╮ -│ Title Delete field │ -│ Revision d373c35876833105f8aafed8b610660b5737cd67 │ -│ Blob d96f425412c9f8ad5d9a9a05c9831d0728e2338d │ -│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │ -│ State accepted │ -│ Quorum yes │ -│ │ -│ Delete 'web' │ -├───────────────────────────────────────────────────────────────────┤ -│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) │ -╰───────────────────────────────────────────────────────────────────╯ +╭────────────────────────────────────────────────────────────────────────╮ +│ Title Delete field │ +│ Revision d373c35876833105f8aafed8b610660b5737cd67 │ +│ Blob d96f425412c9f8ad5d9a9a05c9831d0728e2338d │ +│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │ +│ State accepted │ +│ Quorum yes │ +│ │ +│ Delete 'web' │ +├────────────────────────────────────────────────────────────────────────┤ +│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │ +╰────────────────────────────────────────────────────────────────────────╯ @@ -1,14 +1,13 @@ { diff --git a/radicle-cli/examples/rad-inspect.md b/radicle-cli/examples/rad-inspect.md index 493eec68..548e5711 100644 --- a/radicle-cli/examples/rad-inspect.md +++ b/radicle-cli/examples/rad-inspect.md @@ -48,7 +48,7 @@ $ rad inspect --payload } } $ rad inspect --delegates -did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice) ``` Finally, the `--history` flag allows you to examine the identity document's diff --git a/radicle-cli/examples/rad-issue.md b/radicle-cli/examples/rad-issue.md index f9cb6f51..21e23723 100644 --- a/radicle-cli/examples/rad-issue.md +++ b/radicle-cli/examples/rad-issue.md @@ -8,7 +8,7 @@ $ rad issue open --title "flux capacitor underpowered" --description "Flux capac ╭─────────────────────────────────────────────────────────╮ │ Title flux capacitor underpowered │ │ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Status open │ │ │ │ Flux capacitor power requirements exceed current supply │ @@ -19,11 +19,11 @@ The issue is now listed under our project. ``` $ rad issue list -╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ● ID Title Author Labels Assignees Opened │ -├───────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ ● d87dcfe flux capacitor underpowered z6MknSL…StBU8Vi (you) now │ -╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭──────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Labels Assignees Opened │ +├──────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● d87dcfe flux capacitor underpowered alice (you) now │ +╰──────────────────────────────────────────────────────────────────────────────────────────╯ ``` Show the issue information issue. @@ -33,7 +33,7 @@ $ rad issue show d87dcfe ╭─────────────────────────────────────────────────────────╮ │ Title flux capacitor underpowered │ │ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Status open │ │ │ │ Flux capacitor power requirements exceed current supply │ @@ -59,11 +59,11 @@ It will now show in the list of issues assigned to us, along with the new label. ``` $ rad issue list --assigned -╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ● ID Title Author Labels Assignees Opened │ -├───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ ● d87dcfe flux capacitor underpowered z6MknSL…StBU8Vi (you) good-first-issue z6MknSL…StBU8Vi now │ -╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Labels Assignees Opened │ +├────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● d87dcfe flux capacitor underpowered alice (you) good-first-issue alice now │ +╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` Note: this can always be undone with the `unassign` subcommand. @@ -91,16 +91,16 @@ $ rad issue show d87dcfe8c2b3200e78b128d9b959cfdf7063fefe ╭─────────────────────────────────────────────────────────╮ │ Title flux capacitor underpowered │ │ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Labels good-first-issue │ │ Status open │ │ │ │ Flux capacitor power requirements exceed current supply │ ├─────────────────────────────────────────────────────────┤ -│ z6MknSL…StBU8Vi (you) now 2193e87 │ +│ alice (you) now 2193e87 │ │ The flux capacitor needs 1.21 Gigawatts │ ├─────────────────────────────────────────────────────────┤ -│ z6MknSL…StBU8Vi (you) now 880fdcd │ +│ alice (you) now 880fdcd │ │ More power! │ ╰─────────────────────────────────────────────────────────╯ ``` diff --git a/radicle-cli/examples/rad-node.md b/radicle-cli/examples/rad-node.md index b0e3e395..f5b2688e 100644 --- a/radicle-cli/examples/rad-node.md +++ b/radicle-cli/examples/rad-node.md @@ -34,9 +34,9 @@ $ rad follow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --alias Bo ✓ Follow policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (Bob) ``` -Now, when we use the `rad seed` command we will see -information for repositories that we seed -- in this case a -repository that was already created: +Now, when we use the `rad seed` command we will see information for +repositories that we seed -- in this case a repository that was already +created: ``` $ rad seed @@ -87,6 +87,35 @@ $ rad node stop ✗ Stopping node... error: node is not running ``` +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 node routing --nid z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +``` + +Likewise, if we seed a repository we don't have locally, it won't show up as +part of our inventory: +``` +$ rad seed rad:z3trNYnLWS11cJWC6BbxDs5niGo82 +[...] +$ rad node routing --nid z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +``` + +But if we start seeding the repository we have locally again, it'll show +up in our inventory: +``` +$ rad seed rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji +[...] +$ rad node routing --nid z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +╭─────────────────────────────────────────────────────╮ +│ RID NID │ +├─────────────────────────────────────────────────────┤ +│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji z6MknSL…StBU8Vi │ +╰─────────────────────────────────────────────────────╯ +``` + Some commands also give us a hint if the node isn't running: ``` (fail) diff --git a/radicle-cli/examples/rad-patch-ahead-behind.md b/radicle-cli/examples/rad-patch-ahead-behind.md index c7abb5b3..b0154788 100644 --- a/radicle-cli/examples/rad-patch-ahead-behind.md +++ b/radicle-cli/examples/rad-patch-ahead-behind.md @@ -45,11 +45,11 @@ To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkE When listing, we see that it has one addition: ``` $ rad patch list -╭─────────────────────────────────────────────────────────────────────────────────╮ -│ ● ID Title Author Reviews Head + - Updated │ -├─────────────────────────────────────────────────────────────────────────────────┤ -│ ● 217f050 Add Alan z6MknSL…StBU8Vi (you) - 5c88a79 +1 -0 now │ -╰─────────────────────────────────────────────────────────────────────────────────╯ +╭────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Reviews Head + - Updated │ +├────────────────────────────────────────────────────────────────────────┤ +│ ● 217f050 Add Alan alice (you) - 5c88a79 +1 -0 now │ +╰────────────────────────────────────────────────────────────────────────╯ ``` When showing the patch, we see that it is `ahead 1, behind 1`, since master has @@ -59,7 +59,7 @@ $ rad patch show -v -p 217f050 ╭────────────────────────────────────────────────────╮ │ Title Add Alan │ │ Patch 217f050f8891def8fb863f7c0b4f85c89f97299d │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 5c88a79d75f5c2b4cc51ee6f163d2db91ee198d7 │ │ Base f64fb2c8fe28f7c458c72ec8d700373924794943 │ │ Branches feature/1 │ @@ -68,7 +68,7 @@ $ rad patch show -v -p 217f050 ├────────────────────────────────────────────────────┤ │ 5c88a79 Add Alan │ ├────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (5c88a79) now │ +│ ● opened by alice (you) (5c88a79) now │ ╰────────────────────────────────────────────────────╯ commit 5c88a79d75f5c2b4cc51ee6f163d2db91ee198d7 @@ -105,7 +105,7 @@ $ rad patch show -v e22ff008e2a0ed47262890d13263031d7555b555 ╭────────────────────────────────────────────────────╮ │ Title Add Mel │ │ Patch e22ff008e2a0ed47262890d13263031d7555b555 │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 7f63fcbcf23fc39eea784c091ad3d20d7e4bd005 │ │ Base f64fb2c8fe28f7c458c72ec8d700373924794943 │ │ Branches feature/2 │ @@ -115,7 +115,7 @@ $ rad patch show -v e22ff008e2a0ed47262890d13263031d7555b555 │ 7f63fcb Add Mel │ │ 5c88a79 Add Alan │ ├────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (7f63fcb) now │ +│ ● opened by alice (you) (7f63fcb) now │ ╰────────────────────────────────────────────────────╯ ``` @@ -140,7 +140,7 @@ $ rad patch show -v a467ffa260c4fbe355b6fb550ba0c4956078717e ╭────────────────────────────────────────────────────╮ │ Title Add Mel #2 │ │ Patch a467ffa260c4fbe355b6fb550ba0c4956078717e │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 7f63fcbcf23fc39eea784c091ad3d20d7e4bd005 │ │ Base 5c88a79d75f5c2b4cc51ee6f163d2db91ee198d7 │ │ Branches feature/2 │ @@ -149,6 +149,6 @@ $ rad patch show -v a467ffa260c4fbe355b6fb550ba0c4956078717e ├────────────────────────────────────────────────────┤ │ 7f63fcb Add Mel │ ├────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (7f63fcb) now │ +│ ● opened by alice (you) (7f63fcb) now │ ╰────────────────────────────────────────────────────╯ ``` diff --git a/radicle-cli/examples/rad-patch-change-base.md b/radicle-cli/examples/rad-patch-change-base.md index 6070dbf6..12b295a9 100644 --- a/radicle-cli/examples/rad-patch-change-base.md +++ b/radicle-cli/examples/rad-patch-change-base.md @@ -47,7 +47,7 @@ $ rad patch show 183d343ab47d7fe18baf1b24b7209ad033d7fe5c -v ╭────────────────────────────────────────────────────╮ │ Title Add README, just for the fun │ │ Patch 183d343ab47d7fe18baf1b24b7209ad033d7fe5c │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │ │ Base f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 │ │ Branches add-readme │ @@ -57,7 +57,7 @@ $ rad patch show 183d343ab47d7fe18baf1b24b7209ad033d7fe5c -v │ 27857ec Add README, just for the fun │ │ 3e674d1 Define power requirements │ ├────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (27857ec) now │ +│ ● opened by alice (you) (27857ec) now │ ╰────────────────────────────────────────────────────╯ ``` @@ -78,7 +78,7 @@ $ rad patch show 183d343 -v ╭─────────────────────────────────────────────────────────────────────╮ │ Title Add README, just for the fun │ │ Patch 183d343ab47d7fe18baf1b24b7209ad033d7fe5c │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │ │ Base 3e674d1a1df90807e934f9ae5da2591dd6848a33 │ │ Branches add-readme │ @@ -87,7 +87,7 @@ $ rad patch show 183d343 -v ├─────────────────────────────────────────────────────────────────────┤ │ 27857ec Add README, just for the fun │ ├─────────────────────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (27857ec) now │ +│ ● opened by alice (you) (27857ec) now │ │ ↑ updated to ebe76f9c2148eb595d7a745f82275786bf3458c3 (27857ec) now │ ╰─────────────────────────────────────────────────────────────────────╯ ``` diff --git a/radicle-cli/examples/rad-patch-checkout-revision.md b/radicle-cli/examples/rad-patch-checkout-revision.md index ef48a2e2..f135130f 100644 --- a/radicle-cli/examples/rad-patch-checkout-revision.md +++ b/radicle-cli/examples/rad-patch-checkout-revision.md @@ -18,7 +18,7 @@ $ rad patch show aa45913 ╭─────────────────────────────────────────────────────────────────────╮ │ Title Define power requirements │ │ Patch aa45913e757cacd46972733bddee5472c78fa32a │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 639f44a25145a37f747f3c84265037a9461e44c5 │ │ Branches patch/aa45913 │ │ Commits ahead 3, behind 0 │ @@ -30,7 +30,7 @@ $ rad patch show aa45913 │ 27857ec Add README, just for the fun │ │ 3e674d1 Define power requirements │ ├─────────────────────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (3e674d1) now │ +│ ● opened by alice (you) (3e674d1) now │ │ ↑ updated to 3156bed9d64d4675d6cf56612d217fc5f4e8a53a (27857ec) now │ │ ↑ updated to 2f5324f61e05cda65b667eeea02570d077a8e724 (639f44a) now │ ╰─────────────────────────────────────────────────────────────────────╯ diff --git a/radicle-cli/examples/rad-patch-draft.md b/radicle-cli/examples/rad-patch-draft.md index d391ec59..b5167217 100644 --- a/radicle-cli/examples/rad-patch-draft.md +++ b/radicle-cli/examples/rad-patch-draft.md @@ -21,7 +21,7 @@ $ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97 ╭────────────────────────────────────────────────────╮ │ Title Nothing yet │ │ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │ │ Branches cloudhead/draft │ │ Commits ahead 1, behind 0 │ @@ -29,7 +29,7 @@ $ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97 ├────────────────────────────────────────────────────┤ │ 2a46583 Nothing to see here.. │ ├────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (2a46583) [ .. ] │ +│ ● opened by alice (you) (2a46583) [ .. ] │ ╰────────────────────────────────────────────────────╯ ``` @@ -44,7 +44,7 @@ $ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97 ╭────────────────────────────────────────────────────╮ │ Title Nothing yet │ │ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │ │ Branches cloudhead/draft │ │ Commits ahead 1, behind 0 │ @@ -52,7 +52,7 @@ $ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97 ├────────────────────────────────────────────────────┤ │ 2a46583 Nothing to see here.. │ ├────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (2a46583) [ .. ] │ +│ ● opened by alice (you) (2a46583) [ .. ] │ ╰────────────────────────────────────────────────────╯ ``` @@ -65,7 +65,7 @@ $ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97 ╭────────────────────────────────────────────────────╮ │ Title Nothing yet │ │ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │ │ Branches cloudhead/draft │ │ Commits ahead 1, behind 0 │ @@ -73,6 +73,6 @@ $ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97 ├────────────────────────────────────────────────────┤ │ 2a46583 Nothing to see here.. │ ├────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (2a46583) [ .. ] │ +│ ● opened by alice (you) (2a46583) [ .. ] │ ╰────────────────────────────────────────────────────╯ ``` diff --git a/radicle-cli/examples/rad-patch-edit.md b/radicle-cli/examples/rad-patch-edit.md index 766090a9..0a6afe30 100644 --- a/radicle-cli/examples/rad-patch-edit.md +++ b/radicle-cli/examples/rad-patch-edit.md @@ -44,7 +44,7 @@ $ rad patch show 89f7afb ╭─────────────────────────────────────────────────────────────────────╮ │ Title Add README, just for the fun │ │ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 8945f6189adf027892c85ac57f7e9341049c2537 │ │ Branches changes │ │ Commits ahead 2, behind 0 │ @@ -53,7 +53,7 @@ $ rad patch show 89f7afb │ 8945f61 Define the LICENSE │ │ 03c02af Add README, just for the fun │ ├─────────────────────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (03c02af) now │ +│ ● opened by alice (you) (03c02af) now │ │ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │ ╰─────────────────────────────────────────────────────────────────────╯ ``` @@ -67,7 +67,7 @@ $ rad patch show 89f7afb ╭─────────────────────────────────────────────────────────────────────╮ │ Title Add Metadata │ │ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 8945f6189adf027892c85ac57f7e9341049c2537 │ │ Branches changes │ │ Commits ahead 2, behind 0 │ @@ -78,7 +78,7 @@ $ rad patch show 89f7afb │ 8945f61 Define the LICENSE │ │ 03c02af Add README, just for the fun │ ├─────────────────────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (03c02af) now │ +│ ● opened by alice (you) (03c02af) now │ │ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │ ╰─────────────────────────────────────────────────────────────────────╯ ``` @@ -95,7 +95,7 @@ $ rad patch show 89f7afb ╭─────────────────────────────────────────────────────────────────────╮ │ Title Add Metadata │ │ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 8945f6189adf027892c85ac57f7e9341049c2537 │ │ Branches changes │ │ Commits ahead 2, behind 0 │ @@ -106,7 +106,7 @@ $ rad patch show 89f7afb │ 8945f61 Define the LICENSE │ │ 03c02af Add README, just for the fun │ ├─────────────────────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (03c02af) now │ +│ ● opened by alice (you) (03c02af) now │ │ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │ ╰─────────────────────────────────────────────────────────────────────╯ ``` diff --git a/radicle-cli/examples/rad-patch-update.md b/radicle-cli/examples/rad-patch-update.md index b6ee4f80..1b897efc 100644 --- a/radicle-cli/examples/rad-patch-update.md +++ b/radicle-cli/examples/rad-patch-update.md @@ -16,7 +16,7 @@ $ rad patch show b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 ╭────────────────────────────────────────────────────╮ │ Title Not a real change │ │ Patch b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 51b2f0f77b9849bfaa3e9d3ff68ee2f57771d20c │ │ Branches feature/1 │ │ Commits ahead 1, behind 0 │ @@ -24,7 +24,7 @@ $ rad patch show b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 ├────────────────────────────────────────────────────┤ │ 51b2f0f Not a real change │ ├────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (51b2f0f) now │ +│ ● opened by alice (you) (51b2f0f) now │ ╰────────────────────────────────────────────────────╯ ``` @@ -57,7 +57,7 @@ $ rad patch show b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 ╭─────────────────────────────────────────────────────────────────────╮ │ Title Not a real change │ │ Patch b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 4d272148458a17620541555b1f0905c01658aa9f │ │ Branches feature/1 │ │ Commits ahead 2, behind 0 │ @@ -66,7 +66,7 @@ $ rad patch show b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 │ 4d27214 Rename readme file │ │ 51b2f0f Not a real change │ ├─────────────────────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (51b2f0f) now │ +│ ● opened by alice (you) (51b2f0f) now │ │ ↑ updated to ea7def3857f62f404606d7cd6490cd0de4eaebd1 (4d27214) now │ ╰─────────────────────────────────────────────────────────────────────╯ ``` diff --git a/radicle-cli/examples/rad-patch-via-push.md b/radicle-cli/examples/rad-patch-via-push.md index a9a210d7..5aba56af 100644 --- a/radicle-cli/examples/rad-patch-via-push.md +++ b/radicle-cli/examples/rad-patch-via-push.md @@ -23,7 +23,7 @@ $ rad patch show 6035d2f582afbe01ff23ea87528ae523d76875b6 ╭────────────────────────────────────────────────────╮ │ Title Add things #1 │ │ Patch 6035d2f582afbe01ff23ea87528ae523d76875b6 │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 42d894a83c9c356552a57af09ccdbd5587a99045 │ │ Branches feature/1 │ │ Commits ahead 1, behind 0 │ @@ -33,7 +33,7 @@ $ rad patch show 6035d2f582afbe01ff23ea87528ae523d76875b6 ├────────────────────────────────────────────────────┤ │ 42d894a Add things │ ├────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (42d894a) now │ +│ ● opened by alice (you) (42d894a) now │ ╰────────────────────────────────────────────────────╯ ``` @@ -96,12 +96,12 @@ And both patches: ``` $ rad patch -╭────────────────────────────────────────────────────────────────────────────────────────╮ -│ ● ID Title Author Reviews Head + - Updated │ -├────────────────────────────────────────────────────────────────────────────────────────┤ -│ ● 6035d2f Add things #1 z6MknSL…StBU8Vi (you) - 42d894a +0 -0 now │ -│ ● 9580891 Add more things z6MknSL…StBU8Vi (you) - 8b0ea80 +0 -0 now │ -╰────────────────────────────────────────────────────────────────────────────────────────╯ +╭───────────────────────────────────────────────────────────────────────────────╮ +│ ● 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 │ +╰───────────────────────────────────────────────────────────────────────────────╯ ``` To update our patch, we simply push commits to the upstream branch: @@ -136,7 +136,7 @@ $ rad patch show 9580891 ╭─────────────────────────────────────────────────────────────────────╮ │ Title Add more things │ │ Patch 95808913573cead52ad7b42c7b475260ec45c4b2 │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 02bef3fac41b2f98bb3c02b868a53ddfecb55b5f │ │ Branches feature/2 │ │ Commits ahead 2, behind 0 │ @@ -145,7 +145,7 @@ $ rad patch show 9580891 │ 02bef3f Improve code │ │ 8b0ea80 Add more things │ ├─────────────────────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (8b0ea80) now │ +│ ● opened by alice (you) (8b0ea80) now │ │ ↑ updated to d7040c6c97629c2b94f86fb639bebbff5de39697 (02bef3f) now │ ╰─────────────────────────────────────────────────────────────────────╯ ``` @@ -212,7 +212,7 @@ $ rad patch show 9580891 ╭─────────────────────────────────────────────────────────────────────╮ │ Title Add more things │ │ Patch 95808913573cead52ad7b42c7b475260ec45c4b2 │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 9304dbc445925187994a7a93222a3f8bde73b785 │ │ Branches feature/2 │ │ Commits ahead 2, behind 0 │ @@ -221,7 +221,7 @@ $ rad patch show 9580891 │ 9304dbc Amended commit │ │ 8b0ea80 Add more things │ ├─────────────────────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (8b0ea80) now │ +│ ● opened by alice (you) (8b0ea80) now │ │ ↑ updated to d7040c6c97629c2b94f86fb639bebbff5de39697 (02bef3f) now │ │ ↑ updated to 670d02794aa05afd6e0851f4aa848bc87c4712c7 (9304dbc) now │ ╰─────────────────────────────────────────────────────────────────────╯ diff --git a/radicle-cli/examples/rad-patch.md b/radicle-cli/examples/rad-patch.md index e48b6ff4..54b77ce9 100644 --- a/radicle-cli/examples/rad-patch.md +++ b/radicle-cli/examples/rad-patch.md @@ -35,18 +35,18 @@ It will now be listed as one of the project's open patches. ``` $ rad patch -╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ● ID Title Author Reviews Head + - Updated │ -├──────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ ● aa45913 Define power requirements z6MknSL…StBU8Vi (you) - 3e674d1 +0 -0 now │ -╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭─────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Reviews Head + - Updated │ +├─────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● aa45913 Define power requirements alice (you) - 3e674d1 +0 -0 now │ +╰─────────────────────────────────────────────────────────────────────────────────────────╯ ``` ``` $ rad patch show aa45913e757cacd46972733bddee5472c78fa32a -p ╭────────────────────────────────────────────────────╮ │ Title Define power requirements │ │ Patch aa45913e757cacd46972733bddee5472c78fa32a │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │ │ Branches flux-capacitor-power │ │ Commits ahead 1, behind 0 │ @@ -56,7 +56,7 @@ $ rad patch show aa45913e757cacd46972733bddee5472c78fa32a -p ├────────────────────────────────────────────────────┤ │ 3e674d1 Define power requirements │ ├────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (3e674d1) now │ +│ ● opened by alice (you) (3e674d1) now │ ╰────────────────────────────────────────────────────╯ commit 3e674d1a1df90807e934f9ae5da2591dd6848a33 @@ -75,11 +75,11 @@ We can also list only patches that we've authored. ``` $ rad patch list --authored -╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ● ID Title Author Reviews Head + - Updated │ -├──────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ ● aa45913 Define power requirements z6MknSL…StBU8Vi (you) - 3e674d1 +0 -0 now │ -╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭─────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Reviews Head + - Updated │ +├─────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● aa45913 Define power requirements alice (you) - 3e674d1 +0 -0 now │ +╰─────────────────────────────────────────────────────────────────────────────────────────╯ ``` We can also see that it set an upstream for our patch branch: @@ -99,7 +99,7 @@ $ rad patch show aa45913 ╭────────────────────────────────────────────────────╮ │ Title Define power requirements │ │ Patch aa45913e757cacd46972733bddee5472c78fa32a │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Labels fun │ │ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │ │ Branches flux-capacitor-power │ @@ -110,7 +110,7 @@ $ rad patch show aa45913 ├────────────────────────────────────────────────────┤ │ 3e674d1 Define power requirements │ ├────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (3e674d1) now │ +│ ● opened by alice (you) (3e674d1) now │ ╰────────────────────────────────────────────────────╯ ``` @@ -136,7 +136,7 @@ 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 ╭───────────────────────────────────────╮ -│ z6MknSL…StBU8Vi (you) now 686ec1c │ +│ alice (you) now 686ec1c │ │ I cannot wait to get back to the 90s! │ ╰───────────────────────────────────────╯ $ rad patch comment aa45913 --message 'My favorite decade!' --reply-to 686ec1c -q --no-announce @@ -165,7 +165,7 @@ $ rad patch show aa45913 ╭─────────────────────────────────────────────────────────────────────╮ │ Title Define power requirements │ │ Patch aa45913e757cacd46972733bddee5472c78fa32a │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Labels fun │ │ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │ │ Branches flux-capacitor-power, patch/aa45913 │ @@ -177,16 +177,16 @@ $ rad patch show aa45913 │ 27857ec Add README, just for the fun │ │ 3e674d1 Define power requirements │ ├─────────────────────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (3e674d1) now │ +│ ● opened by alice (you) (3e674d1) now │ │ ↑ updated to 6e5a3b7b2ce27b32e7ccc2f0b3f4594897dde638 (27857ec) now │ -│ └─ ✓ accepted by z6MknSL…StBU8Vi (you) now │ +│ └─ ✓ accepted by alice (you) now │ ╰─────────────────────────────────────────────────────────────────────╯ $ rad patch list -╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ● ID Title Author Reviews Head + - Updated │ -├──────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ ● aa45913 Define power requirements z6MknSL…StBU8Vi (you) ✔ 27857ec +0 -0 now │ -╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭─────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Reviews Head + - Updated │ +├─────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● aa45913 Define power requirements alice (you) ✔ 27857ec +0 -0 now │ +╰─────────────────────────────────────────────────────────────────────────────────────────╯ ``` If you make a mistake on the patch description, you can always change it! @@ -197,7 +197,7 @@ $ rad patch show aa45913 ╭─────────────────────────────────────────────────────────────────────╮ │ Title Define power requirements │ │ Patch aa45913e757cacd46972733bddee5472c78fa32a │ -│ Author z6MknSL…StBU8Vi (you) │ +│ Author alice (you) │ │ Labels fun │ │ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │ │ Branches flux-capacitor-power, patch/aa45913 │ @@ -209,8 +209,8 @@ $ rad patch show aa45913 │ 27857ec Add README, just for the fun │ │ 3e674d1 Define power requirements │ ├─────────────────────────────────────────────────────────────────────┤ -│ ● opened by z6MknSL…StBU8Vi (you) (3e674d1) now │ +│ ● opened by alice (you) (3e674d1) now │ │ ↑ updated to 6e5a3b7b2ce27b32e7ccc2f0b3f4594897dde638 (27857ec) now │ -│ └─ ✓ accepted by z6MknSL…StBU8Vi (you) now │ +│ └─ ✓ accepted by alice (you) now │ ╰─────────────────────────────────────────────────────────────────────╯ ``` diff --git a/radicle-cli/examples/rad-publish.md b/radicle-cli/examples/rad-publish.md index 352ad251..75ed34e7 100644 --- a/radicle-cli/examples/rad-publish.md +++ b/radicle-cli/examples/rad-publish.md @@ -27,18 +27,18 @@ repository private again __will not_ be replicated. ``` $ rad id update --visibility private --title "Privatise" --description "Reverting the rad publish event" ✓ Identity revision 774cc1e72641d97d7dc9377745b7f454a9171747 created -╭───────────────────────────────────────────────────────────────────╮ -│ Title Privatise │ -│ Revision 774cc1e72641d97d7dc9377745b7f454a9171747 │ -│ Blob 88f759a4d46e9535766fccec0cbfe1fed6160b1a │ -│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │ -│ State accepted │ -│ Quorum yes │ -│ │ -│ Reverting the rad publish event │ -├───────────────────────────────────────────────────────────────────┤ -│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) │ -╰───────────────────────────────────────────────────────────────────╯ +╭────────────────────────────────────────────────────────────────────────╮ +│ Title Privatise │ +│ Revision 774cc1e72641d97d7dc9377745b7f454a9171747 │ +│ Blob 88f759a4d46e9535766fccec0cbfe1fed6160b1a │ +│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │ +│ State accepted │ +│ Quorum yes │ +│ │ +│ Reverting the rad publish event │ +├────────────────────────────────────────────────────────────────────────┤ +│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │ +╰────────────────────────────────────────────────────────────────────────╯ @@ -1,13 +1,16 @@ { diff --git a/radicle-cli/src/commands/init.rs b/radicle-cli/src/commands/init.rs index 2c8e3918..d752407d 100644 --- a/radicle-cli/src/commands/init.rs +++ b/radicle-cli/src/commands/init.rs @@ -287,11 +287,14 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()> if options.verbose { term::blob(json::to_string_pretty(&proj)?); } - // It's important to seed our own repositories to make sure that our node signals // interest for them. This ensures that messages relating to them are relayed to us. if options.seed { cli::project::seed(rid, options.scope, &mut node, profile)?; + + if doc.visibility.is_public() { + cli::project::add_inventory(rid, &mut node, profile)?; + } } if options.set_upstream || git::branch_remote(&repo, proj.default_branch()).is_err() { @@ -370,7 +373,6 @@ fn sync( let events = node.subscribe(DEFAULT_SUBSCRIBE_TIMEOUT)?; let sessions = node.sessions()?; - node.update_inventory(rid)?; spinner.message("Announcing.."); if !sessions.iter().any(|s| s.is_connected()) { diff --git a/radicle-cli/src/commands/node/routing.rs b/radicle-cli/src/commands/node/routing.rs index f30d1b72..bcb50411 100644 --- a/radicle-cli/src/commands/node/routing.rs +++ b/radicle-cli/src/commands/node/routing.rs @@ -27,7 +27,7 @@ pub fn run( fn print_table(entries: impl IntoIterator) { let mut t = term::Table::new(term::table::TableOptions::bordered()); - t.push([ + t.header([ term::format::default(String::from("RID")), term::format::default(String::from("NID")), ]); diff --git a/radicle-cli/src/project.rs b/radicle-cli/src/project.rs index 8fe216e3..8d6e2cc4 100644 --- a/radicle-cli/src/project.rs +++ b/radicle-cli/src/project.rs @@ -1,9 +1,10 @@ +use localtime::LocalTime; use radicle::prelude::*; use crate::git; use radicle::git::RefStr; use radicle::node::policy::Scope; -use radicle::node::{Handle, NodeId}; +use radicle::node::{routing, Handle, NodeId}; use radicle::Node; /// Setup a repository remote and tracking branch. @@ -52,8 +53,28 @@ impl<'a> SetupRemote<'a> { } } -/// Seed a repository by first trying to seed through the node, and if the node isn't running, -/// by updating the policy database directly. +/// Add the repo to our inventory. +pub fn add_inventory( + rid: RepoId, + node: &mut Node, + profile: &Profile, +) -> Result { + match node.update_inventory(rid) { + Ok(updated) => Ok(updated), + Err(e) if e.is_connection_err() => { + let now = LocalTime::now(); + let mut db = profile.database_mut()?; + let updates = routing::Store::insert(&mut db, [&rid], *profile.id(), now.into())?; + + Ok(!updates.is_empty()) + } + Err(e) => Err(e.into()), + } +} + +/// Seed a repository by first trying to seed through the node, and if the node isn't running, by +/// updating the policy database directly. If the repo is available locally, we also add it to our +/// inventory. pub fn seed( rid: RepoId, scope: Scope, @@ -64,7 +85,15 @@ pub fn seed( Ok(updated) => Ok(updated), Err(e) if e.is_connection_err() => { let mut config = profile.policies_mut()?; - config.seed(&rid, scope).map_err(|e| e.into()) + let result = config.seed(&rid, scope)?; + + if result && profile.storage.contains(&rid)? { + let now = LocalTime::now(); + let mut db = profile.database_mut()?; + + routing::Store::insert(&mut db, [&rid], *profile.id(), now.into())?; + } + Ok(result) } Err(e) => Err(e.into()), } @@ -77,7 +106,12 @@ pub fn unseed(rid: RepoId, node: &mut Node, profile: &Profile) -> Result Ok(updated), Err(e) if e.is_connection_err() => { let mut config = profile.policies_mut()?; - config.unseed(&rid).map_err(|e| e.into()) + let result = config.unseed(&rid)?; + + let mut db = profile.database_mut()?; + radicle::node::routing::Store::remove(&mut db, &rid, profile.id())?; + + Ok(result) } Err(e) => Err(e.into()), } diff --git a/radicle-cli/tests/commands.rs b/radicle-cli/tests/commands.rs index dbf44bea..2adb1dd4 100644 --- a/radicle-cli/tests/commands.rs +++ b/radicle-cli/tests/commands.rs @@ -730,6 +730,7 @@ fn rad_node() { Address::from(net::SocketAddr::from(([41, 12, 98, 112], 8776))), Address::from_str("seed.cloudhead.io:8776").unwrap(), ], + seeding_policy: SeedingPolicy::Block, ..Config::test(Alias::new("alice")) }); let working = tempfile::tempdir().unwrap(); @@ -1286,6 +1287,8 @@ fn rad_clone_partial_fail() { let working = environment.tmp().join("working"); let carol = NodeId::from_str("z6MksFqXN3Yhqk8pTJdUGLwBTkRfQvwZXPqR2qMEhbS9wzpT").unwrap(); + logger::init(log::Level::Debug); + // Setup a test project. let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack"); @@ -1323,7 +1326,8 @@ fn rad_clone_partial_fail() { eve.connect(&alice); eve.connect(&bob); eve.routes_to(&[(acme, carol), (acme, bob.id), (acme, alice.id)]); - bob.handle.unseed(acme).unwrap(); // Cause the fetch with bob to fail. + bob.storage.repository(acme).unwrap().remove().unwrap(); // Cause the fetch from Bob to fail. + bob.storage.lock_repository(acme).ok(); // Prevent repo from being re-fetched. test( "examples/rad-clone-partial-fail.md", diff --git a/radicle-node/src/runtime.rs b/radicle-node/src/runtime.rs index 90cbb460..8f9c29b5 100644 --- a/radicle-node/src/runtime.rs +++ b/radicle-node/src/runtime.rs @@ -125,10 +125,6 @@ impl Runtime { log::warn!(target: "node", "Unused or deprecated configuration attribute {:?}", key); } log::info!(target: "node", "Opening node database.."); - let db = home - .database_mut()? - .journal_mode(node::db::JournalMode::default())?; - let mut stores: service::Stores<_> = db.clone().into(); log::info!(target: "node", "Opening policy database.."); let policies = home.policies_mut()?; @@ -175,6 +171,18 @@ impl Runtime { .expect("Runtime::init: unable to solve proof-of-work puzzle") }; + let db = home + .database_mut()? + .journal_mode(node::db::JournalMode::default())? + .init( + &id, + announcement.features, + announcement.alias.clone(), + announcement.timestamp, + announcement.addresses.iter(), + )?; + let mut stores: service::Stores<_> = db.clone().into(); + if config.connect.is_empty() && stores.addresses().is_empty()? { log::info!(target: "node", "Address book is empty. Adding bootstrap nodes.."); diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs index ea9c4fdd..30089657 100644 --- a/radicle-node/src/service.rs +++ b/radicle-node/src/service.rs @@ -10,7 +10,7 @@ pub mod message; pub mod session; use std::collections::hash_map::Entry; -use std::collections::{BTreeSet, HashMap, VecDeque}; +use std::collections::{BTreeSet, HashMap, HashSet, VecDeque}; use std::net::IpAddr; use std::ops::{Deref, DerefMut}; use std::sync::Arc; @@ -391,6 +391,12 @@ where } } +impl AsMut for Stores { + fn as_mut(&mut self) -> &mut D { + &mut self.0 + } +} + impl From for Stores { fn from(db: D) -> Self { Self(db) @@ -555,16 +561,23 @@ where /// simply not announcing it anymore, it will eventually be pruned by nodes. pub fn unseed(&mut self, id: &RepoId) -> Result { let updated = self.policies.unseed(id)?; - // Nb. This is potentially slow if we have lots of repos. We should probably - // only re-compute the filter when we've unseeded a certain amount of repos - // and the filter is really out of date. - // - // TODO: Share this code with initialization code. - self.filter = Filter::new( - self.policies - .seed_policies()? - .filter_map(|t| (t.policy.is_allow()).then_some(t.rid)), - ); + + if updated { + // Nb. This is potentially slow if we have lots of repos. We should probably + // only re-compute the filter when we've unseeded a certain amount of repos + // and the filter is really out of date. + // + // TODO: Share this code with initialization code. + self.filter = Filter::new( + self.policies + .seed_policies()? + .filter_map(|t| (t.policy.is_allow()).then_some(t.rid)), + ); + // Update and announce new inventory. + if let Err(e) = self.remove_inventory(id) { + error!(target: "service", "Error updating inventory after unseed: {e}"); + } + } Ok(updated) } @@ -623,12 +636,18 @@ where /// Lookup a repository, both locally and in the routing table. pub fn lookup(&self, rid: RepoId) -> Result { - let remote = self.db.routing().get(&rid)?.iter().cloned().collect(); + let this = self.nid(); + let local = self.storage.get(rid)?; + let remote = self + .db + .routing() + .get(&rid)? + .iter() + .filter(|nid| nid != &this) + .cloned() + .collect(); - Ok(Lookup { - local: self.storage.get(rid)?, - remote, - }) + Ok(Lookup { local, remote }) } /// Initialize service with current time. Call this once. @@ -662,27 +681,13 @@ where Err(e) => error!(target: "service", "Error checking refs database: {e}"), } - // Ensure that our local node is in our address database. - self.db - .addresses_mut() - .insert( - &nid, - self.node.features, - self.node.alias.clone(), - self.node.work(), - self.node.timestamp, - self.node - .addresses - .iter() - .map(|a| KnownAddress::new(a.clone(), address::Source::Peer)), - ) - .expect("Service::initialize: error adding local node to address database"); - let announced = self .db .seeds() .seeded_by(&nid)? .collect::, _>>()?; + let mut inventory = BTreeSet::new(); + for repo in self.storage.repositories()? { let rid = repo.rid; @@ -693,7 +698,7 @@ where } // Add public repositories to inventory. if repo.doc.visibility.is_public() { - self.storage.insert(rid); + inventory.insert(rid); } // If we have no owned refs for this repo, then there's nothing to announce. let Some(updated_at) = repo.synced_at else { @@ -724,7 +729,6 @@ where } { - let inventory = self.storage.inventory()?; // Ensure that our inventory is recorded in our routing table, and we are seeding // all of it. It can happen that inventory is not properly seeded if for eg. the // user creates a new repository while the node is stopped. @@ -805,7 +809,7 @@ where if now - self.last_sync >= SYNC_INTERVAL { trace!(target: "service", "Running 'sync' task..."); - if let Err(e) = self.fetch_missing_inventory() { + if let Err(e) = self.fetch_missing_repositories() { error!(target: "service", "Error fetching missing inventory: {e}"); } self.outbox.wakeup(SYNC_INTERVAL); @@ -814,9 +818,7 @@ where if now - self.last_announce >= ANNOUNCE_INTERVAL { trace!(target: "service", "Running 'announce' task..."); - if let Err(err) = self.announce_inventory() { - error!(target: "service", "Error announcing inventory: {err}"); - } + self.announce_inventory(); self.outbox.wakeup(ANNOUNCE_INTERVAL); } if now - self.last_prune >= PRUNE_INTERVAL { @@ -939,19 +941,16 @@ where } } Command::AnnounceInventory => { - if let Err(err) = self.announce_inventory() { - error!(target: "service", "Error announcing inventory: {err}"); + self.announce_inventory(); + } + Command::UpdateInventory(rid, resp) => match self.add_inventory(rid) { + Ok(updated) => { + resp.send(updated).ok(); } - } - Command::UpdateInventory(rid, resp) => { - self.storage.insert(rid); - - let synced = self - .sync_inventory() - .expect("Service::command: error syncing inventory"); - resp.send(synced.added.len() + synced.removed.len() > 0) - .ok(); - } + Err(e) => { + error!(target: "service", "Error adding {rid} to inventory: {e}"); + } + }, Command::QueryState(query, sender) => { sender.send(query(self)).ok(); } @@ -1171,8 +1170,9 @@ where if clone && doc.visibility.is_public() { debug!(target: "service", "Updating and announcing inventory for cloned repository {rid}.."); - self.storage.insert(rid); - self.sync_and_announce_inventory(); + if let Err(e) = self.add_inventory(rid) { + error!(target: "service", "Error announcing inventory for {rid}: {e}"); + } } // It's possible for a fetch to succeed but nothing was updated. @@ -1539,8 +1539,6 @@ where return Ok(None); } } - - let inventory = self.storage.inventory_ref(); let mut missing = Vec::new(); for id in message.inventory.as_slice() { @@ -1560,14 +1558,20 @@ where ) { // Only if we do not have the repository locally do we fetch here. // If we do have it, only fetch after receiving a ref announcement. - if !inventory.contains(id) { - missing.push(*id); + match self.db.routing().entry(id, self.nid()) { + Ok(entry) => { + if entry.is_none() { + missing.push(*id); + } + } + Err(e) => error!( + target: "service", + "Error checking local inventory for {id}: {e}" + ), } } } } - drop(inventory); - for rid in missing { debug!(target: "service", "Missing seeded inventory {rid}; initiating fetch.."); self.fetch(rid, *announcer, FETCH_TIMEOUT, None); @@ -1931,14 +1935,59 @@ where > 0 } - /// Update our routing table with our local node's inventory. - fn sync_inventory(&mut self) -> Result { - let inventory = self.storage.inventory()?; - let result = self.sync_routing(inventory.clone(), self.node_id(), self.clock.into())?; - // Update cached inventory message. - self.inventory = gossip::inventory(self.timestamp(), inventory); + /// Remove a local repository from our inventory. + fn remove_inventory(&mut self, rid: &RepoId) -> Result { + let node = self.node_id(); + let now = self.timestamp(); - Ok(result) + // Remove inventory. + let removed = self.db.routing_mut().remove(rid, &node)?; + if removed { + // Update cached inventory message. + let inventory = self.inventory()?; + self.inventory = gossip::inventory(now, inventory); + self.announce_inventory(); + } + Ok(removed) + } + + /// Add a local repository to our inventory. + fn add_inventory(&mut self, rid: RepoId) -> Result { + let node = self.node_id(); + let now = self.timestamp(); + + if !self.storage.contains(&rid)? { + error!(target: "service", "Attempt to add non-existing inventory {rid}: repository not found in storage"); + return Ok(false); + } + // Add to our inventory. + let updates = self.db.routing_mut().insert([&rid], node, now)?; + let updated = !updates.is_empty(); + + if updated { + let inventory = self.inventory()?; + + self.inventory = gossip::inventory(now, inventory); + self.announce_inventory(); + } + Ok(updated) + } + + /// Get our local inventory. + /// + /// A node's inventory is the advertized list of repositories offered by a node. + /// + /// A node's inventory consists of *public* repositories that are seeded and available locally + /// in the node's storage. We use the routing table as the canonical state of all inventories, + /// including the local node's. + /// + /// When a repository is unseeded, it is also removed from the inventory. Private repositories + /// are *not* part of a node's inventory. + fn inventory(&self) -> Result, Error> { + self.db + .routing() + .get_resources(self.nid()) + .map_err(Error::from) } /// Process a peer inventory announcement by updating our routing table. @@ -2093,22 +2142,6 @@ where Ok((refs, timestamp)) } - fn sync_and_announce_inventory(&mut self) { - match self.sync_inventory() { - Ok(synced) => { - // Only announce if our inventory changed. - if synced.added.len() + synced.removed.len() > 0 { - if let Err(e) = self.announce_inventory() { - error!(target: "service", "Failed to announce inventory: {e}"); - } - } - } - Err(e) => { - error!(target: "service", "Failed to sync inventory: {e}"); - } - } - } - fn reconnect(&mut self, nid: NodeId, addr: Address) -> bool { if let Some(sess) = self.sessions.get_mut(&nid) { sess.to_initial(); @@ -2263,7 +2296,9 @@ where } /// Announce our inventory to all connected peers. - fn announce_inventory(&mut self) -> Result<(), storage::Error> { + fn announce_inventory(&mut self) { + // TODO: If we're going to announce soon, skip this. + // TODO: If we just announced this exact inventory, also skip. let msg = AnnouncementMessage::from(self.inventory.clone()); self.outbox.announce( @@ -2272,8 +2307,6 @@ where self.db.gossip_mut(), ); self.last_announce = self.clock; - - Ok(()) } fn prune_routing_entries(&mut self, now: &LocalTime) -> Result<(), routing::Error> { @@ -2355,16 +2388,17 @@ where } } - /// Fetch all repositories that are seeded but missing from our inventory. - fn fetch_missing_inventory(&mut self) -> Result<(), Error> { - let inventory = self.storage().inventory()?; - let missing = self - .policies - .seed_policies()? - .filter_map(|t| (t.policy.is_allow()).then_some(t.rid)) - .filter(|rid| !inventory.contains(rid)); + /// Fetch all repositories that are seeded but missing from storage. + fn fetch_missing_repositories(&mut self) -> Result<(), Error> { + for policy in self.policies.seed_policies()? { + let rid = policy.rid; - for rid in missing { + if !policy.is_allow() { + continue; + } + if self.storage.contains(&rid)? { + continue; + } match self.seeds(&rid) { Ok(seeds) => { if let Some(connected) = NonEmpty::from_vec(seeds.connected().collect()) { diff --git a/radicle-node/src/test/environment.rs b/radicle-node/src/test/environment.rs index 7a41b76a..c8d4ba3b 100644 --- a/radicle-node/src/test/environment.rs +++ b/radicle-node/src/test/environment.rs @@ -9,6 +9,7 @@ use std::{ use crossbeam_channel as chan; +use localtime::LocalTime; use radicle::cob::cache::COBS_DB_FILE; use radicle::cob::issue; use radicle::crypto::ssh::{keystore::MemorySigner, Keystore}; @@ -18,7 +19,6 @@ use radicle::git::refname; use radicle::identity::{RepoId, Visibility}; use radicle::node::config::ConnectAddress; use radicle::node::policy::store as policy; -use radicle::node::routing::Store; use radicle::node::seed::Store as _; use radicle::node::Database; use radicle::node::{Alias, POLICIES_DB_FILE}; @@ -113,21 +113,32 @@ impl Environment { let keypair = KeyPair::from_seed(Seed::from([!(self.users as u8); 32])); let policies_db = home.node().join(POLICIES_DB_FILE); let cobs_db = home.cobs().join(COBS_DB_FILE); + let now = LocalTime::now(); config.write(&home.config()).unwrap(); let storage = Storage::open( home.storage(), git::UserInfo { - alias, + alias: alias.clone(), key: keypair.pk.into(), }, ) .unwrap(); + let public_key = keypair.pk.into(); policy::Store::open(policies_db).unwrap(); - home.database_mut().unwrap(); // Just create the database. cob::cache::Store::open(cobs_db).unwrap(); + home.database_mut() + .unwrap() + .init( + &public_key, + config.node.features(), + Alias::new(alias), + now.into(), + config.node.external_addresses.iter(), + ) + .unwrap(); transport::local::register(storage.clone()); keystore.store(keypair.clone(), "radicle", None).unwrap(); @@ -139,7 +150,7 @@ impl Environment { home, storage, keystore, - public_key: keypair.pk.into(), + public_key, config, } } @@ -248,10 +259,15 @@ impl NodeHandle { /// Get routing table entries. pub fn routing(&self) -> impl Iterator { - Database::reader(self.home.node().join(node::NODE_DB_FILE)) - .unwrap() - .entries() - .unwrap() + use node::routing::Store as _; + + self.home.routing_mut().unwrap().entries().unwrap() + } + + pub fn inventory(&self) -> impl Iterator + '_ { + self.routing() + .filter(|(_, n)| *n == self.id) + .map(|(r, _)| r) } /// Get sync status of a repo. @@ -605,7 +621,7 @@ pub fn converge<'a, G: Signer + cyphernet::Ecdh + 'static>( all_routes.insert((rid, seed_id)); } // Routes from the local inventory. - for rid in node.storage.inventory().unwrap() { + for rid in node.inventory() { all_routes.insert((rid, node.id)); } } diff --git a/radicle-node/src/test/peer.rs b/radicle-node/src/test/peer.rs index 20e3dd10..2e6f1671 100644 --- a/radicle-node/src/test/peer.rs +++ b/radicle-node/src/test/peer.rs @@ -1,4 +1,5 @@ #![allow(dead_code)] +use std::collections::HashSet; use std::iter; use std::net; use std::ops::{Deref, DerefMut}; @@ -19,6 +20,7 @@ use crate::crypto::test::signer::MockSigner; use crate::crypto::Signer; use crate::identity::RepoId; use crate::node; +use crate::node::routing::Store as _; use crate::prelude::*; use crate::runtime::Emitter; use crate::service; @@ -27,7 +29,6 @@ use crate::service::message::*; use crate::service::policy::{Scope, SeedingPolicy}; use crate::service::*; use crate::storage::git::transport::remote; -use crate::storage::Inventory; use crate::storage::{RemoteId, WriteStorage}; use crate::test::storage::MockStorage; use crate::test::{arbitrary, fixtures, simulator}; @@ -99,7 +100,6 @@ where pub struct Config { pub config: service::Config, - pub db: Stores, pub local_time: LocalTime, pub policy: SeedingPolicy, pub signer: G, @@ -112,13 +112,10 @@ impl Default for Config { let mut rng = fastrand::Rng::new(); let signer = MockSigner::new(&mut rng); let tmp = tempfile::TempDir::new().unwrap(); - let db = Database::open(tmp.path().join(node::NODE_DB_FILE)) - .unwrap() - .into(); + let config = service::Config::test(Alias::from_str("mocky").unwrap()); Config { - config: service::Config::test(Alias::from_str("mocky").unwrap()), - db, + config, local_time: LocalTime::now(), policy: SeedingPolicy::default(), signer, @@ -164,19 +161,32 @@ where let id = *config.signer.public_key(); let ip = ip.into(); let local_addr = net::SocketAddr::new(ip, config.rng.u16(..)); - let inventory = storage.inventory().unwrap(); + let inventory = storage.repositories().unwrap(); // Make sure the peer address is advertized. config.config.external_addresses.push(local_addr.into()); - for rid in &inventory { - policies.seed(rid, Scope::Followed).unwrap(); + for repo in &inventory { + policies.seed(&repo.rid, Scope::Followed).unwrap(); } + // Initialize database. + let db = Database::open(config.tmp.path().join(node::NODE_DB_FILE)) + .unwrap() + .init( + &id, + config.config.features(), + config.config.alias.clone(), + config.local_time.into(), + config.config.external_addresses.iter(), + ) + .unwrap() + .into(); + let announcement = service::gossip::node(&config.config, Timestamp::from(config.local_time) + 1); let emitter: Emitter = Default::default(); let service = Service::new( config.config, - config.db, + db, storage, policies, config.signer, @@ -255,8 +265,12 @@ where (*self.clock()).into() } - pub fn inventory(&self) -> Inventory { - self.service.storage().inventory().unwrap() + pub fn inventory(&self) -> HashSet { + self.service + .database() + .routing() + .get_resources(self.nid()) + .unwrap() } pub fn git_url(&self, repo: RepoId, namespace: Option) -> remote::Url { diff --git a/radicle-node/src/tests.rs b/radicle-node/src/tests.rs index 4afe559e..1da0a0bd 100644 --- a/radicle-node/src/tests.rs +++ b/radicle-node/src/tests.rs @@ -274,21 +274,21 @@ fn test_inventory_sync() { let bob_storage = fixtures::storage(tmp.path().join("bob"), &bob_signer).unwrap(); let bob = Peer::with_storage("bob", [8, 8, 8, 8], bob_storage); let now = LocalTime::now().into(); - let projs = bob.storage().inventory().unwrap(); + let repos = bob.inventory().into_iter().collect::>(); alice.connect_to(&bob); alice.receive( bob.id(), Message::inventory( InventoryAnnouncement { - inventory: projs.clone().try_into().unwrap(), + inventory: repos.clone().try_into().unwrap(), timestamp: now, }, bob.signer(), ), ); - for proj in &projs { + for proj in &repos { let seeds = alice.database().routing().get(proj).unwrap(); assert!(seeds.contains(&bob.node_id())); } @@ -701,12 +701,7 @@ fn test_refs_announcement_relay() { ) .initialized() }; - let bob_inv = bob - .storage() - .inventory() - .unwrap() - .into_iter() - .collect::>(); + let bob_inv = bob.inventory().into_iter().collect::>(); alice.seed(&bob_inv[0], policy::Scope::All).unwrap(); alice.seed(&bob_inv[1], policy::Scope::All).unwrap(); @@ -779,8 +774,8 @@ fn test_refs_announcement_fetch_trusted_no_inventory() { ) .initialized() }; - let bob_inv = bob.storage().inventory().unwrap(); - let rid = bob_inv.first().unwrap(); + let bob_inv = bob.inventory(); + let rid = bob_inv.iter().next().unwrap(); alice.seed(rid, policy::Scope::Followed).unwrap(); alice.connect_to(&bob); @@ -887,10 +882,7 @@ fn test_refs_announcement_offline() { }, ) }; - let mut inv = alice.inventory(); - let rid = *inv.first().unwrap(); let mut bob = Peer::new("bob", [8, 8, 8, 8]); - bob.seed(&rid, policy::Scope::All).unwrap(); // Make sure alice's service wasn't initialized before. assert_eq!(*alice.clock(), LocalTime::default()); @@ -899,6 +891,11 @@ fn test_refs_announcement_offline() { alice.connect_to(&bob); alice.receive(bob.id, Message::Subscribe(Subscribe::all())); + let mut inv = alice.inventory(); + let rid = *inv.iter().next().unwrap(); + + bob.seed(&rid, policy::Scope::All).unwrap(); + // Alice announces the refs of all projects since she hasn't announced refs for these projects // yet. for msg in alice.messages(bob.id()) { @@ -1429,7 +1426,7 @@ fn test_queued_fetch_max_capacity() { #[test] fn test_queued_fetch_from_ann_same_rid() { - let storage = arbitrary::nonempty_storage(3); + let storage = arbitrary::nonempty_storage(1); // We're testing both public and private repos. let mut repo_keys = storage.repos.keys(); let rid = *repo_keys.next().unwrap(); let mut alice = Peer::with_storage("alice", [7, 7, 7, 7], storage); @@ -1448,8 +1445,6 @@ fn test_queued_fetch_from_ann_same_rid() { timestamp: bob.timestamp(), }; - logger::init(log::Level::Trace); - alice.seed(&rid, policy::Scope::All).unwrap(); alice.connect_to(&bob); alice.connect_to(&eve); @@ -1713,9 +1708,27 @@ fn test_init_and_seed() { fn prop_inventory_exchange_dense() { fn property(alice_inv: MockStorage, bob_inv: MockStorage, eve_inv: MockStorage) { let rng = fastrand::Rng::new(); - let alice = Peer::with_storage("alice", [7, 7, 7, 7], alice_inv.clone()); - let mut bob = Peer::with_storage("bob", [8, 8, 8, 8], bob_inv.clone()); - let mut eve = Peer::with_storage("eve", [9, 9, 9, 9], eve_inv.clone()); + let alice = Peer::with_storage( + "alice", + [7, 7, 7, 7], + alice_inv + .clone() + .map(|doc| doc.visibility = Visibility::Public), + ); + let mut bob = Peer::with_storage( + "bob", + [8, 8, 8, 8], + bob_inv + .clone() + .map(|doc| doc.visibility = Visibility::Public), + ); + let mut eve = Peer::with_storage( + "eve", + [9, 9, 9, 9], + eve_inv + .clone() + .map(|doc| doc.visibility = Visibility::Public), + ); let mut routing = RandomMap::with_hasher(rng.clone().into()); for (inv, peer) in &[ @@ -1913,7 +1926,6 @@ fn test_announcement_message_amplification() { .repos .insert(rid, gen::(1)); alice.command(Command::UpdateInventory(rid, tx)); - alice.command(Command::AnnounceInventory); sim.run_while([&mut alice, &mut bob, &mut eve, &mut zod, &mut tom], |s| { s.elapsed() < LocalDuration::from_mins(3) diff --git a/radicle/src/node/db.rs b/radicle/src/node/db.rs index c5727477..a53446a8 100644 --- a/radicle/src/node/db.rs +++ b/radicle/src/node/db.rs @@ -15,6 +15,7 @@ use std::{fmt, time}; use sqlite as sql; use thiserror::Error; +use crate::node::{address, Address, Alias, Features, KnownAddress, NodeId, Timestamp}; use crate::sql::transaction; /// How long to wait for the database lock to be released before failing a read. @@ -34,6 +35,9 @@ const MIGRATIONS: &[&str] = &[ #[derive(Error, Debug)] pub enum Error { + /// Initialization error. + #[error("error initializing the database: {0}")] + Init(#[from] address::store::Error), /// An Internal error. #[error("internal error: {0}")] Internal(#[from] sql::Error), @@ -121,6 +125,30 @@ impl Database { Ok(self) } + /// Initialize by adding our local node to the database. + pub fn init<'a>( + mut self, + node: &NodeId, + features: Features, + alias: Alias, + timestamp: Timestamp, + addrs: impl IntoIterator, + ) -> Result { + address::Store::insert( + &mut self, + node, + features, + alias, + 0, + timestamp, + addrs + .into_iter() + .map(|a| KnownAddress::new(a.clone(), address::Source::Imported)), + )?; + + Ok(self) + } + /// Create a new in-memory database. pub fn memory() -> Result { let db = sql::Connection::open_thread_safe(":memory:")?; diff --git a/radicle/src/profile.rs b/radicle/src/profile.rs index 171375ca..e54ea4b3 100644 --- a/radicle/src/profile.rs +++ b/radicle/src/profile.rs @@ -14,6 +14,7 @@ use std::io::Write; use std::path::{Path, PathBuf}; use std::{fs, io}; +use localtime::LocalTime; use serde::Serialize; use serde_json as json; use thiserror::Error; @@ -307,8 +308,16 @@ impl Profile { )?; // Create DBs. home.policies_mut()?; - home.database_mut()?; home.notifications_mut()?; + home.database_mut()? + .journal_mode(node::db::JournalMode::default())? + .init( + &public_key, + config.node.features(), + config.node.alias.clone(), + LocalTime::now().into(), + config.node.external_addresses.iter(), + )?; transport::local::register(storage.clone()); @@ -576,6 +585,16 @@ impl Home { Ok(db) } + /// Returns the address store. + pub fn addresses(&self) -> Result { + self.database_mut() + } + + /// Returns the routing store. + pub fn routing_mut(&self) -> Result { + self.database_mut() + } + /// Return a read-only handle for the issues cache. pub fn issues<'a, R>( &self, diff --git a/radicle/src/rad.rs b/radicle/src/rad.rs index 27f3f291..889a897b 100644 --- a/radicle/src/rad.rs +++ b/radicle/src/rad.rs @@ -73,9 +73,6 @@ pub fn init( let (project, _) = Repository::init(&doc, &storage, signer)?; let url = git::Url::from(project.id); - // Update inventory cache for this storage instance. - storage.insert(project.id); - match init_configure(repo, &project, pk, &default_branch, &url, signer) { Ok(signed) => Ok((project.id, doc, signed)), Err(err) => { diff --git a/radicle/src/storage.rs b/radicle/src/storage.rs index 9b97a3ce..71101e24 100644 --- a/radicle/src/storage.rs +++ b/radicle/src/storage.rs @@ -1,7 +1,7 @@ pub mod git; pub mod refs; -use std::collections::{hash_map, BTreeSet, HashSet}; +use std::collections::{hash_map, HashSet}; use std::ops::Deref; use std::path::{Path, PathBuf}; use std::{fmt, io}; @@ -29,7 +29,6 @@ use self::git::UserInfo; use self::refs::{RefsAt, SignedRefs}; pub type BranchName = git::RefString; -pub type Inventory = BTreeSet; /// Basic repository information. #[derive(Debug, Clone, PartialEq, Eq)] @@ -150,8 +149,6 @@ pub enum Error { Ext(#[from] git::ext::Error), #[error("invalid repository identifier {0:?}")] InvalidId(std::ffi::OsString), - #[error("inventory: {0}")] - Inventory(io::Error), #[error("i/o: {0}")] Io(#[from] io::Error), } @@ -405,9 +402,6 @@ impl Deref for Remote { /// Read-only operations on a storage instance. pub trait ReadStorage { type Repository: ReadRepository; - type InventoryRef<'a>: Deref - where - Self: 'a; /// Get user info for this storage. fn info(&self) -> &UserInfo; @@ -417,16 +411,8 @@ pub trait ReadStorage { fn path_of(&self, rid: &RepoId) -> PathBuf; /// Check whether storage contains a repository. fn contains(&self, rid: &RepoId) -> Result; - /// Get the inventory of repositories hosted under this storage. - /// This function should typically only return public repositories. - fn inventory(&self) -> Result; - /// Return a reference to the inventory. Note that this function may hold a - /// lock to the inventory. - fn inventory_ref<'a, 's: 'a>(&'s self) -> Self::InventoryRef<'a>; /// Return all repositories (public and private). fn repositories(&self) -> Result>, Error>; - /// Insert this repository into the inventory. - fn insert(&self, rid: RepoId); /// Open or create a read-only repository. fn repository(&self, rid: RepoId) -> Result; /// Get a repository's identity if it exists. @@ -667,7 +653,6 @@ where S: ReadStorage + 'static, { type Repository = S::Repository; - type InventoryRef<'a> = S::InventoryRef<'a> where T: 'a; fn info(&self) -> &UserInfo { self.deref().info() @@ -681,25 +666,10 @@ where self.deref().path_of(rid) } - fn insert(&self, rid: RepoId) { - self.deref().insert(rid) - } - fn contains(&self, rid: &RepoId) -> Result { self.deref().contains(rid) } - fn inventory(&self) -> Result { - self.deref().inventory() - } - - fn inventory_ref<'a, 's: 'a>(&'s self) -> Self::InventoryRef<'a> - where - T: 'a, - { - self.deref().inventory_ref() - } - fn get(&self, rid: RepoId) -> Result>, RepositoryError> { self.deref().get(rid) } diff --git a/radicle/src/storage/git.rs b/radicle/src/storage/git.rs index 77b61983..82846769 100644 --- a/radicle/src/storage/git.rs +++ b/radicle/src/storage/git.rs @@ -5,7 +5,6 @@ pub mod transport; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex, MutexGuard}; use std::{fs, io}; use crypto::{Signer, Verified}; @@ -20,8 +19,8 @@ use crate::node::SyncedAt; use crate::storage::refs; use crate::storage::refs::{Refs, SignedRefs, SignedRefsAt}; use crate::storage::{ - Inventory, ReadRepository, ReadStorage, Remote, Remotes, RepositoryError, RepositoryInfo, - SetHead, SignRepository, WriteRepository, WriteStorage, + ReadRepository, ReadStorage, Remote, Remotes, RepositoryError, RepositoryInfo, SetHead, + SignRepository, WriteRepository, WriteStorage, }; use crate::{git, node}; @@ -73,34 +72,14 @@ impl<'a> TryFrom> for Ref { } } -/// A reference to a locked inventory. -pub struct InventoryLock<'a> { - guard: MutexGuard<'a, Option>>, -} - -impl<'a> Deref for InventoryLock<'a> { - type Target = BTreeSet; - - #[track_caller] - fn deref(&self) -> &Self::Target { - match *self.guard { - Some(ref cache) => cache, - None => panic!("InventoryLock::deref: inventory cache was not initialized"), - } - } -} - #[derive(Debug, Clone)] pub struct Storage { path: PathBuf, info: UserInfo, - /// Inventory cache. Set to `None` until the cache is populated. - inventory: Arc>>>, } impl ReadStorage for Storage { type Repository = Repository; - type InventoryRef<'a> = InventoryLock<'a>; fn info(&self) -> &UserInfo { &self.info @@ -122,44 +101,6 @@ impl ReadStorage for Storage { Ok(false) } - fn inventory_ref<'a, 's: 'a>(&'s self) -> InventoryLock<'a> { - let guard = self - .inventory - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - - InventoryLock { guard } - } - - fn inventory(&self) -> Result { - let mut cache = self - .inventory - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - - match *cache { - Some(ref cache) => Ok(cache.clone()), - None => { - let repos: BTreeSet<_> = self.public_repositories()?.collect(); - *cache = Some(repos.clone()); - Ok(repos) - } - } - } - - fn insert(&self, rid: RepoId) { - let mut repos = self - .inventory - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - - if let Some(ref mut repos) = *repos { - repos.insert(rid); - } else { - *repos = Some(BTreeSet::from_iter([rid])); - } - } - fn repository(&self, rid: RepoId) -> Result { Repository::open(paths::repository(self, &rid), rid) } @@ -265,11 +206,7 @@ impl Storage { Err(err) => return Err(Error::Io(err)), Ok(()) => {} } - Ok(Self { - path, - info, - inventory: Default::default(), - }) + Ok(Self { path, info }) } /// Create a [`Repository`] in a temporary directory. @@ -336,14 +273,6 @@ impl Storage { } Ok(()) } - - fn public_repositories(&self) -> Result, Error> { - let repos = self.repositories()?; - Ok(repos - .into_iter() - .filter(|r| r.doc.visibility.is_public()) - .map(|r| r.rid)) - } } /// Git implementation of [`WriteRepository`] using the `git2` crate. diff --git a/radicle/src/test/storage.rs b/radicle/src/test/storage.rs index 0a8f632b..91d37e62 100644 --- a/radicle/src/test/storage.rs +++ b/radicle/src/test/storage.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeSet, HashMap}; +use std::collections::HashMap; use std::convert::Infallible; use std::io; use std::path::{Path, PathBuf}; @@ -51,26 +51,20 @@ impl MockStorage { .expect("MockStorage::repo_mut: repository does not exist") } + pub fn map(mut self, f: impl Fn(&mut Doc)) -> Self { + for repo in self.repos.values_mut() { + f(&mut repo.doc.doc); + } + self + } + pub fn empty() -> Self { Self::new(Vec::new()) } } -pub struct InventoryLock { - inner: Inventory, -} - -impl std::ops::Deref for InventoryLock { - type Target = BTreeSet; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - impl ReadStorage for MockStorage { type Repository = MockRepository; - type InventoryRef<'a> = InventoryLock; fn info(&self) -> &git::UserInfo { &self.info @@ -88,18 +82,6 @@ impl ReadStorage for MockStorage { Ok(self.repos.contains_key(rid)) } - fn inventory(&self) -> Result { - Ok(self.repos.keys().cloned().collect::>()) - } - - fn inventory_ref<'a, 's: 'a>(&'s self) -> Self::InventoryRef<'a> { - InventoryLock { - inner: self.inventory().unwrap(), - } - } - - fn insert(&self, _rid: RepoId) {} - fn repository(&self, rid: RepoId) -> Result { self.repos .get(&rid)