radicle: Have migration repair downgrades
Instead of repairing downgrades, automatic migration could fail. This occurs when attempting to load signed references, and a downgrade is detected while coalescing `RepositoryInfo`. Similarly to how fetch recovers, automatic migration must also recover by detecting for the downgrade error, and allowing the migration to happen in this case.
This commit is contained in:
parent
33db6637b4
commit
86cacfb846
|
|
@ -2,7 +2,7 @@ mod args;
|
||||||
|
|
||||||
pub use args::Args;
|
pub use args::Args;
|
||||||
|
|
||||||
use radicle::storage::{ReadStorage, RepositoryInfo};
|
use radicle::storage::{ReadStorage, RepositoryInfo, SignedRefsInfo};
|
||||||
|
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
|
|
||||||
|
|
@ -34,7 +34,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
if !doc.is_public() && args.public {
|
if !doc.is_public() && args.public {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if refs.is_none() && !args.all && !args.seeded {
|
if matches!(refs, SignedRefsInfo::None) && !args.all && !args.seeded {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let seeded = policy.is_seeding(&rid)?;
|
let seeded = policy.is_seeding(&rid)?;
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ use radicle::node::seed;
|
||||||
use radicle::node::seed::Store as _;
|
use radicle::node::seed::Store as _;
|
||||||
use radicle::node::{Penalty, Severity};
|
use radicle::node::{Penalty, Severity};
|
||||||
use radicle::storage::refs::{FeatureLevel, SIGREFS_BRANCH};
|
use radicle::storage::refs::{FeatureLevel, SIGREFS_BRANCH};
|
||||||
use radicle::storage::RepositoryError;
|
use radicle::storage::{RepositoryError, RepositoryInfo, SignedRefsInfo};
|
||||||
use radicle_fetch::policy::SeedingPolicy;
|
use radicle_fetch::policy::SeedingPolicy;
|
||||||
|
|
||||||
use crate::fetcher;
|
use crate::fetcher;
|
||||||
|
|
@ -522,33 +522,19 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upgrade_sigrefs(&mut self, info: &radicle::storage::RepositoryInfo) -> Result<(), Error> {
|
fn upgrade_sigrefs(&mut self, info: &RepositoryInfo) -> Result<(), Error> {
|
||||||
let Some(ref refs) = info.refs else {
|
if !matches!(info.refs, SignedRefsInfo::NeedsMigration) {
|
||||||
// No refs, nothing to upgrade.
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
if refs.feature_level() >= FeatureLevel::LATEST && refs.parent().is_some() {
|
|
||||||
// Refs are at target level or above, nothing to upgrade.
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let rid = info.rid;
|
let rid = info.rid;
|
||||||
|
|
||||||
if refs.parent().is_none() {
|
log::info!(
|
||||||
log::info!(
|
"Migrating `rad/sigrefs` of {rid} to force feature level {}.",
|
||||||
"Migrating `rad/sigrefs` of {rid} to force feature level {}, as the history currently contains only a root commit.",
|
FeatureLevel::LATEST
|
||||||
FeatureLevel::LATEST
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
log::info!(
|
|
||||||
"Migrating `rad/sigrefs` of {rid} from level {} which is lower than target level {}.",
|
|
||||||
refs.feature_level(),
|
|
||||||
FeatureLevel::LATEST
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let repo = self.storage.repository_mut(info.rid)?;
|
let repo = self.storage.repository_mut(rid)?;
|
||||||
// NOTE: We assume to reach `FeatureLevel::LATEST` by signing refs.
|
// NOTE: We assume to reach `FeatureLevel::LATEST` by signing refs.
|
||||||
repo.force_sign_refs(&self.signer)?;
|
repo.force_sign_refs(&self.signer)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,40 @@ use crate::identity::{Identity, RepoId};
|
||||||
use crate::node::device::Device;
|
use crate::node::device::Device;
|
||||||
use crate::node::SyncedAt;
|
use crate::node::SyncedAt;
|
||||||
use crate::storage::git::NAMESPACES_GLOB;
|
use crate::storage::git::NAMESPACES_GLOB;
|
||||||
use crate::storage::refs::Refs;
|
use crate::storage::refs::{FeatureLevel, Refs, SignedRefsAt};
|
||||||
|
|
||||||
use self::refs::{RefsAt, SignedRefs};
|
use self::refs::{RefsAt, SignedRefs};
|
||||||
use crate::git::UserInfo;
|
use crate::git::UserInfo;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum SignedRefsInfo {
|
||||||
|
/// Repositories with this set to `None` are ones that are seeded but not forked.
|
||||||
|
None,
|
||||||
|
/// Local signed refs, if any.
|
||||||
|
Some(refs::SignedRefsAt),
|
||||||
|
NeedsMigration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignedRefsInfo {
|
||||||
|
pub(crate) fn new(
|
||||||
|
result: Result<Option<SignedRefsAt>, refs::sigrefs::read::error::Read>,
|
||||||
|
) -> Result<Self, refs::sigrefs::read::error::Read> {
|
||||||
|
Ok(match result {
|
||||||
|
Ok(Some(refs))
|
||||||
|
if refs.feature_level() >= FeatureLevel::LATEST && refs.parent().is_some() =>
|
||||||
|
{
|
||||||
|
SignedRefsInfo::Some(refs)
|
||||||
|
}
|
||||||
|
Ok(Some(_)) => SignedRefsInfo::NeedsMigration,
|
||||||
|
Ok(None) => SignedRefsInfo::None,
|
||||||
|
Err(refs::sigrefs::read::error::Read::Downgrade { .. }) => {
|
||||||
|
SignedRefsInfo::NeedsMigration
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Basic repository information.
|
/// Basic repository information.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct RepositoryInfo {
|
pub struct RepositoryInfo {
|
||||||
|
|
@ -40,9 +69,8 @@ pub struct RepositoryInfo {
|
||||||
pub head: Oid,
|
pub head: Oid,
|
||||||
/// Identity document.
|
/// Identity document.
|
||||||
pub doc: Doc,
|
pub doc: Doc,
|
||||||
/// Local signed refs, if any.
|
/// Information about local signed references.
|
||||||
/// Repositories with this set to `None` are ones that are seeded but not forked.
|
pub refs: SignedRefsInfo,
|
||||||
pub refs: Option<refs::SignedRefsAt>,
|
|
||||||
/// Sync time of the repository.
|
/// Sync time of the repository.
|
||||||
pub synced_at: Option<SyncedAt>,
|
pub synced_at: Option<SyncedAt>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,8 @@ use crate::identity::doc::DocError;
|
||||||
use crate::identity::{CanonicalRefs, Doc, DocAt, RepoId};
|
use crate::identity::{CanonicalRefs, Doc, DocAt, RepoId};
|
||||||
use crate::identity::{Identity, Project};
|
use crate::identity::{Identity, Project};
|
||||||
use crate::node::device::Device;
|
use crate::node::device::Device;
|
||||||
use crate::node::SyncedAt;
|
|
||||||
use crate::storage::refs;
|
|
||||||
use crate::storage::refs::{FeatureLevel, Refs, SignedRefs, SignedRefsAt};
|
use crate::storage::refs::{FeatureLevel, Refs, SignedRefs, SignedRefsAt};
|
||||||
|
use crate::storage::{refs, SignedRefsInfo};
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
ReadRepository, ReadStorage, Remote, Remotes, RepositoryInfo, SetHead, SignRepository,
|
ReadRepository, ReadStorage, Remote, Remotes, RepositoryInfo, SetHead, SignRepository,
|
||||||
WriteRepository, WriteStorage,
|
WriteRepository, WriteStorage,
|
||||||
|
|
@ -167,12 +166,13 @@ impl ReadStorage for Storage {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Nb. This will be `None` if they were not found.
|
// Nb. This will be `None` if they were not found.
|
||||||
let refs = refs::SignedRefsAt::load(self.info.key, &repo)
|
let refs = SignedRefsInfo::new(refs::SignedRefsAt::load(self.info.key, &repo))
|
||||||
.map_err(|err| Error::Refs(refs::Error::Read(err)))?;
|
.map_err(|err| Error::Refs(refs::Error::Read(err)))?;
|
||||||
let synced_at = refs
|
|
||||||
.as_ref()
|
let synced_at = match &refs {
|
||||||
.map(|r| node::SyncedAt::new(r.at, &repo))
|
SignedRefsInfo::Some(refs) => Some(node::SyncedAt::new(refs.at, &repo)?),
|
||||||
.transpose()?;
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
repos.push(RepositoryInfo {
|
repos.push(RepositoryInfo {
|
||||||
rid,
|
rid,
|
||||||
|
|
@ -258,12 +258,13 @@ impl Storage {
|
||||||
let repo = self.repository(*rid)?;
|
let repo = self.repository(*rid)?;
|
||||||
let (_, head) = repo.head()?;
|
let (_, head) = repo.head()?;
|
||||||
|
|
||||||
let refs = refs::SignedRefsAt::load(self.info.key, &repo)
|
let refs = SignedRefsInfo::new(refs::SignedRefsAt::load(self.info.key, &repo))
|
||||||
.map_err(|err| RepositoryError::from(refs::Error::Read(err)))?;
|
.map_err(|err| Error::Refs(refs::Error::Read(err)))?;
|
||||||
let synced_at = refs
|
|
||||||
.as_ref()
|
let synced_at = match &refs {
|
||||||
.map(|r| SyncedAt::new(r.at, &repo))
|
SignedRefsInfo::Some(refs) => Some(node::SyncedAt::new(refs.at, &repo)?),
|
||||||
.transpose()?;
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(RepositoryInfo {
|
Ok(RepositoryInfo {
|
||||||
rid: *rid,
|
rid: *rid,
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ impl ReadStorage for MockStorage {
|
||||||
rid: *rid,
|
rid: *rid,
|
||||||
head: r.head().unwrap().1,
|
head: r.head().unwrap().1,
|
||||||
doc: r.doc.clone().into(),
|
doc: r.doc.clone().into(),
|
||||||
refs: None,
|
refs: SignedRefsInfo::None,
|
||||||
synced_at: None,
|
synced_at: None,
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue