node: Update inventory when unseeding

Previously, our advertized inventory was not always correct: when we
stopped seeding a repository, it was still advertised as part of our
inventory until node restart, because the `Storage` type doesn't have
access to seeding policies.

In this change, we remove the concept of inventory from `Storage` and
make the authoritative place for it the routing table in our database.

To make this work, we have to add our node to the database on profile
creation, to not violate the foreign key constraint on the routing
table. Hence, the tests are changed to include our alias which is now
always available.
This commit is contained in:
cloudhead 2024-06-06 17:07:51 +02:00
parent d9fa83f9da
commit b24f8a30d6
No known key found for this signature in database
30 changed files with 510 additions and 432 deletions

View File

@ -7,7 +7,7 @@ $ rad issue open --title "flux capacitor underpowered" --description "Flux capac
╭─────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────╮
│ Title flux capacitor underpowered │ │ Title flux capacitor underpowered │
│ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │ │ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Status open │ │ Status open │
│ │ │ │
│ Flux capacitor power requirements exceed current supply │ │ Flux capacitor power requirements exceed current supply │
@ -18,11 +18,11 @@ The issue is now listed under our project.
``` ```
$ rad issue list $ rad issue list
╭─────────────────────────────────────────────────────────────────────────────────────────────────── ╭──────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Labels Assignees Opened │ │ ● ID Title Author Labels Assignees Opened │
├─────────────────────────────────────────────────────────────────────────────────────────────────── ├──────────────────────────────────────────────────────────────────────────────────────────┤
│ ● d87dcfe flux capacitor underpowered z6MknSL…StBU8Vi (you) now │ │ ● d87dcfe flux capacitor underpowered alice (you) now │
╰─────────────────────────────────────────────────────────────────────────────────────────────────── ╰──────────────────────────────────────────────────────────────────────────────────────────╯
``` ```
Let's create a patch, too. Let's create a patch, too.
@ -42,11 +42,11 @@ Patch can be listed.
``` ```
$ rad patch $ rad patch
╭────────────────────────────────────────────────────────────────────────────────────────────────── ╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │ │ ● ID Title Author Reviews Head + - Updated │
├────────────────────────────────────────────────────────────────────────────────────────────────── ├─────────────────────────────────────────────────────────────────────────────────────────┤
│ ● aa45913 Define power requirements z6MknSL…StBU8Vi (you) - 3e674d1 +0 -0 now │ │ ● aa45913 Define power requirements alice (you) - 3e674d1 +0 -0 now │
╰────────────────────────────────────────────────────────────────────────────────────────────────── ╰─────────────────────────────────────────────────────────────────────────────────────────╯
``` ```
Both issue and patch COBs can be listed. Both issue and patch COBs can be listed.

View File

@ -10,7 +10,7 @@ $ rad issue open --title "spice harvester broken" --description "Fremen have att
╭──────────────────────────────────────────────────╮ ╭──────────────────────────────────────────────────╮
│ Title spice harvester broken │ │ Title spice harvester broken │
│ Issue 9de644864342d7a505eb8d58d1ef20e5bb05de2e │ │ Issue 9de644864342d7a505eb8d58d1ef20e5bb05de2e │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Status open │ │ Status open │
│ │ │ │
│ Fremen have attacked, maybe we went too far? │ │ Fremen have attacked, maybe we went too far? │
@ -21,11 +21,11 @@ The issue is now listed under our project.
``` ```
$ rad issue list $ rad issue list
╭────────────────────────────────────────────────────────────────────────────────────────────── ╭─────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Labels Assignees Opened │ │ ● ID Title Author Labels Assignees Opened │
├────────────────────────────────────────────────────────────────────────────────────────────── ├─────────────────────────────────────────────────────────────────────────────────────┤
│ ● 9de6448 spice harvester broken z6MknSL…StBU8Vi (you) now │ │ ● 9de6448 spice harvester broken alice (you) now │
╰────────────────────────────────────────────────────────────────────────────────────────────── ╰─────────────────────────────────────────────────────────────────────────────────────╯
``` ```
Let's create a patch, too. Let's create a patch, too.
@ -45,11 +45,11 @@ Patch can be listed.
``` ```
$ rad patch $ rad patch
╭──────────────────────────────────────────────────────────────────────────────────────────────────── ╭───────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │ │ ● ID Title Author Reviews Head + - Updated │
├──────────────────────────────────────────────────────────────────────────────────────────────────── ├───────────────────────────────────────────────────────────────────────────────────────────┤
│ ● d1f7f86 Start drafting peace treaty z6MknSL…StBU8Vi (you) - 575ed68 +0 -0 now │ │ ● d1f7f86 Start drafting peace treaty alice (you) - 575ed68 +0 -0 now │
╰──────────────────────────────────────────────────────────────────────────────────────────────────── ╰───────────────────────────────────────────────────────────────────────────────────────────╯
``` ```
Both issue and patch COBs can be listed. Both issue and patch COBs can be listed.

View File

@ -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"' $ 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 a8a9fee6c4f83578ab132d375f1da0c81282bef3 created
╭───────────────────────────────────────────────────────────────────╮ ╭────────────────────────────────────────────────────────────────────────
│ Title Add field │ │ Title Add field
│ Revision a8a9fee6c4f83578ab132d375f1da0c81282bef3 │ │ Revision a8a9fee6c4f83578ab132d375f1da0c81282bef3
│ Blob fbe268d13e60f1f3a1972e0ccd592f3cdecf08b5 │ │ Blob fbe268d13e60f1f3a1972e0ccd592f3cdecf08b5
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │ │ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
│ State accepted │ │ State accepted
│ Quorum yes │ │ Quorum yes
│ │
│ Add a new 'web' field │ │ Add a new 'web' field
├───────────────────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────────────────────────
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) │ │ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰───────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────────────
@@ -1,13 +1,14 @@ @@ -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 $ rad id update --title "Delete field" --description "Delete 'web'" --payload xyz.radicle.project web null
✓ Identity revision d373c35876833105f8aafed8b610660b5737cd67 created ✓ Identity revision d373c35876833105f8aafed8b610660b5737cd67 created
╭───────────────────────────────────────────────────────────────────╮ ╭────────────────────────────────────────────────────────────────────────
│ Title Delete field │ │ Title Delete field
│ Revision d373c35876833105f8aafed8b610660b5737cd67 │ │ Revision d373c35876833105f8aafed8b610660b5737cd67
│ Blob d96f425412c9f8ad5d9a9a05c9831d0728e2338d │ │ Blob d96f425412c9f8ad5d9a9a05c9831d0728e2338d
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │ │ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
│ State accepted │ │ State accepted
│ Quorum yes │ │ Quorum yes
│ │
│ Delete 'web' │ │ Delete 'web'
├───────────────────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────────────────────────
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) │ │ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰───────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────────────
@@ -1,14 +1,13 @@ @@ -1,14 +1,13 @@
{ {

View File

@ -48,7 +48,7 @@ $ rad inspect --payload
} }
} }
$ rad inspect --delegates $ rad inspect --delegates
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
``` ```
Finally, the `--history` flag allows you to examine the identity document's Finally, the `--history` flag allows you to examine the identity document's

View File

@ -8,7 +8,7 @@ $ rad issue open --title "flux capacitor underpowered" --description "Flux capac
╭─────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────╮
│ Title flux capacitor underpowered │ │ Title flux capacitor underpowered │
│ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │ │ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Status open │ │ Status open │
│ │ │ │
│ Flux capacitor power requirements exceed current supply │ │ Flux capacitor power requirements exceed current supply │
@ -19,11 +19,11 @@ The issue is now listed under our project.
``` ```
$ rad issue list $ rad issue list
╭─────────────────────────────────────────────────────────────────────────────────────────────────── ╭──────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Labels Assignees Opened │ │ ● ID Title Author Labels Assignees Opened │
├─────────────────────────────────────────────────────────────────────────────────────────────────── ├──────────────────────────────────────────────────────────────────────────────────────────┤
│ ● d87dcfe flux capacitor underpowered z6MknSL…StBU8Vi (you) now │ │ ● d87dcfe flux capacitor underpowered alice (you) now │
╰─────────────────────────────────────────────────────────────────────────────────────────────────── ╰──────────────────────────────────────────────────────────────────────────────────────────╯
``` ```
Show the issue information issue. Show the issue information issue.
@ -33,7 +33,7 @@ $ rad issue show d87dcfe
╭─────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────╮
│ Title flux capacitor underpowered │ │ Title flux capacitor underpowered │
│ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │ │ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Status open │ │ Status open │
│ │ │ │
│ Flux capacitor power requirements exceed current supply │ │ 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 $ rad issue list --assigned
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Labels Assignees Opened │ │ ● ID Title Author Labels Assignees Opened │
├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ├────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ● d87dcfe flux capacitor underpowered z6MknSL…StBU8Vi (you) good-first-issue z6MknSL…StBU8Vi now │ │ ● d87dcfe flux capacitor underpowered alice (you) good-first-issue alice now │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯
``` ```
Note: this can always be undone with the `unassign` subcommand. Note: this can always be undone with the `unassign` subcommand.
@ -91,16 +91,16 @@ $ rad issue show d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
╭─────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────╮
│ Title flux capacitor underpowered │ │ Title flux capacitor underpowered │
│ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │ │ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Labels good-first-issue │ │ Labels good-first-issue │
│ Status open │ │ Status open │
│ │ │ │
│ Flux capacitor power requirements exceed current supply │ │ Flux capacitor power requirements exceed current supply │
├─────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────┤
z6MknSL…StBU8Vi (you) now 2193e87 alice (you) now 2193e87
│ The flux capacitor needs 1.21 Gigawatts │ │ The flux capacitor needs 1.21 Gigawatts │
├─────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────┤
z6MknSL…StBU8Vi (you) now 880fdcd alice (you) now 880fdcd
│ More power! │ │ More power! │
╰─────────────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────────────╯
``` ```

