Introduce a node event for canonical reference updates
Whenever the node fetches new updates, it checks if canonical references can be updated. The node has learned how to return these results and emit them as node events. This is a breaking change since it adds a new variant the `Event` type, which is not forwards-compatible.
This commit is contained in:
parent
690f6b02c0
commit
a8255a2e07
|
|
@ -663,6 +663,7 @@ where
|
||||||
remote,
|
remote,
|
||||||
Rc::new(Ok(fetch::FetchResult {
|
Rc::new(Ok(fetch::FetchResult {
|
||||||
updated: vec![],
|
updated: vec![],
|
||||||
|
canonical: fetch::UpdatedCanonicalRefs::default(),
|
||||||
namespaces: HashSet::new(),
|
namespaces: HashSet::new(),
|
||||||
clone: true,
|
clone: true,
|
||||||
doc: arbitrary::gen(1),
|
doc: arbitrary::gen(1),
|
||||||
|
|
|
||||||
|
|
@ -1589,6 +1589,7 @@ fn test_queued_fetch_from_ann_same_rid() {
|
||||||
name: refname.clone(),
|
name: refname.clone(),
|
||||||
oid,
|
oid,
|
||||||
}],
|
}],
|
||||||
|
canonical: fetch::UpdatedCanonicalRefs::default(),
|
||||||
namespaces: [carol.id()].into_iter().collect(),
|
namespaces: [carol.id()].into_iter().collect(),
|
||||||
clone: false,
|
clone: false,
|
||||||
doc: arbitrary::gen(1),
|
doc: arbitrary::gen(1),
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ use radicle::storage::{
|
||||||
use radicle::{cob, git, node, Storage};
|
use radicle::{cob, git, node, Storage};
|
||||||
use radicle_fetch::git::refs::Applied;
|
use radicle_fetch::git::refs::Applied;
|
||||||
use radicle_fetch::{Allowed, BlockList, FetchLimit};
|
use radicle_fetch::{Allowed, BlockList, FetchLimit};
|
||||||
pub use radicle_protocol::worker::fetch::FetchResult;
|
pub use radicle_protocol::worker::fetch::{FetchResult, UpdatedCanonicalRefs};
|
||||||
|
|
||||||
use super::channels::ChannelsFlush;
|
use super::channels::ChannelsFlush;
|
||||||
|
|
||||||
|
|
@ -128,9 +128,13 @@ impl Handle {
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = set_canonical_refs(&repo, &applied) {
|
let canonical = match set_canonical_refs(&repo, &applied) {
|
||||||
log::warn!(target: "worker", "Failed to set canonical references: {e}");
|
Ok(updates) => updates.unwrap_or_default(),
|
||||||
}
|
Err(e) => {
|
||||||
|
log::warn!(target: "worker", "Failed to set canonical references: {e}");
|
||||||
|
UpdatedCanonicalRefs::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Notifications are only posted for pulls, not clones.
|
// Notifications are only posted for pulls, not clones.
|
||||||
if let Some(mut store) = notifs {
|
if let Some(mut store) = notifs {
|
||||||
|
|
@ -147,6 +151,7 @@ impl Handle {
|
||||||
|
|
||||||
Ok(FetchResult {
|
Ok(FetchResult {
|
||||||
updated: applied.updated,
|
updated: applied.updated,
|
||||||
|
canonical,
|
||||||
namespaces: remotes.into_iter().collect(),
|
namespaces: remotes.into_iter().collect(),
|
||||||
doc: repo.identity_doc()?,
|
doc: repo.identity_doc()?,
|
||||||
clone,
|
clone,
|
||||||
|
|
@ -356,17 +361,21 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_canonical_refs(repo: &Repository, applied: &Applied) -> Result<(), error::Canonical> {
|
fn set_canonical_refs(
|
||||||
|
repo: &Repository,
|
||||||
|
applied: &Applied,
|
||||||
|
) -> Result<Option<UpdatedCanonicalRefs>, error::Canonical> {
|
||||||
let identity = repo.identity()?;
|
let identity = repo.identity()?;
|
||||||
let rules = match identity
|
let rules = match identity
|
||||||
.canonical_refs()?
|
.canonical_refs()?
|
||||||
.map(|crefs| crefs.rules().clone())
|
.map(|crefs| crefs.rules().clone())
|
||||||
.filter(|rules| !rules.is_empty())
|
.filter(|rules| !rules.is_empty())
|
||||||
{
|
{
|
||||||
None => return Ok(()),
|
None => return Ok(None),
|
||||||
Some(rules) => rules,
|
Some(rules) => rules,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut updated_refs = UpdatedCanonicalRefs::default();
|
||||||
for update in applied.updated.iter() {
|
for update in applied.updated.iter() {
|
||||||
let name = match update {
|
let name = match update {
|
||||||
RefUpdate::Updated { name, .. } | RefUpdate::Created { name, .. } => name,
|
RefUpdate::Updated { name, .. } | RefUpdate::Created { name, .. } => name,
|
||||||
|
|
@ -421,9 +430,11 @@ fn set_canonical_refs(repo: &Repository, applied: &Applied) -> Result<(), error:
|
||||||
target: "worker",
|
target: "worker",
|
||||||
"Failed to set canonical reference {refname}->{oid}: {e}"
|
"Failed to set canonical reference {refname}->{oid}: {e}"
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
updated_refs.updated(refname, oid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(Some(updated_refs))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1177,6 +1177,7 @@ where
|
||||||
match result {
|
match result {
|
||||||
Ok(crate::worker::fetch::FetchResult {
|
Ok(crate::worker::fetch::FetchResult {
|
||||||
updated,
|
updated,
|
||||||
|
canonical,
|
||||||
namespaces,
|
namespaces,
|
||||||
clone,
|
clone,
|
||||||
doc,
|
doc,
|
||||||
|
|
@ -1198,6 +1199,16 @@ where
|
||||||
rid,
|
rid,
|
||||||
updated: updated.clone(),
|
updated: updated.clone(),
|
||||||
});
|
});
|
||||||
|
self.emitter.emit_all(
|
||||||
|
canonical
|
||||||
|
.into_iter()
|
||||||
|
.map(|(refname, target)| Event::CanonicalRefUpdated {
|
||||||
|
rid,
|
||||||
|
refname,
|
||||||
|
target,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
// Announce our new inventory if this fetch was a full clone.
|
// Announce our new inventory if this fetch was a full clone.
|
||||||
// Only update and announce inventory for public repositories.
|
// Only update and announce inventory for public repositories.
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::{BTreeMap, HashSet};
|
||||||
|
|
||||||
use radicle::crypto::PublicKey;
|
use radicle::crypto::PublicKey;
|
||||||
|
use radicle::git;
|
||||||
use radicle::{identity::DocAt, storage::RefUpdate};
|
use radicle::{identity::DocAt, storage::RefUpdate};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FetchResult {
|
pub struct FetchResult {
|
||||||
/// The set of updated references.
|
/// The set of updated references.
|
||||||
pub updated: Vec<RefUpdate>,
|
pub updated: Vec<RefUpdate>,
|
||||||
|
/// The canonical references that were updated as part of the fetch process.
|
||||||
|
pub canonical: UpdatedCanonicalRefs,
|
||||||
/// The set of remote namespaces that were updated.
|
/// The set of remote namespaces that were updated.
|
||||||
pub namespaces: HashSet<PublicKey>,
|
pub namespaces: HashSet<PublicKey>,
|
||||||
/// The fetch was a full clone.
|
/// The fetch was a full clone.
|
||||||
|
|
@ -21,6 +24,7 @@ impl FetchResult {
|
||||||
pub fn new(doc: DocAt) -> Self {
|
pub fn new(doc: DocAt) -> Self {
|
||||||
Self {
|
Self {
|
||||||
updated: vec![],
|
updated: vec![],
|
||||||
|
canonical: UpdatedCanonicalRefs::default(),
|
||||||
namespaces: HashSet::new(),
|
namespaces: HashSet::new(),
|
||||||
clone: false,
|
clone: false,
|
||||||
doc,
|
doc,
|
||||||
|
|
@ -28,11 +32,41 @@ impl FetchResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The set of canonical references, updated after a fetch, and their
|
||||||
|
/// corresponding targets.
|
||||||
|
#[derive(Clone, Default, Debug)]
|
||||||
|
pub struct UpdatedCanonicalRefs {
|
||||||
|
inner: BTreeMap<git::Qualified<'static>, git::Oid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for UpdatedCanonicalRefs {
|
||||||
|
type Item = (git::Qualified<'static>, git::Oid);
|
||||||
|
type IntoIter = std::collections::btree_map::IntoIter<git::Qualified<'static>, git::Oid>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.inner.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdatedCanonicalRefs {
|
||||||
|
/// Insert a new updated entry for the canonical reference identified by
|
||||||
|
/// `refname` and its new `target.`
|
||||||
|
pub fn updated(&mut self, refname: git::Qualified<'static>, target: git::Oid) {
|
||||||
|
self.inner.insert(refname, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an iterator of all the updates.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (&git::Qualified<'static>, &git::Oid)> {
|
||||||
|
self.inner.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test"))]
|
#[cfg(any(test, feature = "test"))]
|
||||||
impl qcheck::Arbitrary for FetchResult {
|
impl qcheck::Arbitrary for FetchResult {
|
||||||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
||||||
FetchResult {
|
FetchResult {
|
||||||
updated: vec![],
|
updated: vec![],
|
||||||
|
canonical: UpdatedCanonicalRefs::default(),
|
||||||
namespaces: HashSet::arbitrary(g),
|
namespaces: HashSet::arbitrary(g),
|
||||||
clone: bool::arbitrary(g),
|
clone: bool::arbitrary(g),
|
||||||
doc: DocAt::arbitrary(g),
|
doc: DocAt::arbitrary(g),
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Introduce a node event for canonical reference updates, `Event::CanonicalRefUpdated`.
|
||||||
|
Whenever the node fetches new updates, it checks if canonical references can
|
||||||
|
be updated. The node has learned how to return these results and emit them as
|
||||||
|
node events. This is a breaking change since it adds a new variant the `Event`
|
||||||
|
type.
|
||||||
|
- Add `#[non_exhaustive]` to `Event` to prevent any further breaking changes
|
||||||
|
when adding new variants.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- `radicle::profile::Home::socket` defaults to the path `\\.\pipe\radicle-node`
|
- `radicle::profile::Home::socket` defaults to the path `\\.\pipe\radicle-node`
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use std::time;
|
||||||
|
|
||||||
use crossbeam_channel as chan;
|
use crossbeam_channel as chan;
|
||||||
|
|
||||||
use crate::git::Oid;
|
use crate::git::{Oid, Qualified};
|
||||||
use crate::node;
|
use crate::node;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::storage::{refs, RefUpdate};
|
use crate::storage::{refs, RefUpdate};
|
||||||
|
|
@ -21,6 +21,7 @@ pub const MAX_PENDING_EVENTS: usize = 8192;
|
||||||
///
|
///
|
||||||
/// The node emits events of this type to its control socket for other
|
/// The node emits events of this type to its control socket for other
|
||||||
/// programs to consume.
|
/// programs to consume.
|
||||||
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase", tag = "type")]
|
#[serde(rename_all = "camelCase", tag = "type")]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
|
@ -122,6 +123,15 @@ pub enum Event {
|
||||||
},
|
},
|
||||||
/// The node has uploaded a Git pack file to another node.
|
/// The node has uploaded a Git pack file to another node.
|
||||||
UploadPack(upload_pack::UploadPack),
|
UploadPack(upload_pack::UploadPack),
|
||||||
|
/// A canonical reference was updated after a fetch.
|
||||||
|
CanonicalRefUpdated {
|
||||||
|
/// The repository the canonical reference was updated for.
|
||||||
|
rid: RepoId,
|
||||||
|
/// The reference name of the canonical reference update.
|
||||||
|
refname: Qualified<'static>,
|
||||||
|
/// The new target of the reference, after the update.
|
||||||
|
target: Oid,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<upload_pack::UploadPack> for Event {
|
impl From<upload_pack::UploadPack> for Event {
|
||||||
|
|
@ -214,6 +224,20 @@ impl<T: Clone> Emitter<T> {
|
||||||
.retain(|s| s.try_send(event.clone()).is_ok());
|
.retain(|s| s.try_send(event.clone()).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emit a batch of events to subscribers and drop those who can't receive
|
||||||
|
/// them.
|
||||||
|
/// N.b. subscribers are also dropped if their channel is full.
|
||||||
|
pub fn emit_all(&self, events: Vec<T>) {
|
||||||
|
// SAFETY: We deliberately propagate panics from other threads holding the lock.
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
self.subscribers.lock().unwrap().retain(|s| {
|
||||||
|
events
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.all(|event| s.try_send(event).is_ok())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Subscribe to events stream.
|
/// Subscribe to events stream.
|
||||||
pub fn subscribe(&self) -> chan::Receiver<T> {
|
pub fn subscribe(&self) -> chan::Receiver<T> {
|
||||||
let (sender, receiver) = chan::bounded(MAX_PENDING_EVENTS);
|
let (sender, receiver) = chan::bounded(MAX_PENDING_EVENTS);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue