From 6c8ee4330ea04286567a20880f2c5b76d5af9da4 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 14 Nov 2024 17:21:33 +0100 Subject: [PATCH] radicle: Implement migration callback mechanism Add the ability to have native migrations and progress callback functions in migration code for the COB cache. --- radicle-cli/src/commands/clone.rs | 5 +- radicle-cli/src/commands/cob.rs | 5 +- radicle-cli/src/commands/inbox.rs | 9 +- radicle-cli/src/commands/issue.rs | 5 +- radicle-cli/src/commands/issue/cache.rs | 6 +- radicle-cli/src/commands/patch.rs | 4 +- radicle-cli/src/commands/patch/archive.rs | 3 +- radicle-cli/src/commands/patch/assign.rs | 3 +- radicle-cli/src/commands/patch/cache.rs | 5 +- radicle-cli/src/commands/patch/checkout.rs | 3 +- radicle-cli/src/commands/patch/comment.rs | 4 +- radicle-cli/src/commands/patch/delete.rs | 3 +- radicle-cli/src/commands/patch/diff.rs | 4 +- radicle-cli/src/commands/patch/edit.rs | 5 +- radicle-cli/src/commands/patch/label.rs | 3 +- radicle-cli/src/commands/patch/list.rs | 4 +- radicle-cli/src/commands/patch/ready.rs | 3 +- radicle-cli/src/commands/patch/redact.rs | 4 +- radicle-cli/src/commands/patch/resolve.rs | 5 +- radicle-cli/src/commands/patch/review.rs | 3 +- radicle-cli/src/commands/patch/show.rs | 4 +- radicle-cli/src/commands/patch/update.rs | 4 +- radicle-cli/src/commands/stats.rs | 5 +- radicle-remote-helper/src/list.rs | 3 +- radicle-remote-helper/src/push.rs | 8 +- radicle-tools/src/rad-merge.rs | 3 +- radicle/src/cob.rs | 1 + radicle/src/cob/cache.rs | 118 +++++++++++++++++---- radicle/src/cob/issue/cache.rs | 7 +- radicle/src/cob/patch/cache.rs | 7 +- radicle/src/profile.rs | 20 +++- 31 files changed, 183 insertions(+), 83 deletions(-) diff --git a/radicle-cli/src/commands/clone.rs b/radicle-cli/src/commands/clone.rs index af598fe5..e9fa8eee 100644 --- a/radicle-cli/src/commands/clone.rs +++ b/radicle-cli/src/commands/clone.rs @@ -9,6 +9,7 @@ use radicle::issue::cache::Issues as _; use radicle::patch::cache::Patches as _; use thiserror::Error; +use radicle::cob::migrate; use radicle::git::raw; use radicle::identity::doc; use radicle::identity::doc::{DocError, RepoId}; @@ -182,8 +183,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { info.push([term::format::bold(proj.name()).into()]); info.push([term::format::italic(proj.description()).into()]); - let issues = profile.issues(&repo)?.counts()?; - let patches = profile.patches(&repo)?.counts()?; + let issues = profile.issues(&repo, migrate::ignore)?.counts()?; + let patches = profile.patches(&repo, migrate::ignore)?.counts()?; info.push([term::Line::spaced([ term::format::tertiary(issues.open).into(), diff --git a/radicle-cli/src/commands/cob.rs b/radicle-cli/src/commands/cob.rs index 07b17266..02c4bc74 100644 --- a/radicle-cli/src/commands/cob.rs +++ b/radicle-cli/src/commands/cob.rs @@ -5,6 +5,7 @@ use anyhow::anyhow; use chrono::prelude::*; use nonempty::NonEmpty; use radicle::cob; +use radicle::cob::migrate; use radicle::cob::Op; use radicle::identity::Identity; use radicle::issue::cache::Issues; @@ -180,13 +181,13 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { let oid = &oid.resolve(&repo.backend)?; if options.type_name == cob::patch::TYPENAME.clone() { - let patches = profile.patches(&repo)?; + let patches = profile.patches(&repo, migrate::ignore)?; let Some(patch) = patches.get(oid)? else { anyhow::bail!(cob::store::Error::NotFound(options.type_name, *oid)) }; serde_json::to_writer_pretty(std::io::stdout(), &patch)? } else if options.type_name == cob::issue::TYPENAME.clone() { - let issues = profile.issues(&repo)?; + let issues = profile.issues(&repo, migrate::ignore)?; let Some(issue) = issues.get(oid)? else { anyhow::bail!(cob::store::Error::NotFound(options.type_name, *oid)) }; diff --git a/radicle-cli/src/commands/inbox.rs b/radicle-cli/src/commands/inbox.rs index eaef2ade..9a7e0f2a 100644 --- a/radicle-cli/src/commands/inbox.rs +++ b/radicle-cli/src/commands/inbox.rs @@ -6,6 +6,7 @@ use anyhow::anyhow; use git_ref_format::Qualified; use localtime::LocalTime; +use radicle::cob::migrate; use radicle::cob::TypedId; use radicle::identity::Identity; use radicle::issue::cache::Issues as _; @@ -271,8 +272,8 @@ where let (_, head) = repo.head()?; let doc = repo.identity_doc()?; let proj = doc.project()?; - let issues = profile.issues(&repo)?; - let patches = profile.patches(&repo)?; + let issues = profile.issues(&repo, migrate::ignore)?; + let patches = profile.patches(&repo, migrate::ignore)?; let mut notifs = notifs.by_repo(&rid, sort_by.field)?.collect::>(); if !sort_by.reverse { @@ -535,7 +536,7 @@ fn show( match n.kind { NotificationKind::Cob { typed_id } if typed_id.is_issue() => { - let issues = profile.issues(&repo)?; + let issues = profile.issues(&repo, migrate::ignore)?; let issue = issues.get(&typed_id.id)?.unwrap(); term::issue::show( @@ -546,7 +547,7 @@ fn show( )?; } NotificationKind::Cob { typed_id } if typed_id.is_patch() => { - let patches = profile.patches(&repo)?; + let patches = profile.patches(&repo, migrate::ignore)?; let patch = patches.get(&typed_id.id)?.unwrap(); term::patch::show(&patch, &typed_id.id, false, &repo, None, profile)?; diff --git a/radicle-cli/src/commands/issue.rs b/radicle-cli/src/commands/issue.rs index 1ccaefe0..8e6b3a4d 100644 --- a/radicle-cli/src/commands/issue.rs +++ b/radicle-cli/src/commands/issue.rs @@ -8,9 +8,8 @@ use std::str::FromStr; use anyhow::{anyhow, Context as _}; use radicle::cob::common::{Label, Reaction}; -use radicle::cob::issue; use radicle::cob::issue::{CloseReason, State}; -use radicle::cob::thread; +use radicle::cob::{issue, migrate, thread}; use radicle::crypto::Signer; use radicle::issue::cache::Issues as _; use radicle::prelude::{Did, RepoId}; @@ -461,7 +460,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { | Operation::Comment { .. } ); - let mut issues = profile.issues_mut(&repo)?; + let mut issues = profile.issues_mut(&repo, migrate::ignore)?; match options.op { Operation::Edit { diff --git a/radicle-cli/src/commands/issue/cache.rs b/radicle-cli/src/commands/issue/cache.rs index 853210fd..54bcb6d0 100644 --- a/radicle-cli/src/commands/issue/cache.rs +++ b/radicle-cli/src/commands/issue/cache.rs @@ -1,5 +1,6 @@ use std::ops::ControlFlow; +use radicle::cob::migrate; use radicle::issue::IssueId; use radicle::storage::git::Repository; use radicle::storage::ReadStorage as _; @@ -37,8 +38,9 @@ pub fn run(mode: CacheMode, profile: &Profile) -> anyhow::Result<()> { } fn cache(id: Option, repository: &Repository, profile: &Profile) -> anyhow::Result<()> { - let mut issues = profile.issues_mut(repository)?; + let mut issues = profile.issues_mut(repository, migrate::ignore)?; + // FIXME: Should this change? match id { Some(id) => { issues.write(&id)?; @@ -48,7 +50,7 @@ fn cache(id: Option, repository: &Repository, profile: &Profile) -> any match result { Ok((id, _)) => term::success!( "Successfully cached issue {id} ({}/{})", - progress.seen(), + progress.current(), progress.total() ), Err(e) => term::warning(format!("Failed to retrieve issue: {e}")), diff --git a/radicle-cli/src/commands/patch.rs b/radicle-cli/src/commands/patch.rs index 3073ef14..35437741 100644 --- a/radicle-cli/src/commands/patch.rs +++ b/radicle-cli/src/commands/patch.rs @@ -37,7 +37,7 @@ use std::ffi::OsString; use anyhow::anyhow; use radicle::cob::patch::PatchId; -use radicle::cob::{patch, Label}; +use radicle::cob::{migrate, patch, Label}; use radicle::git::RefString; use radicle::patch::cache::Patches as _; use radicle::storage::git::transport; @@ -928,7 +928,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { label::run(&patch_id, add, delete, &profile, &repository)?; } Operation::Set { patch_id, remote } => { - let patches = profile.patches(&repository)?; + let patches = profile.patches(&repository, migrate::ignore)?; let patch_id = patch_id.resolve(&repository.backend)?; let patch = patches .get(&patch_id)? diff --git a/radicle-cli/src/commands/patch/archive.rs b/radicle-cli/src/commands/patch/archive.rs index 170038e8..7283bfec 100644 --- a/radicle-cli/src/commands/patch/archive.rs +++ b/radicle-cli/src/commands/patch/archive.rs @@ -1,5 +1,6 @@ use super::*; +use radicle::cob::migrate; use radicle::prelude::*; use radicle::storage::git::Repository; @@ -10,7 +11,7 @@ pub fn run( repository: &Repository, ) -> anyhow::Result<()> { let signer = term::signer(profile)?; - let mut patches = profile.patches_mut(repository)?; + let mut patches = profile.patches_mut(repository, migrate::ignore)?; let Ok(mut patch) = patches.get_mut(patch_id) else { anyhow::bail!("Patch `{patch_id}` not found"); }; diff --git a/radicle-cli/src/commands/patch/assign.rs b/radicle-cli/src/commands/patch/assign.rs index c96db74c..15776ea0 100644 --- a/radicle-cli/src/commands/patch/assign.rs +++ b/radicle-cli/src/commands/patch/assign.rs @@ -2,6 +2,7 @@ use std::collections::BTreeSet; use super::*; +use radicle::cob::migrate; use radicle::prelude::Did; use radicle::storage::git::Repository; @@ -15,7 +16,7 @@ pub fn run( repository: &Repository, ) -> anyhow::Result<()> { let signer = term::signer(profile)?; - let mut patches = profile.patches_mut(repository)?; + let mut patches = profile.patches_mut(repository, migrate::ignore)?; let Ok(mut patch) = patches.get_mut(patch_id) else { anyhow::bail!("Patch `{patch_id}` not found"); }; diff --git a/radicle-cli/src/commands/patch/cache.rs b/radicle-cli/src/commands/patch/cache.rs index 9a1ad480..c4cf90be 100644 --- a/radicle-cli/src/commands/patch/cache.rs +++ b/radicle-cli/src/commands/patch/cache.rs @@ -1,5 +1,6 @@ use std::ops::ControlFlow; +use radicle::cob::migrate; use radicle::patch::PatchId; use radicle::storage::git::Repository; use radicle::storage::ReadStorage as _; @@ -37,7 +38,7 @@ pub fn run(mode: CacheMode, profile: &Profile) -> anyhow::Result<()> { } fn cache(id: Option, repository: &Repository, profile: &Profile) -> anyhow::Result<()> { - let mut patches = profile.patches_mut(repository)?; + let mut patches = profile.patches_mut(repository, migrate::ignore)?; match id { Some(id) => { @@ -48,7 +49,7 @@ fn cache(id: Option, repository: &Repository, profile: &Profile) -> any match result { Ok((id, _)) => term::success!( "Successfully cached patch {id} ({}/{})", - progress.seen(), + progress.current(), progress.total() ), Err(e) => term::warning(format!("Failed to retrieve patch: {e}")), diff --git a/radicle-cli/src/commands/patch/checkout.rs b/radicle-cli/src/commands/patch/checkout.rs index f5f77671..11224574 100644 --- a/radicle-cli/src/commands/patch/checkout.rs +++ b/radicle-cli/src/commands/patch/checkout.rs @@ -1,6 +1,7 @@ use anyhow::anyhow; use git_ref_format::Qualified; +use radicle::cob::migrate; use radicle::cob::patch; use radicle::cob::patch::RevisionId; use radicle::git::RefString; @@ -38,7 +39,7 @@ pub fn run( profile: &Profile, opts: Options, ) -> anyhow::Result<()> { - let patches = profile.patches(stored)?; + let patches = profile.patches(stored, migrate::ignore)?; let patch = patches .get(patch_id)? .ok_or_else(|| anyhow!("Patch `{patch_id}` not found"))?; diff --git a/radicle-cli/src/commands/patch/comment.rs b/radicle-cli/src/commands/patch/comment.rs index cf377369..0837df69 100644 --- a/radicle-cli/src/commands/patch/comment.rs +++ b/radicle-cli/src/commands/patch/comment.rs @@ -1,8 +1,8 @@ use super::*; use radicle::cob; -use radicle::cob::patch; use radicle::cob::thread::CommentId; +use radicle::cob::{migrate, patch}; use radicle::patch::ByRevision; use radicle::prelude::*; use radicle::storage::git::Repository; @@ -20,7 +20,7 @@ pub fn run( profile: &Profile, ) -> anyhow::Result<()> { let signer = term::signer(profile)?; - let mut patches = profile.patches_mut(repo)?; + let mut patches = profile.patches_mut(repo, migrate::ignore)?; let revision_id = revision_id.resolve::(&repo.backend)?; let ByRevision { diff --git a/radicle-cli/src/commands/patch/delete.rs b/radicle-cli/src/commands/patch/delete.rs index 4d2c0e63..ecce8006 100644 --- a/radicle-cli/src/commands/patch/delete.rs +++ b/radicle-cli/src/commands/patch/delete.rs @@ -1,3 +1,4 @@ +use radicle::cob::migrate; use radicle::prelude::*; use radicle::storage::git::Repository; @@ -5,7 +6,7 @@ use super::*; pub fn run(patch_id: &PatchId, profile: &Profile, repository: &Repository) -> anyhow::Result<()> { let signer = &term::signer(profile)?; - let mut patches = profile.patches_mut(repository)?; + let mut patches = profile.patches_mut(repository, migrate::ignore)?; patches.remove(patch_id, signer)?; Ok(()) diff --git a/radicle-cli/src/commands/patch/diff.rs b/radicle-cli/src/commands/patch/diff.rs index 560caef2..dc730c6e 100644 --- a/radicle-cli/src/commands/patch/diff.rs +++ b/radicle-cli/src/commands/patch/diff.rs @@ -1,6 +1,6 @@ use std::process; -use radicle::cob::patch; +use radicle::cob::{migrate, patch}; use radicle::storage::git::Repository; use super::*; @@ -11,7 +11,7 @@ pub fn run( stored: &Repository, profile: &Profile, ) -> anyhow::Result<()> { - let patches = profile.patches(stored)?; + let patches = profile.patches(stored, migrate::ignore)?; let Some(patch) = patches.get(patch_id)? else { anyhow::bail!("Patch `{patch_id}` not found"); }; diff --git a/radicle-cli/src/commands/patch/edit.rs b/radicle-cli/src/commands/patch/edit.rs index a3ac8631..db041442 100644 --- a/radicle-cli/src/commands/patch/edit.rs +++ b/radicle-cli/src/commands/patch/edit.rs @@ -1,6 +1,7 @@ use super::*; -use radicle::cob::{self, patch}; +use radicle::cob; +use radicle::cob::{migrate, patch}; use radicle::crypto; use radicle::prelude::*; use radicle::storage::git::Repository; @@ -15,7 +16,7 @@ pub fn run( repository: &Repository, ) -> anyhow::Result<()> { let signer = term::signer(profile)?; - let mut patches = profile.patches_mut(repository)?; + let mut patches = profile.patches_mut(repository, migrate::ignore)?; let Ok(patch) = patches.get_mut(patch_id) else { anyhow::bail!("Patch `{patch_id}` not found"); }; diff --git a/radicle-cli/src/commands/patch/label.rs b/radicle-cli/src/commands/patch/label.rs index 009c8f45..f98a11da 100644 --- a/radicle-cli/src/commands/patch/label.rs +++ b/radicle-cli/src/commands/patch/label.rs @@ -1,5 +1,6 @@ use super::*; +use radicle::cob::migrate; use radicle::storage::git::Repository; use crate::terminal as term; @@ -12,7 +13,7 @@ pub fn run( repository: &Repository, ) -> anyhow::Result<()> { let signer = term::signer(profile)?; - let mut patches = profile.patches_mut(repository)?; + let mut patches = profile.patches_mut(repository, migrate::ignore)?; let Ok(mut patch) = patches.get_mut(patch_id) else { anyhow::bail!("Patch `{patch_id}` not found"); }; diff --git a/radicle-cli/src/commands/patch/list.rs b/radicle-cli/src/commands/patch/list.rs index 3edf5c91..249c51e1 100644 --- a/radicle-cli/src/commands/patch/list.rs +++ b/radicle-cli/src/commands/patch/list.rs @@ -1,7 +1,7 @@ use std::collections::{BTreeMap, BTreeSet}; -use radicle::cob::patch; use radicle::cob::patch::{Patch, PatchId}; +use radicle::cob::{migrate, patch}; use radicle::patch::cache::Patches as _; use radicle::prelude::*; use radicle::profile::Profile; @@ -21,7 +21,7 @@ pub fn run( repository: &Repository, profile: &Profile, ) -> anyhow::Result<()> { - let patches = profile.patches(repository)?; + let patches = profile.patches(repository, migrate::ignore)?; let mut all = Vec::new(); let iter = match filter { diff --git a/radicle-cli/src/commands/patch/ready.rs b/radicle-cli/src/commands/patch/ready.rs index 6a7147f6..bdf69995 100644 --- a/radicle-cli/src/commands/patch/ready.rs +++ b/radicle-cli/src/commands/patch/ready.rs @@ -1,5 +1,6 @@ use super::*; +use radicle::cob::migrate; use radicle::prelude::*; use radicle::storage::git::Repository; @@ -10,7 +11,7 @@ pub fn run( repository: &Repository, ) -> anyhow::Result { let signer = term::signer(profile)?; - let mut patches = profile.patches_mut(repository)?; + let mut patches = profile.patches_mut(repository, migrate::ignore)?; let Ok(mut patch) = patches.get_mut(patch_id) else { anyhow::bail!("Patch `{patch_id}` not found"); }; diff --git a/radicle-cli/src/commands/patch/redact.rs b/radicle-cli/src/commands/patch/redact.rs index 3e26bb94..1a966103 100644 --- a/radicle-cli/src/commands/patch/redact.rs +++ b/radicle-cli/src/commands/patch/redact.rs @@ -1,4 +1,4 @@ -use radicle::cob::patch; +use radicle::cob::{migrate, patch}; use radicle::git::Oid; use radicle::prelude::*; use radicle::storage::git::Repository; @@ -13,7 +13,7 @@ pub fn run( repository: &Repository, ) -> anyhow::Result<()> { let signer = &term::signer(profile)?; - let mut patches = profile.patches_mut(repository)?; + let mut patches = profile.patches_mut(repository, migrate::ignore)?; let revision_id = revision_id.resolve::(&repository.backend)?; let patch::ByRevision { diff --git a/radicle-cli/src/commands/patch/resolve.rs b/radicle-cli/src/commands/patch/resolve.rs index da594e7c..c96a2c09 100644 --- a/radicle-cli/src/commands/patch/resolve.rs +++ b/radicle-cli/src/commands/patch/resolve.rs @@ -1,4 +1,5 @@ use anyhow::anyhow; +use radicle::cob::migrate; use radicle::cob::thread::CommentId; use radicle::patch::{self, PatchId}; use radicle::patch::{cache::Patches as _, ReviewId}; @@ -15,7 +16,7 @@ pub fn resolve( profile: &Profile, ) -> anyhow::Result<()> { let signer = term::signer(profile)?; - let mut patches = profile.patches_mut(repo)?; + let mut patches = profile.patches_mut(repo, migrate::ignore)?; let patch = patches .get(&patch_id)? .ok_or_else(|| anyhow!("Patch `{patch_id}` not found"))?; @@ -32,7 +33,7 @@ pub fn unresolve( profile: &Profile, ) -> anyhow::Result<()> { let signer = term::signer(profile)?; - let mut patches = profile.patches_mut(repo)?; + let mut patches = profile.patches_mut(repo, migrate::ignore)?; let patch = patches .get(&patch_id)? .ok_or_else(|| anyhow!("Patch `{patch_id}` not found"))?; diff --git a/radicle-cli/src/commands/patch/review.rs b/radicle-cli/src/commands/patch/review.rs index 418d8ed9..ae706f1c 100644 --- a/radicle-cli/src/commands/patch/review.rs +++ b/radicle-cli/src/commands/patch/review.rs @@ -3,6 +3,7 @@ mod builder; use anyhow::{anyhow, Context}; +use radicle::cob::migrate; use radicle::cob::patch::{PatchId, RevisionId, Verdict}; use radicle::git; use radicle::prelude::*; @@ -61,7 +62,7 @@ pub fn run( "couldn't load repository {} from local state", repository.id ))?; - let mut patches = profile.patches_mut(repository)?; + let mut patches = profile.patches_mut(repository, migrate::ignore)?; let mut patch = patches .get_mut(&patch_id) .context(format!("couldn't find patch {patch_id} locally"))?; diff --git a/radicle-cli/src/commands/patch/show.rs b/radicle-cli/src/commands/patch/show.rs index 9fab1cad..436556af 100644 --- a/radicle-cli/src/commands/patch/show.rs +++ b/radicle-cli/src/commands/patch/show.rs @@ -1,6 +1,6 @@ use std::process; -use radicle::cob::patch; +use radicle::cob::{migrate, patch}; use radicle::git; use radicle::storage::git::Repository; @@ -32,7 +32,7 @@ pub fn run( stored: &Repository, workdir: Option<&git::raw::Repository>, ) -> anyhow::Result<()> { - let patches = profile.patches(stored)?; + let patches = profile.patches(stored, migrate::ignore)?; let Some(patch) = patches.get(patch_id).map_err(|e| Error::WithHint { err: e.into(), hint: "reset the cache with `rad patch cache` and try again", diff --git a/radicle-cli/src/commands/patch/update.rs b/radicle-cli/src/commands/patch/update.rs index 44765366..6778e854 100644 --- a/radicle-cli/src/commands/patch/update.rs +++ b/radicle-cli/src/commands/patch/update.rs @@ -1,4 +1,4 @@ -use radicle::cob::patch; +use radicle::cob::{migrate, patch}; use radicle::git; use radicle::prelude::*; use radicle::storage::git::Repository; @@ -19,7 +19,7 @@ pub fn run( let head_branch = try_branch(workdir.head()?)?; let (_, target_oid) = get_merge_target(repository, &head_branch)?; - let mut patches = profile.patches_mut(repository)?; + let mut patches = profile.patches_mut(repository, migrate::ignore)?; let Ok(mut patch) = patches.get_mut(&patch_id) else { anyhow::bail!("Patch `{patch_id}` not found"); }; diff --git a/radicle-cli/src/commands/stats.rs b/radicle-cli/src/commands/stats.rs index ed425c11..11099a53 100644 --- a/radicle-cli/src/commands/stats.rs +++ b/radicle-cli/src/commands/stats.rs @@ -3,6 +3,7 @@ use std::path::Path; use localtime::LocalDuration; use localtime::LocalTime; +use radicle::cob::migrate; use radicle::git; use radicle::issue::cache::Issues as _; use radicle::node::address; @@ -95,8 +96,8 @@ pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> { for repo in storage.repositories()? { let repo = storage.repository(repo.rid)?; - let issues = profile.issues(&repo)?.counts()?; - let patches = profile.patches(&repo)?.counts()?; + let issues = profile.issues(&repo, migrate::ignore)?.counts()?; + let patches = profile.patches(&repo, migrate::ignore)?.counts()?; stats.local.issues += issues.total(); stats.local.patches += patches.total(); diff --git a/radicle-remote-helper/src/list.rs b/radicle-remote-helper/src/list.rs index fd8c35d3..0228d918 100644 --- a/radicle-remote-helper/src/list.rs +++ b/radicle-remote-helper/src/list.rs @@ -3,6 +3,7 @@ use radicle::profile; use thiserror::Error; use radicle::cob; +use radicle::cob::migrate; use radicle::git; use radicle::storage::git::transport::local::Url; use radicle::storage::ReadRepository; @@ -87,7 +88,7 @@ fn patch_refs( profile: &Profile, stored: &R, ) -> Result<(), Error> { - let patches = profile.patches(stored)?; + let patches = profile.patches(stored, migrate::ignore)?; for patch in patches.list()? { let Ok((id, patch)) = patch else { // Ignore patches that fail to decode. diff --git a/radicle-remote-helper/src/push.rs b/radicle-remote-helper/src/push.rs index 4bfd2355..a9e8574d 100644 --- a/radicle-remote-helper/src/push.rs +++ b/radicle-remote-helper/src/push.rs @@ -9,8 +9,8 @@ use thiserror::Error; use radicle::cob; use radicle::cob::object::ParseObjectId; -use radicle::cob::patch; use radicle::cob::patch::cache::Patches as _; +use radicle::cob::{migrate, patch}; use radicle::crypto::Signer; use radicle::explorer::ExplorerResource; use radicle::git::canonical; @@ -227,7 +227,7 @@ pub fn run( &nid, &working, stored, - profile.patches_mut(stored)?, + profile.patches_mut(stored, migrate::ignore)?, &signer, profile, opts.clone(), @@ -247,7 +247,7 @@ pub fn run( &nid, &working, stored, - profile.patches_mut(stored)?, + profile.patches_mut(stored, migrate::ignore)?, &signer, opts.clone(), ) @@ -320,7 +320,7 @@ pub fn run( &nid, &working, stored, - profile.patches_mut(stored)?, + profile.patches_mut(stored, migrate::ignore)?, &signer, ) } diff --git a/radicle-tools/src/rad-merge.rs b/radicle-tools/src/rad-merge.rs index c909adee..143b9666 100644 --- a/radicle-tools/src/rad-merge.rs +++ b/radicle-tools/src/rad-merge.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use std::env; use anyhow::{anyhow, bail}; +use radicle::cob::migrate; use radicle::cob::patch::{PatchId, RevisionId}; use radicle::git::Oid; use radicle::storage::ReadStorage; @@ -26,7 +27,7 @@ fn main() -> anyhow::Result<()> { let profile = radicle::Profile::load()?; let (working, rid) = radicle::rad::cwd()?; let stored = profile.storage.repository(rid)?; - let mut patches = profile.patches_mut(&stored)?; + let mut patches = profile.patches_mut(&stored, migrate::ignore)?; let mut patch = patches.get_mut(&pid)?; if patch.is_merged() { diff --git a/radicle/src/cob.rs b/radicle/src/cob.rs index 4b9083cb..c8ec0a74 100644 --- a/radicle/src/cob.rs +++ b/radicle/src/cob.rs @@ -12,6 +12,7 @@ pub mod thread; #[cfg(test)] pub mod test; +pub use cache::{migrate, MigrateCallback}; pub use common::*; pub use op::{ActorId, Op}; pub use radicle_cob::{ diff --git a/radicle/src/cob/cache.rs b/radicle/src/cob/cache.rs index ae4fdc2e..16cb239c 100644 --- a/radicle/src/cob/cache.rs +++ b/radicle/src/cob/cache.rs @@ -23,7 +23,58 @@ const DB_WRITE_TIMEOUT: time::Duration = time::Duration::from_secs(6); /// Database migrations. /// The first migration is the creation of the initial tables. -const MIGRATIONS: &[&str] = &[include_str!("cache/migrations/1.sql")]; +const MIGRATIONS: &[Migration] = &[Migration::Sql(include_str!("cache/migrations/1.sql"))]; + +/// Function signature for native migrations. +type MigrateFn = fn(&sql::Connection, Progress, &dyn MigrateCallback) -> Result; + +/// A database migration. +enum Migration { + /// Migration written in SQL. + Sql(&'static str), + /// Migration function written in Rust. + #[allow(dead_code)] + Native(MigrateFn), +} + +/// Something that can process migration progress. +pub trait MigrateCallback { + /// A migration has progressed. + /// The first [`Progress`] parameter refers to the progress within the list of migrations. + /// The second [`Progress`] parameter refers to the progress within the current migration. + fn progress(&mut self, migration: Progress, item: Progress) -> Result; +} + +impl MigrateCallback for F +where + F: Fn(Progress, Progress) -> Result, +{ + fn progress(&mut self, migration: Progress, item: Progress) -> Result { + (self)(migration, item) + } +} + +/// Migration functions that implement [`MigrateCallback`]. +pub mod migrate { + use super::*; + + /// Log progress via installed logger at "info" level. + pub fn log(migration: Progress, item: Progress) -> Result { + log::info!( + target: "db", + "Migration {}/{} in progress.. ({}%)", + migration.current() + 1, + migration.total(), + item.percentage() + ); + Ok(true) + } + + /// Ignore progress, just migrate. + pub fn ignore(_migration: Progress, _item: Progress) -> Result { + Ok(true) + } +} #[derive(Error, Debug)] pub enum Error { @@ -97,7 +148,7 @@ impl Store { pub fn open>(path: P) -> Result { let mut db = sql::Connection::open_thread_safe(path)?; db.set_busy_timeout(DB_WRITE_TIMEOUT.as_millis() as usize)?; - migrate(&db)?; + migrate(&db, migrate::ignore)?; Ok(Self { db: Arc::new(db), @@ -108,7 +159,7 @@ impl Store { /// Create a new in-memory database. pub fn memory() -> Result { let db = Arc::new(sql::Connection::open_thread_safe(":memory:")?); - migrate(&db)?; + migrate(&db, migrate::ignore)?; Ok(Self { db, @@ -164,17 +215,34 @@ fn bump(db: &sql::Connection) -> Result { } /// Migrate the database to the latest schema. -fn migrate(db: &sql::Connection) -> Result { +fn migrate(db: &sql::Connection, mut callback: M) -> Result +where + M: MigrateCallback, +{ let mut version = version(db)?; - for (i, migration) in MIGRATIONS.iter().enumerate() { - if i >= version { - transaction(db, |db| { - db.execute(migration)?; - version = bump(db)?; + let total = MIGRATIONS.len(); - Ok::<_, Error>(()) - })?; + for (i, migration) in MIGRATIONS.iter().enumerate() { + if i < version { + continue; } + transaction(db, |db| { + match migration { + Migration::Sql(query) => { + db.execute(query)?; + callback.progress( + Progress { total, current: i }, + Progress::done(db.change_count()), + )?; + } + Migration::Native(migrate) => { + migrate(db, Progress { total, current: i }, &callback)?; + } + } + version = bump(db)?; + + Ok::<_, Error>(()) + })?; } Ok(version) } @@ -283,20 +351,28 @@ impl Remove for NoCache { /// /// See [`crate::cob::issue::Cache::write_all`] and /// [`crate::cob::patch::Cache::write_all`]. -pub struct WriteAllProgress { +pub struct Progress { + current: usize, total: usize, - seen: usize, } -impl WriteAllProgress { +impl Progress { /// Create a new progress tracker with the given `total` amount. pub fn new(total: usize) -> Self { - Self { total, seen: 0 } + Self { current: 0, total } } - /// Increment the [`WriteAllProgress::seen`] progress. + /// Create a new progress tracker that is "done". + pub fn done(total: usize) -> Self { + Self { + current: total, + total, + } + } + + /// Increment the [`Progress::current`] progress. pub fn inc(&mut self) { - self.seen += 1; + self.current += 1; } /// Return the `total` amount. @@ -304,9 +380,9 @@ impl WriteAllProgress { self.total } - /// Return the `seen` amount. - pub fn seen(&self) -> usize { - self.seen + /// Return the `current` amount. + pub fn current(&self) -> usize { + self.current } /// Return the percentage of the progress made. @@ -315,6 +391,6 @@ impl WriteAllProgress { /// /// If the `total` provided is `0`. pub fn percentage(&self) -> f32 { - (self.seen as f32 / self.total as f32) * 100.0 + (self.current as f32 / self.total as f32) * 100.0 } } diff --git a/radicle/src/cob/issue/cache.rs b/radicle/src/cob/issue/cache.rs index 30f3eea4..26788697 100644 --- a/radicle/src/cob/issue/cache.rs +++ b/radicle/src/cob/issue/cache.rs @@ -143,10 +143,7 @@ impl<'a, R, C> Cache, C> { /// or break from the process. pub fn write_all( &mut self, - on_issue: impl Fn( - &Result<(IssueId, Issue), store::Error>, - &cache::WriteAllProgress, - ) -> ControlFlow<()>, + on_issue: impl Fn(&Result<(IssueId, Issue), store::Error>, &cache::Progress) -> ControlFlow<()>, ) -> Result<(), super::Error> where R: ReadRepository + cob::Store, @@ -158,7 +155,7 @@ impl<'a, R, C> Cache, C> { .map_err(|e| super::Error::CacheRemoveAll { err: e.into() })?; let issues = self.store.all()?; - let mut progress = cache::WriteAllProgress::new(issues.len()); + let mut progress = cache::Progress::new(issues.len()); for issue in self.store.all()? { progress.inc(); match on_issue(&issue, &progress) { diff --git a/radicle/src/cob/patch/cache.rs b/radicle/src/cob/patch/cache.rs index 17736836..109951fa 100644 --- a/radicle/src/cob/patch/cache.rs +++ b/radicle/src/cob/patch/cache.rs @@ -210,10 +210,7 @@ impl<'a, R, C> Cache, C> { /// or break from the process. pub fn write_all( &mut self, - callback: impl Fn( - &Result<(PatchId, Patch), store::Error>, - &cache::WriteAllProgress, - ) -> ControlFlow<()>, + callback: impl Fn(&Result<(PatchId, Patch), store::Error>, &cache::Progress) -> ControlFlow<()>, ) -> Result<(), super::Error> where R: ReadRepository + cob::Store, @@ -225,7 +222,7 @@ impl<'a, R, C> Cache, C> { .map_err(|e| super::Error::CacheRemoveAll { err: e.into() })?; let patches = self.store.all()?; - let mut progress = cache::WriteAllProgress::new(patches.len()); + let mut progress = cache::Progress::new(patches.len()); for patch in self.store.all()? { progress.inc(); match callback(&patch, &progress) { diff --git a/radicle/src/profile.rs b/radicle/src/profile.rs index 716acf66..cb7fd430 100644 --- a/radicle/src/profile.rs +++ b/radicle/src/profile.rs @@ -562,58 +562,70 @@ impl Home { } /// Return a read-only handle for the issues cache. - pub fn issues<'a, R>( + pub fn issues<'a, R, M>( &self, repository: &'a R, + _migrate: M, ) -> Result, cob::cache::StoreReader>, Error> where + M: cob::MigrateCallback, R: ReadRepository + cob::Store, { let path = self.cobs().join(cob::cache::COBS_DB_FILE); let db = cob::cache::Store::reader(path)?; let store = cob::issue::Issues::open(repository)?; + Ok(cob::issue::Cache::reader(store, db)) } /// Return a read-write handle for the issues cache. - pub fn issues_mut<'a, R>( + pub fn issues_mut<'a, R, M>( &self, repository: &'a R, + _migrate: M, ) -> Result, cob::cache::StoreWriter>, Error> where + M: cob::MigrateCallback, R: ReadRepository + cob::Store, { let path = self.cobs().join(cob::cache::COBS_DB_FILE); let db = cob::cache::Store::open(path)?; let store = cob::issue::Issues::open(repository)?; + Ok(cob::issue::Cache::open(store, db)) } /// Return a read-only handle for the patches cache. - pub fn patches<'a, R>( + pub fn patches<'a, R, M>( &self, repository: &'a R, + _migrate: M, ) -> Result, cob::cache::StoreReader>, Error> where + M: cob::MigrateCallback, R: ReadRepository + cob::Store, { let path = self.cobs().join(cob::cache::COBS_DB_FILE); let db = cob::cache::Store::reader(path)?; let store = cob::patch::Patches::open(repository)?; + Ok(cob::patch::Cache::reader(store, db)) } /// Return a read-write handle for the patches cache. - pub fn patches_mut<'a, R>( + pub fn patches_mut<'a, R, M>( &self, repository: &'a R, + _migrate: M, ) -> Result, cob::cache::StoreWriter>, Error> where + M: cob::MigrateCallback, R: ReadRepository + cob::Store, { let path = self.cobs().join(cob::cache::COBS_DB_FILE); let db = cob::cache::Store::open(path)?; let store = cob::patch::Patches::open(repository)?; + Ok(cob::patch::Cache::open(store, db)) } }