View File

@ -34,9 +34,9 @@ $ rad follow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --alias Bo
✓ Follow policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (Bob) ✓ Follow policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (Bob)
``` ```
Now, when we use the `rad seed` command we will see Now, when we use the `rad seed` command we will see information for
information for repositories that we seed -- in this case a repositories that we seed -- in this case a repository that was already
repository that was already created: created:
``` ```
$ rad seed $ rad seed
@ -87,6 +87,35 @@ $ rad node stop
✗ Stopping node... error: node is not running ✗ 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: Some commands also give us a hint if the node isn't running:
``` (fail) ``` (fail)

View File

@ -45,11 +45,11 @@ To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkE
When listing, we see that it has one addition: When listing, we see that it has one addition:
``` ```
$ rad patch list $ rad patch list
╭───────────────────────────────────────────────────────────────────────────────── ╭────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │ │ ● ID Title Author Reviews Head + - Updated │
├───────────────────────────────────────────────────────────────────────────────── ├────────────────────────────────────────────────────────────────────────┤
│ ● 217f050 Add Alan z6MknSL…StBU8Vi (you) - 5c88a79 +1 -0 now │ │ ● 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 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 │ │ Title Add Alan │
│ Patch 217f050f8891def8fb863f7c0b4f85c89f97299d │ │ Patch 217f050f8891def8fb863f7c0b4f85c89f97299d │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 5c88a79d75f5c2b4cc51ee6f163d2db91ee198d7 │ │ Head 5c88a79d75f5c2b4cc51ee6f163d2db91ee198d7 │
│ Base f64fb2c8fe28f7c458c72ec8d700373924794943 │ │ Base f64fb2c8fe28f7c458c72ec8d700373924794943 │
│ Branches feature/1 │ │ Branches feature/1 │
@ -68,7 +68,7 @@ $ rad patch show -v -p 217f050
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ 5c88a79 Add Alan │ │ 5c88a79 Add Alan │
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ ● opened by z6MknSL…StBU8Vi (you) (5c88a79) now │ ● opened by alice (you) (5c88a79) now
╰────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────╯
commit 5c88a79d75f5c2b4cc51ee6f163d2db91ee198d7 commit 5c88a79d75f5c2b4cc51ee6f163d2db91ee198d7
@ -105,7 +105,7 @@ $ rad patch show -v e22ff008e2a0ed47262890d13263031d7555b555
╭────────────────────────────────────────────────────╮ ╭────────────────────────────────────────────────────╮
│ Title Add Mel │ │ Title Add Mel │
│ Patch e22ff008e2a0ed47262890d13263031d7555b555 │ │ Patch e22ff008e2a0ed47262890d13263031d7555b555 │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 7f63fcbcf23fc39eea784c091ad3d20d7e4bd005 │ │ Head 7f63fcbcf23fc39eea784c091ad3d20d7e4bd005 │
│ Base f64fb2c8fe28f7c458c72ec8d700373924794943 │ │ Base f64fb2c8fe28f7c458c72ec8d700373924794943 │
│ Branches feature/2 │ │ Branches feature/2 │
@ -115,7 +115,7 @@ $ rad patch show -v e22ff008e2a0ed47262890d13263031d7555b555
│ 7f63fcb Add Mel │ │ 7f63fcb Add Mel │
│ 5c88a79 Add Alan │ │ 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 │ Title Add Mel #2
│ Patch a467ffa260c4fbe355b6fb550ba0c4956078717e │ │ Patch a467ffa260c4fbe355b6fb550ba0c4956078717e │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 7f63fcbcf23fc39eea784c091ad3d20d7e4bd005 │ │ Head 7f63fcbcf23fc39eea784c091ad3d20d7e4bd005 │
│ Base 5c88a79d75f5c2b4cc51ee6f163d2db91ee198d7 │ │ Base 5c88a79d75f5c2b4cc51ee6f163d2db91ee198d7 │
│ Branches feature/2 │ │ Branches feature/2 │
@ -149,6 +149,6 @@ $ rad patch show -v a467ffa260c4fbe355b6fb550ba0c4956078717e
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ 7f63fcb Add Mel │ │ 7f63fcb Add Mel │
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ ● opened by z6MknSL…StBU8Vi (you) (7f63fcb) now │ ● opened by alice (you) (7f63fcb) now
╰────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────╯
``` ```

View File

@ -47,7 +47,7 @@ $ rad patch show 183d343ab47d7fe18baf1b24b7209ad033d7fe5c -v
╭────────────────────────────────────────────────────╮ ╭────────────────────────────────────────────────────╮
│ Title Add README, just for the fun │ │ Title Add README, just for the fun │
│ Patch 183d343ab47d7fe18baf1b24b7209ad033d7fe5c │ │ Patch 183d343ab47d7fe18baf1b24b7209ad033d7fe5c │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │ │ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
│ Base f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 │ │ Base f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 │
│ Branches add-readme │ │ Branches add-readme │
@ -57,7 +57,7 @@ $ rad patch show 183d343ab47d7fe18baf1b24b7209ad033d7fe5c -v
│ 27857ec Add README, just for the fun │ │ 27857ec Add README, just for the fun │
│ 3e674d1 Define power requirements │ │ 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 │ │ Title Add README, just for the fun │
│ Patch 183d343ab47d7fe18baf1b24b7209ad033d7fe5c │ │ Patch 183d343ab47d7fe18baf1b24b7209ad033d7fe5c │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │ │ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
│ Base 3e674d1a1df90807e934f9ae5da2591dd6848a33 │ │ Base 3e674d1a1df90807e934f9ae5da2591dd6848a33 │
│ Branches add-readme │ │ Branches add-readme │
@ -87,7 +87,7 @@ $ rad patch show 183d343 -v
├─────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────┤
│ 27857ec Add README, just for the fun │ │ 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 │ │ ↑ updated to ebe76f9c2148eb595d7a745f82275786bf3458c3 (27857ec) now │
╰─────────────────────────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────────────────────────╯
``` ```

View File

@ -18,7 +18,7 @@ $ rad patch show aa45913
╭─────────────────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────────────────╮
│ Title Define power requirements │ │ Title Define power requirements │
│ Patch aa45913e757cacd46972733bddee5472c78fa32a │ │ Patch aa45913e757cacd46972733bddee5472c78fa32a │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 639f44a25145a37f747f3c84265037a9461e44c5 │ │ Head 639f44a25145a37f747f3c84265037a9461e44c5 │
│ Branches patch/aa45913 │ │ Branches patch/aa45913 │
│ Commits ahead 3, behind 0 │ │ Commits ahead 3, behind 0 │
@ -30,7 +30,7 @@ $ rad patch show aa45913
│ 27857ec Add README, just for the fun │ │ 27857ec Add README, just for the fun │
│ 3e674d1 Define power requirements │ │ 3e674d1 Define power requirements │
├─────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────┤
│ ● opened by z6MknSL…StBU8Vi (you) (3e674d1) now │ ● opened by alice (you) (3e674d1) now
│ ↑ updated to 3156bed9d64d4675d6cf56612d217fc5f4e8a53a (27857ec) now │ │ ↑ updated to 3156bed9d64d4675d6cf56612d217fc5f4e8a53a (27857ec) now │
│ ↑ updated to 2f5324f61e05cda65b667eeea02570d077a8e724 (639f44a) now │ │ ↑ updated to 2f5324f61e05cda65b667eeea02570d077a8e724 (639f44a) now │
╰─────────────────────────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────────────────────────╯

View File

