diff --git a/radicle-cli/src/commands/ls.rs b/radicle-cli/src/commands/ls.rs index 2161183a..d8852f93 100644 --- a/radicle-cli/src/commands/ls.rs +++ b/radicle-cli/src/commands/ls.rs @@ -102,6 +102,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { head, doc, refs, + .. } in repos { if doc.visibility.is_public() && options.private && !options.public { diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs index c6e693e0..8dbbaf0b 100644 --- a/radicle-node/src/service.rs +++ b/radicle-node/src/service.rs @@ -587,11 +587,9 @@ where assert_ne!(time, LocalTime::default()); let nid = self.node_id(); - let inventory = self.storage.inventory()?; self.started_at = Some(time); self.clock = time; - self.inventory = gossip::inventory(self.timestamp(), inventory.clone()); // Populate refs database. This is only useful as part of the upgrade process for nodes // that have been online since before the refs database was created. @@ -622,28 +620,25 @@ where ) .expect("Service::initialize: error adding local node to address database"); - // Ensure that our inventory is recorded in our routing table, and we are seeding - // all of it. It can happen that inventory is not properly seeded if for eg. the - // user creates a new repository while the node is stopped. - self.db - .routing_mut() - .insert(inventory.iter(), nid, time.into())?; - let announced = self .db .seeds() .seeded_by(&nid)? .collect::, _>>()?; - for rid in inventory { - let repo = self.storage.repository(rid)?; + for repo in self.storage.repositories()? { + let rid = repo.rid; // If we're not seeding this repo, just skip it. if !self.policies.is_seeding(&rid)? { warn!(target: "service", "Local repository {rid} is not seeded"); continue; } + // Add public repositories to inventory. + if repo.doc.visibility.is_public() { + self.storage.insert(rid); + } // If we have no owned refs for this repo, then there's nothing to announce. - let Ok(updated_at) = SyncedAt::load(&repo, nid) else { + let Some(updated_at) = repo.synced_at else { continue; }; @@ -672,6 +667,17 @@ where } } + { + let inventory = self.storage.inventory()?; + // Ensure that our inventory is recorded in our routing table, and we are seeding + // all of it. It can happen that inventory is not properly seeded if for eg. the + // user creates a new repository while the node is stopped. + self.db + .routing_mut() + .insert(inventory.iter(), nid, time.into())?; + self.inventory = gossip::inventory(self.timestamp(), inventory); + } + // Setup subscription filter for seeded repos. self.filter = Filter::new( self.policies diff --git a/radicle-node/src/tests.rs b/radicle-node/src/tests.rs index 8714c0c6..ddb62617 100644 --- a/radicle-node/src/tests.rs +++ b/radicle-node/src/tests.rs @@ -838,7 +838,6 @@ fn test_refs_announcement_no_subscribe() { #[test] fn test_refs_announcement_offline() { - logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); let mut alice = { let signer = MockSigner::default(); @@ -854,10 +853,10 @@ fn test_refs_announcement_offline() { }, ) }; - let inv = alice.inventory(); - let rid = inv.first().unwrap(); + let mut inv = alice.inventory(); + let rid = *inv.first().unwrap(); let mut bob = Peer::new("bob", [8, 8, 8, 8]); - bob.seed(rid, policy::Scope::All).unwrap(); + bob.seed(&rid, policy::Scope::All).unwrap(); // Make sure alice's service wasn't initialized before. assert_eq!(*alice.clock(), LocalTime::default()); @@ -868,25 +867,23 @@ fn test_refs_announcement_offline() { // Alice announces the refs of all projects since she hasn't announced refs for these projects // yet. - let mut messages = alice.messages(bob.id()); - for i in &inv { - let msg = messages.next(); + for msg in alice.messages(bob.id()) { assert_matches!( msg, - Some(Message::Announcement(Announcement { + Message::Announcement(Announcement { node, message: AnnouncementMessage::Refs(RefsAnnouncement { rid, .. }), .. - })) - if node == alice.id && rid == *i + }) + if node == alice.id && inv.remove(&rid) ); } // Create an issue without telling the node. - let repo = alice.storage().repository(*rid).unwrap(); + let repo = alice.storage().repository(rid).unwrap(); let old_refs = RefsAt::new(&repo, alice.id).unwrap(); let mut issues = radicle::issue::Cache::no_cache(&repo).unwrap(); issues @@ -935,7 +932,7 @@ fn test_refs_announcement_offline() { .collect::>(); assert_eq!(anns.len(), 1); - assert_eq!(anns.first().unwrap().rid, *rid); + assert_eq!(anns.first().unwrap().rid, rid); assert_eq!(anns.first().unwrap().refs.first().unwrap().at, new_refs.at); } diff --git a/radicle/src/storage.rs b/radicle/src/storage.rs index 3f9673c3..9b97a3ce 100644 --- a/radicle/src/storage.rs +++ b/radicle/src/storage.rs @@ -21,6 +21,7 @@ use crate::git::{refspec::Refspec, PatternString, Qualified, RefError, RefStr, R use crate::identity::{Did, PayloadError}; use crate::identity::{Doc, DocAt, DocError}; use crate::identity::{Identity, RepoId}; +use crate::node::SyncedAt; use crate::storage::git::NAMESPACES_GLOB; use crate::storage::refs::Refs; @@ -42,6 +43,8 @@ pub struct RepositoryInfo { /// Local signed refs, if any. /// Repositories with this set to `None` are ones that are seeded but not forked. pub refs: Option, + /// Sync time of the repository. + pub synced_at: Option, } /// Describes one or more namespaces. diff --git a/radicle/src/storage/git.rs b/radicle/src/storage/git.rs index 4ec7e8e1..77b61983 100644 --- a/radicle/src/storage/git.rs +++ b/radicle/src/storage/git.rs @@ -13,16 +13,17 @@ use once_cell::sync::Lazy; use tempfile::TempDir; use crate::crypto::Unverified; -use crate::git; use crate::identity::doc::DocError; use crate::identity::{doc::DocAt, Doc, RepoId}; use crate::identity::{Identity, Project}; +use crate::node::SyncedAt; use crate::storage::refs; use crate::storage::refs::{Refs, SignedRefs, SignedRefsAt}; use crate::storage::{ Inventory, ReadRepository, ReadStorage, Remote, Remotes, RepositoryError, RepositoryInfo, SetHead, SignRepository, WriteRepository, WriteStorage, }; +use crate::{git, node}; pub use crate::git::{ ext, raw, refname, refspec, Oid, PatternStr, Qualified, RefError, RefString, UserInfo, @@ -152,10 +153,10 @@ impl ReadStorage for Storage { .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); - // If the cache hasn't been populated yet, we don't do anything, since this repo - // will be loaded when the cache is populated. if let Some(ref mut repos) = *repos { repos.insert(rid); + } else { + *repos = Some(BTreeSet::from_iter([rid])); } } @@ -211,12 +212,17 @@ impl ReadStorage for Storage { }; // Nb. This will be `None` if they were not found. let refs = refs::SignedRefsAt::load(self.info.key, &repo)?; + let synced_at = refs + .as_ref() + .map(|r| node::SyncedAt::new(r.at, &repo)) + .transpose()?; repos.push(RepositoryInfo { rid, head, doc, refs, + synced_at, }); } Ok(repos) @@ -298,11 +304,17 @@ impl Storage { rids.try_fold(Vec::new(), |mut infos, rid| { let repo = self.repository(*rid)?; let (_, head) = repo.head()?; + let refs = refs::SignedRefsAt::load(self.info.key, &repo)?; + let synced_at = refs + .as_ref() + .map(|r| SyncedAt::new(r.at, &repo)) + .transpose()?; let info = RepositoryInfo { rid: *rid, head, doc: repo.identity_doc()?.into(), - refs: refs::SignedRefsAt::load(self.info.key, &repo)?, + refs, + synced_at, }; infos.push(info); Ok(infos) diff --git a/radicle/src/storage/refs.rs b/radicle/src/storage/refs.rs index 1a999625..15b8bdb7 100644 --- a/radicle/src/storage/refs.rs +++ b/radicle/src/storage/refs.rs @@ -411,8 +411,8 @@ impl SignedRefsAt { where S: ReadRepository, { - let at = match repo.reference_oid(&remote, &SIGREFS_BRANCH) { - Ok(at) => at, + let at = match RefsAt::new(repo, remote) { + Ok(RefsAt { at, .. }) => at, Err(e) if git::is_not_found_err(&e) => return Ok(None), Err(e) => return Err(e.into()), }; diff --git a/radicle/src/test/fixtures.rs b/radicle/src/test/fixtures.rs index 3b1beef5..a8633034 100644 --- a/radicle/src/test/fixtures.rs +++ b/radicle/src/test/fixtures.rs @@ -25,7 +25,13 @@ pub fn user() -> git::UserInfo { /// Create a new storage with a project. pub fn storage, G: Signer>(path: P, signer: &G) -> Result { let path = path.as_ref(); - let storage = Storage::open(path.join("storage"), user())?; + let storage = Storage::open( + path.join("storage"), + git::UserInfo { + alias: Alias::new("Radcliff"), + key: *signer.public_key(), + }, + )?; transport::local::register(storage.clone()); transport::remote::mock::register(signer.public_key(), storage.path()); diff --git a/radicle/src/test/storage.rs b/radicle/src/test/storage.rs index c514da41..0a8f632b 100644 --- a/radicle/src/test/storage.rs +++ b/radicle/src/test/storage.rs @@ -118,6 +118,7 @@ impl ReadStorage for MockStorage { head: r.head().unwrap().1, doc: r.doc.clone().into(), refs: None, + synced_at: None, }) .collect()) }