@ -21,7 +21,7 @@ $ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97
╭────────────────────────────────────────────────────╮ ╭────────────────────────────────────────────────────╮
│ Title Nothing yet │ │ Title Nothing yet │
│ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 │ │ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │ │ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │
│ Branches cloudhead/draft │ │ Branches cloudhead/draft │
│ Commits ahead 1, behind 0 │ │ Commits ahead 1, behind 0 │
@ -29,7 +29,7 @@ $ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ 2a46583 Nothing to see here.. │ │ 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 │ │ Title Nothing yet │
│ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 │ │ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │ │ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │
│ Branches cloudhead/draft │ │ Branches cloudhead/draft │
│ Commits ahead 1, behind 0 │ │ Commits ahead 1, behind 0 │
@ -52,7 +52,7 @@ $ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ 2a46583 Nothing to see here.. │ │ 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 │ │ Title Nothing yet │
│ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 │ │ Patch 97e18f8598237a396a1c0ac1509c89028e666c97 │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │ │ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │
│ Branches cloudhead/draft │ │ Branches cloudhead/draft │
│ Commits ahead 1, behind 0 │ │ Commits ahead 1, behind 0 │
@ -73,6 +73,6 @@ $ rad patch show 97e18f8598237a396a1c0ac1509c89028e666c97
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ 2a46583 Nothing to see here.. │ │ 2a46583 Nothing to see here.. │
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ ● opened by z6MknSL…StBU8Vi (you) (2a46583) [ .. ] │ ● opened by alice (you) (2a46583) [ .. ]
╰────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────╯
``` ```

View File

@ -44,7 +44,7 @@ $ rad patch show 89f7afb
╭─────────────────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────────────────╮
│ Title Add README, just for the fun │ │ Title Add README, just for the fun │
│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │ │ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 8945f6189adf027892c85ac57f7e9341049c2537 │ │ Head 8945f6189adf027892c85ac57f7e9341049c2537 │
│ Branches changes │ │ Branches changes │
│ Commits ahead 2, behind 0 │ │ Commits ahead 2, behind 0 │
@ -53,7 +53,7 @@ $ rad patch show 89f7afb
│ 8945f61 Define the LICENSE │ │ 8945f61 Define the LICENSE │
│ 03c02af Add README, just for the fun │ │ 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 │ │ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
╰─────────────────────────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────────────────────────╯
``` ```
@ -67,7 +67,7 @@ $ rad patch show 89f7afb
╭─────────────────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────────────────╮
│ Title Add Metadata │ │ Title Add Metadata │
│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │ │ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 8945f6189adf027892c85ac57f7e9341049c2537 │ │ Head 8945f6189adf027892c85ac57f7e9341049c2537 │
│ Branches changes │ │ Branches changes │
│ Commits ahead 2, behind 0 │ │ Commits ahead 2, behind 0 │
@ -78,7 +78,7 @@ $ rad patch show 89f7afb
│ 8945f61 Define the LICENSE │ │ 8945f61 Define the LICENSE │
│ 03c02af Add README, just for the fun │ │ 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 │ │ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
╰─────────────────────────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────────────────────────╯
``` ```
@ -95,7 +95,7 @@ $ rad patch show 89f7afb
╭─────────────────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────────────────╮
│ Title Add Metadata │ │ Title Add Metadata │
│ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │ │ Patch 89f7afb1511b976482b21f6b2f39aef7f4fb88a2 │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 8945f6189adf027892c85ac57f7e9341049c2537 │ │ Head 8945f6189adf027892c85ac57f7e9341049c2537 │
│ Branches changes │ │ Branches changes │
│ Commits ahead 2, behind 0 │ │ Commits ahead 2, behind 0 │
@ -106,7 +106,7 @@ $ rad patch show 89f7afb
│ 8945f61 Define the LICENSE │ │ 8945f61 Define the LICENSE │
│ 03c02af Add README, just for the fun │ │ 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 │ │ ↑ updated to 5d78dd5376453e25df5988ec86951c99cb73742c (8945f61) now │
╰─────────────────────────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────────────────────────╯
``` ```

View File

@ -16,7 +16,7 @@ $ rad patch show b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5
╭────────────────────────────────────────────────────╮ ╭────────────────────────────────────────────────────╮
│ Title Not a real change │ │ Title Not a real change │
│ Patch b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 │ │ Patch b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 51b2f0f77b9849bfaa3e9d3ff68ee2f57771d20c │ │ Head 51b2f0f77b9849bfaa3e9d3ff68ee2f57771d20c │
│ Branches feature/1 │ │ Branches feature/1 │
│ Commits ahead 1, behind 0 │ │ Commits ahead 1, behind 0 │
@ -24,7 +24,7 @@ $ rad patch show b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ 51b2f0f Not a real change │ │ 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 │ │ Title Not a real change │
│ Patch b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 │ │ Patch b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5 │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 4d272148458a17620541555b1f0905c01658aa9f │ │ Head 4d272148458a17620541555b1f0905c01658aa9f │
│ Branches feature/1 │ │ Branches feature/1 │
│ Commits ahead 2, behind 0 │ │ Commits ahead 2, behind 0 │
@ -66,7 +66,7 @@ $ rad patch show b6a23eb08656de0ef1fcc0b5fe8820841e5cb2e5
│ 4d27214 Rename readme file │ │ 4d27214 Rename readme file │
│ 51b2f0f Not a real change │ │ 51b2f0f Not a real change │
├─────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────┤
│ ● opened by z6MknSL…StBU8Vi (you) (51b2f0f) now │ ● opened by alice (you) (51b2f0f) now
│ ↑ updated to ea7def3857f62f404606d7cd6490cd0de4eaebd1 (4d27214) now │ │ ↑ updated to ea7def3857f62f404606d7cd6490cd0de4eaebd1 (4d27214) now │
╰─────────────────────────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────────────────────────╯
``` ```

View File

@ -23,7 +23,7 @@ $ rad patch show 6035d2f582afbe01ff23ea87528ae523d76875b6
╭────────────────────────────────────────────────────╮ ╭────────────────────────────────────────────────────╮
│ Title Add things #1 │ Title Add things #1
│ Patch 6035d2f582afbe01ff23ea87528ae523d76875b6 │ │ Patch 6035d2f582afbe01ff23ea87528ae523d76875b6 │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 42d894a83c9c356552a57af09ccdbd5587a99045 │ │ Head 42d894a83c9c356552a57af09ccdbd5587a99045 │
│ Branches feature/1 │ │ Branches feature/1 │
│ Commits ahead 1, behind 0 │ │ Commits ahead 1, behind 0 │
@ -33,7 +33,7 @@ $ rad patch show 6035d2f582afbe01ff23ea87528ae523d76875b6
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ 42d894a Add things │ │ 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 $ rad patch
╭──────────────────────────────────────────────────────────────────────────────────────── ╭───────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │ │ ● ID Title Author Reviews Head + - Updated │
├──────────────────────────────────────────────────────────────────────────────────────── ├───────────────────────────────────────────────────────────────────────────────┤
│ ● 6035d2f Add things #1 z6MknSL…StBU8Vi (you) - 42d894a +0 -0 now │ │ ● 6035d2f Add things #1 alice (you) - 42d894a +0 -0 now │
│ ● 9580891 Add more things z6MknSL…StBU8Vi (you) - 8b0ea80 +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: To update our patch, we simply push commits to the upstream branch:
@ -136,7 +136,7 @@ $ rad patch show 9580891
╭─────────────────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────────────────╮
│ Title Add more things │ │ Title Add more things │
│ Patch 95808913573cead52ad7b42c7b475260ec45c4b2 │ │ Patch 95808913573cead52ad7b42c7b475260ec45c4b2 │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 02bef3fac41b2f98bb3c02b868a53ddfecb55b5f │ │ Head 02bef3fac41b2f98bb3c02b868a53ddfecb55b5f │
│ Branches feature/2 │ │ Branches feature/2 │
│ Commits ahead 2, behind 0 │ │ Commits ahead 2, behind 0 │
@ -145,7 +145,7 @@ $ rad patch show 9580891
│ 02bef3f Improve code │ │ 02bef3f Improve code │
│ 8b0ea80 Add more things │ │ 8b0ea80 Add more things │
├─────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────┤
│ ● opened by z6MknSL…StBU8Vi (you) (8b0ea80) now │ ● opened by alice (you) (8b0ea80) now
│ ↑ updated to d7040c6c97629c2b94f86fb639bebbff5de39697 (02bef3f) now │ │ ↑ updated to d7040c6c97629c2b94f86fb639bebbff5de39697 (02bef3f) now │
╰─────────────────────────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────────────────────────╯
``` ```
@ -212,7 +212,7 @@ $ rad patch show 9580891
╭─────────────────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────────────────╮
│ Title Add more things │ │ Title Add more things │
│ Patch 95808913573cead52ad7b42c7b475260ec45c4b2 │ │ Patch 95808913573cead52ad7b42c7b475260ec45c4b2 │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 9304dbc445925187994a7a93222a3f8bde73b785 │ │ Head 9304dbc445925187994a7a93222a3f8bde73b785 │
│ Branches feature/2 │ │ Branches feature/2 │
│ Commits ahead 2, behind 0 │ │ Commits ahead 2, behind 0 │
@ -221,7 +221,7 @@ $ rad patch show 9580891
│ 9304dbc Amended commit │ │ 9304dbc Amended commit │
│ 8b0ea80 Add more things │ │ 8b0ea80 Add more things │
├─────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────┤
│ ● opened by z6MknSL…StBU8Vi (you) (8b0ea80) now │ ● opened by alice (you) (8b0ea80) now
│ ↑ updated to d7040c6c97629c2b94f86fb639bebbff5de39697 (02bef3f) now │ │ ↑ updated to d7040c6c97629c2b94f86fb639bebbff5de39697 (02bef3f) now │
│ ↑ updated to 670d02794aa05afd6e0851f4aa848bc87c4712c7 (9304dbc) now │ │ ↑ updated to 670d02794aa05afd6e0851f4aa848bc87c4712c7 (9304dbc) now │
╰─────────────────────────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────────────────────────╯

View File

@ -35,18 +35,18 @@ It will now be listed as one of the project's open patches.
``` ```
$ rad patch $ rad patch
╭────────────────────────────────────────────────────────────────────────────────────────────────── ╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │ │ ● ID Title Author Reviews Head + - Updated │
├────────────────────────────────────────────────────────────────────────────────────────────────── ├─────────────────────────────────────────────────────────────────────────────────────────┤
│ ● aa45913 Define power requirements z6MknSL…StBU8Vi (you) - 3e674d1 +0 -0 now │ │ ● aa45913 Define power requirements alice (you) - 3e674d1 +0 -0 now │
╰────────────────────────────────────────────────────────────────────────────────────────────────── ╰─────────────────────────────────────────────────────────────────────────────────────────╯
``` ```
``` ```
$ rad patch show aa45913e757cacd46972733bddee5472c78fa32a -p $ rad patch show aa45913e757cacd46972733bddee5472c78fa32a -p
╭────────────────────────────────────────────────────╮ ╭────────────────────────────────────────────────────╮
│ Title Define power requirements │ │ Title Define power requirements │
│ Patch aa45913e757cacd46972733bddee5472c78fa32a │ │ Patch aa45913e757cacd46972733bddee5472c78fa32a │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │ │ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │
│ Branches flux-capacitor-power │ │ Branches flux-capacitor-power │
│ Commits ahead 1, behind 0 │ │ Commits ahead 1, behind 0 │
@ -56,7 +56,7 @@ $ rad patch show aa45913e757cacd46972733bddee5472c78fa32a -p
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ 3e674d1 Define power requirements │ │ 3e674d1 Define power requirements │
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ ● opened by z6MknSL…StBU8Vi (you) (3e674d1) now │ ● opened by alice (you) (3e674d1) now
╰────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────╯
commit 3e674d1a1df90807e934f9ae5da2591dd6848a33 commit 3e674d1a1df90807e934f9ae5da2591dd6848a33
@ -75,11 +75,11 @@ We can also list only patches that we've authored.
``` ```
$ rad patch list --authored $ rad patch list --authored
╭────────────────────────────────────────────────────────────────────────────────────────────────── ╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │ │ ● ID Title Author Reviews Head + - Updated │
├────────────────────────────────────────────────────────────────────────────────────────────────── ├─────────────────────────────────────────────────────────────────────────────────────────┤
│ ● aa45913 Define power requirements z6MknSL…StBU8Vi (you) - 3e674d1 +0 -0 now │ │ ● aa45913 Define power requirements alice (you) - 3e674d1 +0 -0 now │
╰────────────────────────────────────────────────────────────────────────────────────────────────── ╰─────────────────────────────────────────────────────────────────────────────────────────╯
``` ```
We can also see that it set an upstream for our patch branch: 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 │ │ Title Define power requirements │
│ Patch aa45913e757cacd46972733bddee5472c78fa32a │ │ Patch aa45913e757cacd46972733bddee5472c78fa32a │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Labels fun │ │ Labels fun │
│ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │ │ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │
│ Branches flux-capacitor-power │ │ Branches flux-capacitor-power │
@ -110,7 +110,7 @@ $ rad patch show aa45913
├────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────┤
│ 3e674d1 Define power requirements │ │ 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 $ 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! │ │ I cannot wait to get back to the 90s! │
╰───────────────────────────────────────╯ ╰───────────────────────────────────────╯
$ rad patch comment aa45913 --message 'My favorite decade!' --reply-to 686ec1c -q --no-announce $ 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 │ │ Title Define power requirements │
│ Patch aa45913e757cacd46972733bddee5472c78fa32a │ │ Patch aa45913e757cacd46972733bddee5472c78fa32a │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Labels fun │ │ Labels fun │
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │ │ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
│ Branches flux-capacitor-power, patch/aa45913 │ │ Branches flux-capacitor-power, patch/aa45913 │
@ -177,16 +177,16 @@ $ rad patch show aa45913
│ 27857ec Add README, just for the fun │ │ 27857ec Add README, just for the fun │
│ 3e674d1 Define power requirements │ │ 3e674d1 Define power requirements │
├─────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────┤
│ ● opened by z6MknSL…StBU8Vi (you) (3e674d1) now │ ● opened by alice (you) (3e674d1) now
│ ↑ updated to 6e5a3b7b2ce27b32e7ccc2f0b3f4594897dde638 (27857ec) now │ │ ↑ updated to 6e5a3b7b2ce27b32e7ccc2f0b3f4594897dde638 (27857ec) now │
│ └─ ✓ accepted by z6MknSL…StBU8Vi (you) now │ └─ ✓ accepted by alice (you) now
╰─────────────────────────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────────────────────────╯
$ rad patch list $ rad patch list
╭────────────────────────────────────────────────────────────────────────────────────────────────── ╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │ │ ● ID Title Author Reviews Head + - Updated │
├────────────────────────────────────────────────────────────────────────────────────────────────── ├─────────────────────────────────────────────────────────────────────────────────────────┤
│ ● aa45913 Define power requirements z6MknSL…StBU8Vi (you) ✔ 27857ec +0 -0 now │ │ ● aa45913 Define power requirements alice (you) ✔ 27857ec +0 -0 now │
╰────────────────────────────────────────────────────────────────────────────────────────────────── ╰─────────────────────────────────────────────────────────────────────────────────────────╯
``` ```
If you make a mistake on the patch description, you can always change it! 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 │ │ Title Define power requirements │
│ Patch aa45913e757cacd46972733bddee5472c78fa32a │ │ Patch aa45913e757cacd46972733bddee5472c78fa32a │
│ Author z6MknSL…StBU8Vi (you) │ Author alice (you)
│ Labels fun │ │ Labels fun │
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │ │ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
│ Branches flux-capacitor-power, patch/aa45913 │ │ Branches flux-capacitor-power, patch/aa45913 │
@ -209,8 +209,8 @@ $ rad patch show aa45913
│ 27857ec Add README, just for the fun │ │ 27857ec Add README, just for the fun │
│ 3e674d1 Define power requirements │ │ 3e674d1 Define power requirements │
├─────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────┤
│ ● opened by z6MknSL…StBU8Vi (you) (3e674d1) now │ ● opened by alice (you) (3e674d1) now
│ ↑ updated to 6e5a3b7b2ce27b32e7ccc2f0b3f4594897dde638 (27857ec) now │ │ ↑ updated to 6e5a3b7b2ce27b32e7ccc2f0b3f4594897dde638 (27857ec) now │
│ └─ ✓ accepted by z6MknSL…StBU8Vi (you) now │ └─ ✓ accepted by alice (you) now
╰─────────────────────────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────────────────────────╯
``` ```

View File

@ -27,18 +27,18 @@ repository private again __will not_ be replicated.
``` ```
$ rad id update --visibility private --title "Privatise" --description "Reverting the rad publish event" $ rad id update --visibility private --title "Privatise" --description "Reverting the rad publish event"
✓ Identity revision 774cc1e72641d97d7dc9377745b7f454a9171747 created ✓ Identity revision 774cc1e72641d97d7dc9377745b7f454a9171747 created
╭───────────────────────────────────────────────────────────────────╮ ╭────────────────────────────────────────────────────────────────────────
│ Title Privatise │ │ Title Privatise
│ Revision 774cc1e72641d97d7dc9377745b7f454a9171747 │ │ Revision 774cc1e72641d97d7dc9377745b7f454a9171747
│ Blob 88f759a4d46e9535766fccec0cbfe1fed6160b1a │ │ Blob 88f759a4d46e9535766fccec0cbfe1fed6160b1a
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │ │ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
│ State accepted │ │ State accepted
│ Quorum yes │ │ Quorum yes
│ │
│ Reverting the rad publish event │ │ Reverting the rad publish event
├───────────────────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────────────────────────
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) │ │ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰───────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────────────
@@ -1,13 +1,16 @@ @@ -1,13 +1,16 @@
{ {

View File

@ -287,11 +287,14 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
if options.verbose { if options.verbose {
term::blob(json::to_string_pretty(&proj)?); term::blob(json::to_string_pretty(&proj)?);
} }
// It's important to seed our own repositories to make sure that our node signals // 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. // interest for them. This ensures that messages relating to them are relayed to us.
if options.seed { if options.seed {
cli::project::seed(rid, options.scope, &mut node, profile)?; 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() { 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 events = node.subscribe(DEFAULT_SUBSCRIBE_TIMEOUT)?;
let sessions = node.sessions()?; let sessions = node.sessions()?;
node.update_inventory(rid)?;
spinner.message("Announcing.."); spinner.message("Announcing..");
if !sessions.iter().any(|s| s.is_connected()) { if !sessions.iter().any(|s| s.is_connected()) {

View File

@ -27,7 +27,7 @@ pub fn run<S: node::routing::Store>(
fn print_table(entries: impl IntoIterator<Item = (RepoId, NodeId)>) { fn print_table(entries: impl IntoIterator<Item = (RepoId, NodeId)>) {
let mut t = term::Table::new(term::table::TableOptions::bordered()); 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("RID")),
term::format::default(String::from("NID")), term::format::default(String::from("NID")),
]); ]);

View File

@ -1,9 +1,10 @@
use localtime::LocalTime;
use radicle::prelude::*; use radicle::prelude::*;
use crate::git; use crate::git;
use radicle::git::RefStr; use radicle::git::RefStr;
use radicle::node::policy::Scope; use radicle::node::policy::Scope;
use radicle::node::{Handle, NodeId}; use radicle::node::{routing, Handle, NodeId};
use radicle::Node; use radicle::Node;
/// Setup a repository remote and tracking branch. /// 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, /// Add the repo to our inventory.
/// by updating the policy database directly. pub fn add_inventory(
rid: RepoId,
node: &mut Node,
profile: &Profile,
) -> Result<bool, anyhow::Error> {
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( pub fn seed(
rid: RepoId, rid: RepoId,
scope: Scope, scope: Scope,
@ -64,7 +85,15 @@ pub fn seed(
Ok(updated) => Ok(updated), Ok(updated) => Ok(updated),
Err(e) if e.is_connection_err() => { Err(e) if e.is_connection_err() => {
let mut config = profile.policies_mut()?; 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()), Err(e) => Err(e.into()),
} }
@ -77,7 +106,12 @@ pub fn unseed(rid: RepoId, node: &mut Node, profile: &Profile) -> Result<bool, a
Ok(updated) => Ok(updated), Ok(updated) => Ok(updated),
Err(e) if e.is_connection_err() => { Err(e) if e.is_connection_err() => {
let mut config = profile.policies_mut()?; 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()), Err(e) => Err(e.into()),
} }

View File

@ -730,6 +730,7 @@ fn rad_node() {
Address::from(net::SocketAddr::from(([41, 12, 98, 112], 8776))), Address::from(net::SocketAddr::from(([41, 12, 98, 112], 8776))),
Address::from_str("seed.cloudhead.io:8776").unwrap(), Address::from_str("seed.cloudhead.io:8776").unwrap(),
], ],
seeding_policy: SeedingPolicy::Block,
..Config::test(Alias::new("alice")) ..Config::test(Alias::new("alice"))
}); });
let working = tempfile::tempdir().unwrap(); let working = tempfile::tempdir().unwrap();
@ -1286,6 +1287,8 @@ fn rad_clone_partial_fail() {
let working = environment.tmp().join("working"); let working = environment.tmp().join("working");
let carol = NodeId::from_str("z6MksFqXN3Yhqk8pTJdUGLwBTkRfQvwZXPqR2qMEhbS9wzpT").unwrap(); let carol = NodeId::from_str("z6MksFqXN3Yhqk8pTJdUGLwBTkRfQvwZXPqR2qMEhbS9wzpT").unwrap();
logger::init(log::Level::Debug);
// Setup a test project. // Setup a test project.
let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack"); let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack");
@ -1323,7 +1326,8 @@ fn rad_clone_partial_fail() {
eve.connect(&alice); eve.connect(&alice);
eve.connect(&bob); eve.connect(&bob);
eve.routes_to(&[(acme, carol), (acme, bob.id), (acme, alice.id)]); 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( test(
"examples/rad-clone-partial-fail.md", "examples/rad-clone-partial-fail.md",

View File

@ -125,10 +125,6 @@ impl Runtime {
log::warn!(target: "node", "Unused or deprecated configuration attribute {:?}", key); log::warn!(target: "node", "Unused or deprecated configuration attribute {:?}", key);
} }
log::info!(target: "node", "Opening node database.."); 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.."); log::info!(target: "node", "Opening policy database..");
let policies = home.policies_mut()?; let policies = home.policies_mut()?;
@ -175,6 +171,18 @@ impl Runtime {
.expect("Runtime::init: unable to solve proof-of-work puzzle") .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()? { if config.connect.is_empty() && stores.addresses().is_empty()? {
log::info!(target: "node", "Address book is empty. Adding bootstrap nodes.."); log::info!(target: "node", "Address book is empty. Adding bootstrap nodes..");

View File

@ -10,7 +10,7 @@ pub mod message;
pub mod session; pub mod session;
use std::collections::hash_map::Entry; 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::net::IpAddr;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::Arc; use std::sync::Arc;
@ -391,6 +391,12 @@ where
} }
} }
impl<D> AsMut<D> for Stores<D> {
fn as_mut(&mut self) -> &mut D {
&mut self.0
}
}
impl<D> From<D> for Stores<D> { impl<D> From<D> for Stores<D> {
fn from(db: D) -> Self { fn from(db: D) -> Self {
Self(db) Self(db)
@ -555,16 +561,23 @@ where
/// simply not announcing it anymore, it will eventually be pruned by nodes. /// simply not announcing it anymore, it will eventually be pruned by nodes.
pub fn unseed(&mut self, id: &RepoId) -> Result<bool, policy::Error> { pub fn unseed(&mut self, id: &RepoId) -> Result<bool, policy::Error> {
let updated = self.policies.unseed(id)?; 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 if updated {
// and the filter is really out of date. // 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
// TODO: Share this code with initialization code. // and the filter is really out of date.
self.filter = Filter::new( //
self.policies // TODO: Share this code with initialization code.
.seed_policies()? self.filter = Filter::new(
.filter_map(|t| (t.policy.is_allow()).then_some(t.rid)), 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) Ok(updated)
} }
@ -623,12 +636,18 @@ where
/// Lookup a repository, both locally and in the routing table. /// Lookup a repository, both locally and in the routing table.
pub fn lookup(&self, rid: RepoId) -> Result<Lookup, LookupError> { pub fn lookup(&self, rid: RepoId) -> Result<Lookup, LookupError> {
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 { Ok(Lookup { local, remote })
local: self.storage.get(rid)?,
remote,
})
} }
/// Initialize service with current time. Call this once. /// Initialize service with current time. Call this once.
@ -662,27 +681,13 @@ where
Err(e) => error!(target: "service", "Error checking refs database: {e}"), 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 let announced = self
.db .db
.seeds() .seeds()
.seeded_by(&nid)? .seeded_by(&nid)?
.collect::<Result<HashMap<_, _>, _>>()?; .collect::<Result<HashMap<_, _>, _>>()?;
let mut inventory = BTreeSet::new();
for repo in self.storage.repositories()? { for repo in self.storage.repositories()? {
let rid = repo.rid; let rid = repo.rid;
@ -693,7 +698,7 @@ where
} }
// Add public repositories to inventory. // Add public repositories to inventory.
if repo.doc.visibility.is_public() { 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. // If we have no owned refs for this repo, then there's nothing to announce.
let Some(updated_at) = repo.synced_at else { 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 // 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 // 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. // user creates a new repository while the node is stopped.
@ -805,7 +809,7 @@ where
if now - self.last_sync >= SYNC_INTERVAL { if now - self.last_sync >= SYNC_INTERVAL {
trace!(target: "service", "Running 'sync' task..."); 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}"); error!(target: "service", "Error fetching missing inventory: {e}");
} }
self.outbox.wakeup(SYNC_INTERVAL); self.outbox.wakeup(SYNC_INTERVAL);
@ -814,9 +818,7 @@ where
if now - self.last_announce >= ANNOUNCE_INTERVAL { if now - self.last_announce >= ANNOUNCE_INTERVAL {
trace!(target: "service", "Running 'announce' task..."); trace!(target: "service", "Running 'announce' task...");
if let Err(err) = self.announce_inventory() { self.announce_inventory();
error!(target: "service", "Error announcing inventory: {err}");
}
self.outbox.wakeup(ANNOUNCE_INTERVAL); self.outbox.wakeup(ANNOUNCE_INTERVAL);
} }
if now - self.last_prune >= PRUNE_INTERVAL { if now - self.last_prune >= PRUNE_INTERVAL {
@ -939,19 +941,16 @@ where
} }
} }
Command::AnnounceInventory => { Command::AnnounceInventory => {
if let Err(err) = self.announce_inventory() { self.announce_inventory();
error!(target: "service", "Error announcing inventory: {err}"); }
Command::UpdateInventory(rid, resp) => match self.add_inventory(rid) {
Ok(updated) => {
resp.send(updated).ok();
} }
} Err(e) => {
Command::UpdateInventory(rid, resp) => { error!(target: "service", "Error adding {rid} to inventory: {e}");
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();
}
Command::QueryState(query, sender) => { Command::QueryState(query, sender) => {
sender.send(query(self)).ok(); sender.send(query(self)).ok();
} }
@ -1171,8 +1170,9 @@ where
if clone && doc.visibility.is_public() { if clone && doc.visibility.is_public() {
debug!(target: "service", "Updating and announcing inventory for cloned repository {rid}.."); debug!(target: "service", "Updating and announcing inventory for cloned repository {rid}..");
self.storage.insert(rid); if let Err(e) = self.add_inventory(rid) {
self.sync_and_announce_inventory(); error!(target: "service", "Error announcing inventory for {rid}: {e}");
}
} }
// It's possible for a fetch to succeed but nothing was updated. // It's possible for a fetch to succeed but nothing was updated.
@ -1539,8 +1539,6 @@ where
return Ok(None); return Ok(None);
} }
} }
let inventory = self.storage.inventory_ref();
let mut missing = Vec::new(); let mut missing = Vec::new();
for id in message.inventory.as_slice() { 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. // 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 we do have it, only fetch after receiving a ref announcement.
if !inventory.contains(id) { match self.db.routing().entry(id, self.nid()) {
missing.push(*id); 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 { for rid in missing {
debug!(target: "service", "Missing seeded inventory {rid}; initiating fetch.."); debug!(target: "service", "Missing seeded inventory {rid}; initiating fetch..");
self.fetch(rid, *announcer, FETCH_TIMEOUT, None); self.fetch(rid, *announcer, FETCH_TIMEOUT, None);
@ -1931,14 +1935,59 @@ where
> 0 > 0
} }
/// Update our routing table with our local node's inventory. /// Remove a local repository from our inventory.
fn sync_inventory(&mut self) -> Result<SyncedRouting, Error> { fn remove_inventory(&mut self, rid: &RepoId) -> Result<bool, Error> {
let inventory = self.storage.inventory()?; let node = self.node_id();
let result = self.sync_routing(inventory.clone(), self.node_id(), self.clock.into())?; let now = self.timestamp();
// Update cached inventory message.
self.inventory = gossip::inventory(self.timestamp(), inventory);
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<bool, Error> {
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<HashSet<RepoId>, Error> {
self.db
.routing()
.get_resources(self.nid())
.map_err(Error::from)
} }
/// Process a peer inventory announcement by updating our routing table. /// Process a peer inventory announcement by updating our routing table.
@ -2093,22 +2142,6 @@ where
Ok((refs, timestamp)) 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 { fn reconnect(&mut self, nid: NodeId, addr: Address) -> bool {
if let Some(sess) = self.sessions.get_mut(&nid) { if let Some(sess) = self.sessions.get_mut(&nid) {
sess.to_initial(); sess.to_initial();
@ -2263,7 +2296,9 @@ where
} }
/// Announce our inventory to all connected peers. /// 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()); let msg = AnnouncementMessage::from(self.inventory.clone());
self.outbox.announce( self.outbox.announce(
@ -2272,8 +2307,6 @@ where
self.db.gossip_mut(), self.db.gossip_mut(),
); );
self.last_announce = self.clock; self.last_announce = self.clock;
Ok(())
} }
fn prune_routing_entries(&mut self, now: &LocalTime) -> Result<(), routing::Error> { 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. /// Fetch all repositories that are seeded but missing from storage.
fn fetch_missing_inventory(&mut self) -> Result<(), Error> { fn fetch_missing_repositories(&mut self) -> Result<(), Error> {
let inventory = self.storage().inventory()?; for policy in self.policies.seed_policies()? {
let missing = self let rid = policy.rid;
.policies
.seed_policies()?
.filter_map(|t| (t.policy.is_allow()).then_some(t.rid))
.filter(|rid| !inventory.contains(rid));
for rid in missing { if !policy.is_allow() {
continue;
}
if self.storage.contains(&rid)? {
continue;
}
match self.seeds(&rid) { match self.seeds(&rid) {
Ok(seeds) => { Ok(seeds) => {
if let Some(connected) = NonEmpty::from_vec(seeds.connected().collect()) { if let Some(connected) = NonEmpty::from_vec(seeds.connected().collect()) {

View File

@ -9,6 +9,7 @@ use std::{
use crossbeam_channel as chan; use crossbeam_channel as chan;
use localtime::LocalTime;
use radicle::cob::cache::COBS_DB_FILE; use radicle::cob::cache::COBS_DB_FILE;
use radicle::cob::issue; use radicle::cob::issue;
use radicle::crypto::ssh::{keystore::MemorySigner, Keystore}; use radicle::crypto::ssh::{keystore::MemorySigner, Keystore};
@ -18,7 +19,6 @@ use radicle::git::refname;
use radicle::identity::{RepoId, Visibility}; use radicle::identity::{RepoId, Visibility};
use radicle::node::config::ConnectAddress; use radicle::node::config::ConnectAddress;
use radicle::node::policy::store as policy; use radicle::node::policy::store as policy;
use radicle::node::routing::Store;
use radicle::node::seed::Store as _; use radicle::node::seed::Store as _;
use radicle::node::Database; use radicle::node::Database;
use radicle::node::{Alias, POLICIES_DB_FILE}; 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 keypair = KeyPair::from_seed(Seed::from([!(self.users as u8); 32]));
let policies_db = home.node().join(POLICIES_DB_FILE); let policies_db = home.node().join(POLICIES_DB_FILE);
let cobs_db = home.cobs().join(COBS_DB_FILE); let cobs_db = home.cobs().join(COBS_DB_FILE);
let now = LocalTime::now();
config.write(&home.config()).unwrap(); config.write(&home.config()).unwrap();
let storage = Storage::open( let storage = Storage::open(
home.storage(), home.storage(),
git::UserInfo { git::UserInfo {
alias, alias: alias.clone(),
key: keypair.pk.into(), key: keypair.pk.into(),
}, },
) )
.unwrap(); .unwrap();
let public_key = keypair.pk.into();
policy::Store::open(policies_db).unwrap(); policy::Store::open(policies_db).unwrap();
home.database_mut().unwrap(); // Just create the database.
cob::cache::Store::open(cobs_db).unwrap(); 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()); transport::local::register(storage.clone());
keystore.store(keypair.clone(), "radicle", None).unwrap(); keystore.store(keypair.clone(), "radicle", None).unwrap();
@ -139,7 +150,7 @@ impl Environment {
home, home,
storage, storage,
keystore, keystore,
public_key: keypair.pk.into(), public_key,
config, config,
} }
} }
@ -248,10 +259,15 @@ impl<G: Signer + cyphernet::Ecdh> NodeHandle<G> {
/// Get routing table entries. /// Get routing table entries.
pub fn routing(&self) -> impl Iterator<Item = (RepoId, NodeId)> { pub fn routing(&self) -> impl Iterator<Item = (RepoId, NodeId)> {
Database::reader(self.home.node().join(node::NODE_DB_FILE)) use node::routing::Store as _;
.unwrap()
.entries() self.home.routing_mut().unwrap().entries().unwrap()
.unwrap() }
pub fn inventory(&self) -> impl Iterator<Item = RepoId> + '_ {
self.routing()
.filter(|(_, n)| *n == self.id)
.map(|(r, _)| r)
} }
/// Get sync status of a repo. /// 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)); all_routes.insert((rid, seed_id));
} }
// Routes from the local inventory. // Routes from the local inventory.
for rid in node.storage.inventory().unwrap() { for rid in node.inventory() {
all_routes.insert((rid, node.id)); all_routes.insert((rid, node.id));
} }
} }

View File

@ -1,4 +1,5 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::collections::HashSet;
use std::iter; use std::iter;
use std::net; use std::net;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@ -19,6 +20,7 @@ use crate::crypto::test::signer::MockSigner;
use crate::crypto::Signer; use crate::crypto::Signer;
use crate::identity::RepoId; use crate::identity::RepoId;
use crate::node; use crate::node;
use crate::node::routing::Store as _;
use crate::prelude::*; use crate::prelude::*;
use crate::runtime::Emitter; use crate::runtime::Emitter;
use crate::service; use crate::service;
@ -27,7 +29,6 @@ use crate::service::message::*;
use crate::service::policy::{Scope, SeedingPolicy}; use crate::service::policy::{Scope, SeedingPolicy};
use crate::service::*; use crate::service::*;
use crate::storage::git::transport::remote; use crate::storage::git::transport::remote;
use crate::storage::Inventory;
use crate::storage::{RemoteId, WriteStorage}; use crate::storage::{RemoteId, WriteStorage};
use crate::test::storage::MockStorage; use crate::test::storage::MockStorage;
use crate::test::{arbitrary, fixtures, simulator}; use crate::test::{arbitrary, fixtures, simulator};
@ -99,7 +100,6 @@ where
pub struct Config<G: Signer + 'static> { pub struct Config<G: Signer + 'static> {
pub config: service::Config, pub config: service::Config,
pub db: Stores<node::Database>,
pub local_time: LocalTime, pub local_time: LocalTime,
pub policy: SeedingPolicy, pub policy: SeedingPolicy,
pub signer: G, pub signer: G,
@ -112,13 +112,10 @@ impl Default for Config<MockSigner> {
let mut rng = fastrand::Rng::new(); let mut rng = fastrand::Rng::new();
let signer = MockSigner::new(&mut rng); let signer = MockSigner::new(&mut rng);
let tmp = tempfile::TempDir::new().unwrap(); let tmp = tempfile::TempDir::new().unwrap();
let db = Database::open(tmp.path().join(node::NODE_DB_FILE)) let config = service::Config::test(Alias::from_str("mocky").unwrap());
.unwrap()
.into();
Config { Config {
config: service::Config::test(Alias::from_str("mocky").unwrap()), config,
db,
local_time: LocalTime::now(), local_time: LocalTime::now(),
policy: SeedingPolicy::default(), policy: SeedingPolicy::default(),
signer, signer,
@ -164,19 +161,32 @@ where
let id = *config.signer.public_key(); let id = *config.signer.public_key();
let ip = ip.into(); let ip = ip.into();
let local_addr = net::SocketAddr::new(ip, config.rng.u16(..)); 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. // Make sure the peer address is advertized.
config.config.external_addresses.push(local_addr.into()); config.config.external_addresses.push(local_addr.into());
for rid in &inventory { for repo in &inventory {
policies.seed(rid, Scope::Followed).unwrap(); 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 = let announcement =
service::gossip::node(&config.config, Timestamp::from(config.local_time) + 1); service::gossip::node(&config.config, Timestamp::from(config.local_time) + 1);
let emitter: Emitter<Event> = Default::default(); let emitter: Emitter<Event> = Default::default();
let service = Service::new( let service = Service::new(
config.config, config.config,
config.db, db,
storage, storage,
policies, policies,
config.signer, config.signer,
@ -255,8 +265,12 @@ where
(*self.clock()).into() (*self.clock()).into()
} }
pub fn inventory(&self) -> Inventory { pub fn inventory(&self) -> HashSet<RepoId> {
self.service.storage().inventory().unwrap() self.service
.database()
.routing()
.get_resources(self.nid())
.unwrap()
} }
pub fn git_url(&self, repo: RepoId, namespace: Option<RemoteId>) -> remote::Url { pub fn git_url(&self, repo: RepoId, namespace: Option<RemoteId>) -> remote::Url {

View File

@ -274,21 +274,21 @@ fn test_inventory_sync() {
let bob_storage = fixtures::storage(tmp.path().join("bob"), &bob_signer).unwrap(); 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 bob = Peer::with_storage("bob", [8, 8, 8, 8], bob_storage);
let now = LocalTime::now().into(); let now = LocalTime::now().into();
let projs = bob.storage().inventory().unwrap(); let repos = bob.inventory().into_iter().collect::<Vec<_>>();
alice.connect_to(&bob); alice.connect_to(&bob);
alice.receive( alice.receive(
bob.id(), bob.id(),
Message::inventory( Message::inventory(
InventoryAnnouncement { InventoryAnnouncement {
inventory: projs.clone().try_into().unwrap(), inventory: repos.clone().try_into().unwrap(),
timestamp: now, timestamp: now,
}, },
bob.signer(), bob.signer(),
), ),
); );
for proj in &projs { for proj in &repos {
let seeds = alice.database().routing().get(proj).unwrap(); let seeds = alice.database().routing().get(proj).unwrap();
assert!(seeds.contains(&bob.node_id())); assert!(seeds.contains(&bob.node_id()));
} }
@ -701,12 +701,7 @@ fn test_refs_announcement_relay() {
) )
.initialized() .initialized()
}; };
let bob_inv = bob let bob_inv = bob.inventory().into_iter().collect::<Vec<_>>();
.storage()
.inventory()
.unwrap()
.into_iter()
.collect::<Vec<_>>();
alice.seed(&bob_inv[0], policy::Scope::All).unwrap(); alice.seed(&bob_inv[0], policy::Scope::All).unwrap();
alice.seed(&bob_inv[1], 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() .initialized()
}; };
let bob_inv = bob.storage().inventory().unwrap(); let bob_inv = bob.inventory();
let rid = bob_inv.first().unwrap(); let rid = bob_inv.iter().next().unwrap();
alice.seed(rid, policy::Scope::Followed).unwrap(); alice.seed(rid, policy::Scope::Followed).unwrap();
alice.connect_to(&bob); 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]); 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. // Make sure alice's service wasn't initialized before.
assert_eq!(*alice.clock(), LocalTime::default()); assert_eq!(*alice.clock(), LocalTime::default());
@ -899,6 +891,11 @@ fn test_refs_announcement_offline() {
alice.connect_to(&bob); alice.connect_to(&bob);
alice.receive(bob.id, Message::Subscribe(Subscribe::all())); 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 // Alice announces the refs of all projects since she hasn't announced refs for these projects
// yet. // yet.
for msg in alice.messages(bob.id()) { for msg in alice.messages(bob.id()) {
@ -1429,7 +1426,7 @@ fn test_queued_fetch_max_capacity() {
#[test] #[test]
fn test_queued_fetch_from_ann_same_rid() { 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 mut repo_keys = storage.repos.keys();
let rid = *repo_keys.next().unwrap(); let rid = *repo_keys.next().unwrap();
let mut alice = Peer::with_storage("alice", [7, 7, 7, 7], storage); 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(), timestamp: bob.timestamp(),
}; };
logger::init(log::Level::Trace);
alice.seed(&rid, policy::Scope::All).unwrap(); alice.seed(&rid, policy::Scope::All).unwrap();
alice.connect_to(&bob); alice.connect_to(&bob);
alice.connect_to(&eve); alice.connect_to(&eve);
@ -1713,9 +1708,27 @@ fn test_init_and_seed() {
fn prop_inventory_exchange_dense() { fn prop_inventory_exchange_dense() {
fn property(alice_inv: MockStorage, bob_inv: MockStorage, eve_inv: MockStorage) { fn property(alice_inv: MockStorage, bob_inv: MockStorage, eve_inv: MockStorage) {
let rng = fastrand::Rng::new(); let rng = fastrand::Rng::new();
let alice = Peer::with_storage("alice", [7, 7, 7, 7], alice_inv.clone()); let alice = Peer::with_storage(
let mut bob = Peer::with_storage("bob", [8, 8, 8, 8], bob_inv.clone()); "alice",
let mut eve = Peer::with_storage("eve", [9, 9, 9, 9], eve_inv.clone()); [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()); let mut routing = RandomMap::with_hasher(rng.clone().into());
for (inv, peer) in &[ for (inv, peer) in &[
@ -1913,7 +1926,6 @@ fn test_announcement_message_amplification() {
.repos .repos
.insert(rid, gen::<MockRepository>(1)); .insert(rid, gen::<MockRepository>(1));
alice.command(Command::UpdateInventory(rid, tx)); alice.command(Command::UpdateInventory(rid, tx));
alice.command(Command::AnnounceInventory);
sim.run_while([&mut alice, &mut bob, &mut eve, &mut zod, &mut tom], |s| { sim.run_while([&mut alice, &mut bob, &mut eve, &mut zod, &mut tom], |s| {
s.elapsed() < LocalDuration::from_mins(3) s.elapsed() < LocalDuration::from_mins(3)

View File

@ -15,6 +15,7 @@ use std::{fmt, time};
use sqlite as sql; use sqlite as sql;
use thiserror::Error; use thiserror::Error;
use crate::node::{address, Address, Alias, Features, KnownAddress, NodeId, Timestamp};
use crate::sql::transaction; use crate::sql::transaction;
/// How long to wait for the database lock to be released before failing a read. /// 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)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
/// Initialization error.
#[error("error initializing the database: {0}")]
Init(#[from] address::store::Error),
/// An Internal error. /// An Internal error.
#[error("internal error: {0}")] #[error("internal error: {0}")]
Internal(#[from] sql::Error), Internal(#[from] sql::Error),
@ -121,6 +125,30 @@ impl Database {
Ok(self) 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<Item = &'a Address>,
) -> Result<Self, Error> {
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. /// Create a new in-memory database.
pub fn memory() -> Result<Self, Error> { pub fn memory() -> Result<Self, Error> {
let db = sql::Connection::open_thread_safe(":memory:")?; let db = sql::Connection::open_thread_safe(":memory:")?;

View File

@ -14,6 +14,7 @@ use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{fs, io}; use std::{fs, io};
use localtime::LocalTime;
use serde::Serialize; use serde::Serialize;
use serde_json as json; use serde_json as json;
use thiserror::Error; use thiserror::Error;
@ -307,8 +308,16 @@ impl Profile {
)?; )?;
// Create DBs. // Create DBs.
home.policies_mut()?; home.policies_mut()?;
home.database_mut()?;
home.notifications_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()); transport::local::register(storage.clone());
@ -576,6 +585,16 @@ impl Home {
Ok(db) Ok(db)
} }
/// Returns the address store.
pub fn addresses(&self) -> Result<impl node::address::Store, node::db::Error> {
self.database_mut()
}
/// Returns the routing store.
pub fn routing_mut(&self) -> Result<impl node::routing::Store, node::db::Error> {
self.database_mut()
}
/// Return a read-only handle for the issues cache. /// Return a read-only handle for the issues cache.
pub fn issues<'a, R>( pub fn issues<'a, R>(
&self, &self,

View File

@ -73,9 +73,6 @@ pub fn init<G: Signer, S: WriteStorage>(
let (project, _) = Repository::init(&doc, &storage, signer)?; let (project, _) = Repository::init(&doc, &storage, signer)?;
let url = git::Url::from(project.id); 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) { match init_configure(repo, &project, pk, &default_branch, &url, signer) {
Ok(signed) => Ok((project.id, doc, signed)), Ok(signed) => Ok((project.id, doc, signed)),
Err(err) => { Err(err) => {

View File

@ -1,7 +1,7 @@
pub mod git; pub mod git;
pub mod refs; pub mod refs;
use std::collections::{hash_map, BTreeSet, HashSet}; use std::collections::{hash_map, HashSet};
use std::ops::Deref; use std::ops::Deref;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{fmt, io}; use std::{fmt, io};
@ -29,7 +29,6 @@ use self::git::UserInfo;
use self::refs::{RefsAt, SignedRefs}; use self::refs::{RefsAt, SignedRefs};
pub type BranchName = git::RefString; pub type BranchName = git::RefString;
pub type Inventory = BTreeSet<RepoId>;
/// Basic repository information. /// Basic repository information.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -150,8 +149,6 @@ pub enum Error {
Ext(#[from] git::ext::Error), Ext(#[from] git::ext::Error),
#[error("invalid repository identifier {0:?}")] #[error("invalid repository identifier {0:?}")]
InvalidId(std::ffi::OsString), InvalidId(std::ffi::OsString),
#[error("inventory: {0}")]
Inventory(io::Error),
#[error("i/o: {0}")] #[error("i/o: {0}")]
Io(#[from] io::Error), Io(#[from] io::Error),
} }
@ -405,9 +402,6 @@ impl<V> Deref for Remote<V> {
/// Read-only operations on a storage instance. /// Read-only operations on a storage instance.
pub trait ReadStorage { pub trait ReadStorage {
type Repository: ReadRepository; type Repository: ReadRepository;
type InventoryRef<'a>: Deref<Target = Inventory>
where
Self: 'a;
/// Get user info for this storage. /// Get user info for this storage.
fn info(&self) -> &UserInfo; fn info(&self) -> &UserInfo;
@ -417,16 +411,8 @@ pub trait ReadStorage {
fn path_of(&self, rid: &RepoId) -> PathBuf; fn path_of(&self, rid: &RepoId) -> PathBuf;
/// Check whether storage contains a repository. /// Check whether storage contains a repository.
fn contains(&self, rid: &RepoId) -> Result<bool, RepositoryError>; fn contains(&self, rid: &RepoId) -> Result<bool, RepositoryError>;
/// Get the inventory of repositories hosted under this storage.
/// This function should typically only return public repositories.
fn inventory(&self) -> Result<Inventory, Error>;
/// 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). /// Return all repositories (public and private).
fn repositories(&self) -> Result<Vec<RepositoryInfo<Verified>>, Error>; fn repositories(&self) -> Result<Vec<RepositoryInfo<Verified>>, Error>;
/// Insert this repository into the inventory.
fn insert(&self, rid: RepoId);
/// Open or create a read-only repository. /// Open or create a read-only repository.
fn repository(&self, rid: RepoId) -> Result<Self::Repository, RepositoryError>; fn repository(&self, rid: RepoId) -> Result<Self::Repository, RepositoryError>;
/// Get a repository's identity if it exists. /// Get a repository's identity if it exists.
@ -667,7 +653,6 @@ where
S: ReadStorage + 'static, S: ReadStorage + 'static,
{ {
type Repository = S::Repository; type Repository = S::Repository;
type InventoryRef<'a> = S::InventoryRef<'a> where T: 'a;
fn info(&self) -> &UserInfo { fn info(&self) -> &UserInfo {
self.deref().info() self.deref().info()
@ -681,25 +666,10 @@ where
self.deref().path_of(rid) self.deref().path_of(rid)
} }
fn insert(&self, rid: RepoId) {
self.deref().insert(rid)
}
fn contains(&self, rid: &RepoId) -> Result<bool, RepositoryError> { fn contains(&self, rid: &RepoId) -> Result<bool, RepositoryError> {
self.deref().contains(rid) self.deref().contains(rid)
} }
fn inventory(&self) -> Result<Inventory, Error> {
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<Option<Doc<Verified>>, RepositoryError> { fn get(&self, rid: RepoId) -> Result<Option<Doc<Verified>>, RepositoryError> {
self.deref().get(rid) self.deref().get(rid)
} }

View File

@ -5,7 +5,6 @@ pub mod transport;
use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, MutexGuard};
use std::{fs, io}; use std::{fs, io};
use crypto::{Signer, Verified}; use crypto::{Signer, Verified};
@ -20,8 +19,8 @@ use crate::node::SyncedAt;
use crate::storage::refs; use crate::storage::refs;
use crate::storage::refs::{Refs, SignedRefs, SignedRefsAt}; use crate::storage::refs::{Refs, SignedRefs, SignedRefsAt};
use crate::storage::{ use crate::storage::{
Inventory, ReadRepository, ReadStorage, Remote, Remotes, RepositoryError, RepositoryInfo, ReadRepository, ReadStorage, Remote, Remotes, RepositoryError, RepositoryInfo, SetHead,
SetHead, SignRepository, WriteRepository, WriteStorage, SignRepository, WriteRepository, WriteStorage,
}; };
use crate::{git, node}; use crate::{git, node};
@ -73,34 +72,14 @@ impl<'a> TryFrom<git2::Reference<'a>> for Ref {
} }
} }
/// A reference to a locked inventory.
pub struct InventoryLock<'a> {
guard: MutexGuard<'a, Option<BTreeSet<RepoId>>>,
}
impl<'a> Deref for InventoryLock<'a> {
type Target = BTreeSet<RepoId>;
#[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)] #[derive(Debug, Clone)]
pub struct Storage { pub struct Storage {
path: PathBuf, path: PathBuf,
info: UserInfo, info: UserInfo,
/// Inventory cache. Set to `None` until the cache is populated.
inventory: Arc<Mutex<Option<BTreeSet<RepoId>>>>,
} }
impl ReadStorage for Storage { impl ReadStorage for Storage {
type Repository = Repository; type Repository = Repository;
type InventoryRef<'a> = InventoryLock<'a>;
fn info(&self) -> &UserInfo { fn info(&self) -> &UserInfo {
&self.info &self.info
@ -122,44 +101,6 @@ impl ReadStorage for Storage {
Ok(false) 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<Inventory, Error> {
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<Self::Repository, RepositoryError> { fn repository(&self, rid: RepoId) -> Result<Self::Repository, RepositoryError> {
Repository::open(paths::repository(self, &rid), rid) Repository::open(paths::repository(self, &rid), rid)
} }
@ -265,11 +206,7 @@ impl Storage {
Err(err) => return Err(Error::Io(err)), Err(err) => return Err(Error::Io(err)),
Ok(()) => {} Ok(()) => {}
} }
Ok(Self { Ok(Self { path, info })
path,
info,
inventory: Default::default(),
})
} }
/// Create a [`Repository`] in a temporary directory. /// Create a [`Repository`] in a temporary directory.
@ -336,14 +273,6 @@ impl Storage {
} }
Ok(()) Ok(())
} }
fn public_repositories(&self) -> Result<impl Iterator<Item = RepoId>, 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. /// Git implementation of [`WriteRepository`] using the `git2` crate.

View File

@ -1,4 +1,4 @@
use std::collections::{BTreeSet, HashMap}; use std::collections::HashMap;
use std::convert::Infallible; use std::convert::Infallible;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -51,26 +51,20 @@ impl MockStorage {
.expect("MockStorage::repo_mut: repository does not exist") .expect("MockStorage::repo_mut: repository does not exist")
} }
pub fn map(mut self, f: impl Fn(&mut Doc<Verified>)) -> Self {
for repo in self.repos.values_mut() {
f(&mut repo.doc.doc);
}
self
}
pub fn empty() -> Self { pub fn empty() -> Self {
Self::new(Vec::new()) Self::new(Vec::new())
} }
} }
pub struct InventoryLock {
inner: Inventory,
}
impl std::ops::Deref for InventoryLock {
type Target = BTreeSet<RepoId>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl ReadStorage for MockStorage { impl ReadStorage for MockStorage {
type Repository = MockRepository; type Repository = MockRepository;
type InventoryRef<'a> = InventoryLock;
fn info(&self) -> &git::UserInfo { fn info(&self) -> &git::UserInfo {
&self.info &self.info
@ -88,18 +82,6 @@ impl ReadStorage for MockStorage {
Ok(self.repos.contains_key(rid)) Ok(self.repos.contains_key(rid))
} }
fn inventory(&self) -> Result<Inventory, Error> {
Ok(self.repos.keys().cloned().collect::<BTreeSet<_>>())
}
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::Repository, RepositoryError> { fn repository(&self, rid: RepoId) -> Result<Self::Repository, RepositoryError> {
self.repos self.repos
.get(&rid) .get(&rid)