diff --git a/Cargo.lock b/Cargo.lock index 237f85e6..3c3eb9fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -239,12 +239,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.21.7" @@ -1119,9 +1113,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git-ref-format" -version = "0.3.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7428e0d6e549a9a613d6f019b839a0f5142c331295b79e119ca8f4faac145da1" +checksum = "76314f6eb43910ebc5eb89a1f0728724a6b9144dabd18bca4e7cc7c01c3804e3" dependencies = [ "git-ref-format-core", "git-ref-format-macro", @@ -1129,9 +1123,9 @@ dependencies = [ [[package]] name = "git-ref-format-core" -version = "0.3.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaeb9672a55e9e32cb6d3ef781e7526b25ab97d499fae71615649340b143424" +checksum = "ae6d9ee666ca7d4ad49cbf7174f785f299a18b37565694f665e8c7df24999cdd" dependencies = [ "bstr", "serde", @@ -1140,12 +1134,12 @@ dependencies = [ [[package]] name = "git-ref-format-macro" -version = "0.3.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b6ca5353accc201f6324dff744ba4660099546d4daf187ba868f07562e36ca4" +checksum = "2dc2ded12a6ea2b6e63afaf09415e4c15bf4baa74e530acd9daed9ff47e7dd41" dependencies = [ "git-ref-format-core", - "proc-macro-error", + "proc-macro-error2", "quote", "syn 2.0.89", ] @@ -2238,12 +2232,6 @@ dependencies = [ "cyphergraphy", ] -[[package]] -name = "nonempty" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7ac1e5ea23db6d61ad103e91864675049644bf47c35912336352fa4e9c109" - [[package]] name = "nonempty" version = "0.9.0" @@ -2643,27 +2631,25 @@ dependencies = [ ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", - "version_check", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ + "proc-macro-error-attr2", "proc-macro2", "quote", - "version_check", + "syn 2.0.89", ] [[package]] @@ -2751,13 +2737,15 @@ dependencies = [ "localtime", "log", "multibase", - "nonempty 0.9.0", + "nonempty", "pretty_assertions", "qcheck", "qcheck-macros", "radicle-cob", "radicle-crypto", - "radicle-git-ext", + "radicle-git-metadata", + "radicle-git-ref-format", + "radicle-oid", "radicle-ssh", "schemars", "serde", @@ -2779,19 +2767,18 @@ dependencies = [ "chrono", "clap", "dunce", - "git-ref-format", "human-panic", "itertools", "lexopt", "localtime", "log", - "nonempty 0.9.0", + "nonempty", "pretty_assertions", "radicle", "radicle-cli-test", "radicle-cob", "radicle-crypto", - "radicle-git-ext", + "radicle-git-ref-format", "radicle-node", "radicle-surf", "radicle-term", @@ -2837,14 +2824,17 @@ name = "radicle-cob" version = "0.17.0" dependencies = [ "fastrand", + "git-ref-format-core", "git2", "log", - "nonempty 0.9.0", + "nonempty", "qcheck", "qcheck-macros", "radicle-crypto", "radicle-dag", - "radicle-git-ext", + "radicle-git-metadata", + "radicle-git-ref-format", + "radicle-oid", "serde", "serde_json", "signature 2.2.0", @@ -2894,17 +2884,18 @@ dependencies = [ "gix-protocol", "gix-transport", "log", - "nonempty 0.9.0", + "nonempty", "radicle", - "radicle-git-ext", + "radicle-git-ref-format", + "radicle-oid", "thiserror 1.0.69", ] [[package]] name = "radicle-git-ext" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b78c26e67d1712ad5a0c602ae3b236609461372ac04e200bda359fe4a1c6650" +checksum = "fb3de6999a8ff570e0dd92a04ba3132853dd08a3bcfc2f7faf56de7b1bd36053" dependencies = [ "git-ref-format", "git2", @@ -2944,13 +2935,12 @@ dependencies = [ "localtime", "log", "mio 1.0.4", - "nonempty 0.9.0", + "nonempty", "qcheck", "qcheck-macros", "radicle", "radicle-crypto", "radicle-fetch", - "radicle-git-ext", "radicle-protocol", "radicle-signals", "radicle-systemd", @@ -2991,14 +2981,13 @@ dependencies = [ "fastrand", "localtime", "log", - "nonempty 0.9.0", + "nonempty", "paste", "qcheck", "qcheck-macros", "radicle", "radicle-crypto", "radicle-fetch", - "radicle-git-ext", "scrypt", "serde", "serde_json", @@ -3015,7 +3004,6 @@ dependencies = [ "radicle", "radicle-cli", "radicle-crypto", - "radicle-git-ext", "thiserror 1.0.69", ] @@ -3049,22 +3037,22 @@ dependencies = [ [[package]] name = "radicle-std-ext" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db20136bbc9ae63f3fec8e5a6c369f4902fac2244501b5dfc6d668e43475aaa4" +checksum = "5310e7a04506b6ce92dc9c47b26bd24c1c680937a3dcd13cd20847f89dbda32a" [[package]] name = "radicle-surf" -version = "0.22.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb308c3989087f71e43d8c7a2737273fdc7fbcd3e6628af81a42f601ae64f314" +checksum = "2f08adc954d4a49287d86bd1ce14ca7e34a11a3acd904044db30551ca8f4e46a" dependencies = [ "anyhow", - "base64 0.13.1", + "base64 0.21.7", "flate2", "git2", "log", - "nonempty 0.5.0", + "nonempty", "radicle-git-ext", "radicle-std-ext", "tar", diff --git a/Cargo.toml b/Cargo.toml index 185eedb0..3e910b57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,8 +28,6 @@ crossbeam-channel = "0.5.6" cyphernet = "0.5.2" dunce = "1.0.5" fastrand = { version = "2.0.0", default-features = false } -git-ref-format-core = { version = "0.3.0", default-features = false } -git-ref-format = { version = "0.3.0", default-features = false } git2 = { version = "0.19.0", default-features = false } human-panic = "2" itertools = "0.14" @@ -49,7 +47,7 @@ radicle-cob = { version = "0.17", path = "crates/radicle-cob" } radicle-crypto = { version = "0.14", path = "crates/radicle-crypto" } radicle-dag = { version = "0.10", path = "crates/radicle-dag" } radicle-fetch = { version = "0.16", path = "crates/radicle-fetch" } -radicle-git-ext = { version = "0.8", default-features = false } +radicle-git-metadata = { version = "0.1.0", path = "crates/radicle-git-metadata", default-features = false } radicle-git-ref-format = { version = "0.1.0", path = "crates/radicle-git-ref-format", default-features = false } radicle-node = { version = "0.16", path = "crates/radicle-node" } radicle-oid = { version = "0.1.0", path = "crates/radicle-oid", default-features = false } @@ -70,6 +68,13 @@ thiserror = "1.0" winpipe = "0.1.1" zeroize = "1.5.7" +# Crates from the "radicle-git" workspace. These should be synced manually. +# When updating, start from `radicle-surf`: +# `radicle-surf` → `radicle-git-ext` → `git-ref-format` → `git-ref-format-core` +# Also note that `radicle-surf → git2` so try to also sync with `git2`. +git-ref-format-core = { version = "0.5.0", default-features = false } +radicle-surf = "0.25.0" + [workspace.lints] clippy.type_complexity = "allow" clippy.enum_variant_names = "allow" diff --git a/crates/radicle-cli/Cargo.toml b/crates/radicle-cli/Cargo.toml index 4517bbed..878f3601 100644 --- a/crates/radicle-cli/Cargo.toml +++ b/crates/radicle-cli/Cargo.toml @@ -18,7 +18,6 @@ anyhow = "1" chrono = { workspace = true, features = ["clock", "std"] } clap = { version = "4.5.44", features = ["derive"] } dunce = { workspace = true } -git-ref-format = { version = "0.3.0", features = ["macro"] } human-panic.workspace = true itertools.workspace = true lexopt = { workspace = true } @@ -28,10 +27,8 @@ nonempty = { workspace = true } radicle = { workspace = true, features = ["logger", "schemars"] } radicle-cob = { workspace = true } radicle-crypto = { workspace = true } -# N.b. this is required to use macros, even though it's re-exported -# through radicle -radicle-git-ext = { workspace = true, features = ["serde"] } -radicle-surf = "0.22.0" +radicle-git-ref-format = { workspace = true, features = ["macro"] } +radicle-surf = { workspace = true } radicle-term = { workspace = true } schemars = { workspace = true } serde = { workspace = true } diff --git a/crates/radicle-cli/src/commands/checkout.rs b/crates/radicle-cli/src/commands/checkout.rs index 265a8611..1f25ee22 100644 --- a/crates/radicle-cli/src/commands/checkout.rs +++ b/crates/radicle-cli/src/commands/checkout.rs @@ -156,9 +156,9 @@ pub fn setup_remotes( pub fn setup_remote( setup: &project::SetupRemote, remote_id: &NodeId, - remote_name: Option, + remote_name: Option, aliases: &impl AliasStore, -) -> anyhow::Result { +) -> anyhow::Result { let remote_name = if let Some(name) = remote_name { name } else { @@ -167,7 +167,7 @@ pub fn setup_remote( } else { remote_id.to_human() }; - git::RefString::try_from(name.as_str()) + git::fmt::RefString::try_from(name.as_str()) .map_err(|_| anyhow!("invalid remote name: '{name}'"))? }; let (remote, branch) = setup.run(&remote_name, *remote_id)?; diff --git a/crates/radicle-cli/src/commands/inbox.rs b/crates/radicle-cli/src/commands/inbox.rs index 2b7cfa00..81a7bd67 100644 --- a/crates/radicle-cli/src/commands/inbox.rs +++ b/crates/radicle-cli/src/commands/inbox.rs @@ -4,16 +4,17 @@ use std::process; use anyhow::anyhow; -use git_ref_format::Qualified; use localtime::LocalTime; use radicle::cob::TypedId; +use radicle::git::fmt::Qualified; +use radicle::git::BranchName; use radicle::identity::Identity; use radicle::issue::cache::Issues as _; use radicle::node::notifications; use radicle::node::notifications::*; use radicle::patch::cache::Patches as _; use radicle::prelude::{NodeId, Profile, RepoId}; -use radicle::storage::{BranchName, ReadRepository, ReadStorage}; +use radicle::storage::{ReadRepository, ReadStorage}; use radicle::{cob, git, Storage}; use term::Element as _; diff --git a/crates/radicle-cli/src/commands/init.rs b/crates/radicle-cli/src/commands/init.rs index 0d74ccf7..d44ea66a 100644 --- a/crates/radicle-cli/src/commands/init.rs +++ b/crates/radicle-cli/src/commands/init.rs @@ -16,9 +16,9 @@ use serde_json as json; use radicle::crypto::ssh; use radicle::explorer::ExplorerUrl; +use radicle::git::fmt::RefString; use radicle::git::raw; use radicle::git::raw::ErrorExt as _; -use radicle::git::RefString; use radicle::identity::project::ProjectName; use radicle::identity::{Doc, RepoId, Visibility}; use radicle::node::events::UploadPack; diff --git a/crates/radicle-cli/src/commands/patch.rs b/crates/radicle-cli/src/commands/patch.rs index 67f03ac8..187abfc9 100644 --- a/crates/radicle-cli/src/commands/patch.rs +++ b/crates/radicle-cli/src/commands/patch.rs @@ -24,7 +24,7 @@ use anyhow::anyhow; use radicle::cob::patch::PatchId; use radicle::cob::{patch, Label, Reaction}; -use radicle::git::RefString; +use radicle::git::fmt::RefString; use radicle::patch::cache::Patches as _; use radicle::storage::git::transport; use radicle::{prelude::*, Node}; diff --git a/crates/radicle-cli/src/commands/patch/checkout.rs b/crates/radicle-cli/src/commands/patch/checkout.rs index 9fdec248..ef1b400a 100644 --- a/crates/radicle-cli/src/commands/patch/checkout.rs +++ b/crates/radicle-cli/src/commands/patch/checkout.rs @@ -1,10 +1,10 @@ use anyhow::anyhow; -use git_ref_format::Qualified; use radicle::cob::patch; use radicle::cob::patch::RevisionId; +use radicle::git::fmt::Qualified; +use radicle::git::fmt::RefString; use radicle::git::raw::ErrorExt as _; -use radicle::git::RefString; use radicle::patch::cache::Patches as _; use radicle::patch::PatchId; use radicle::storage::git::Repository; @@ -25,7 +25,7 @@ impl Options { Some(refname) => Ok(Qualified::from_refstr(refname) .map_or_else(|| refname.clone(), |q| q.to_ref_string())), // SAFETY: Patch IDs are valid refstrings. - None => Ok(git::refname!("patch") + None => Ok(git::fmt::refname!("patch") .join(RefString::try_from(term::format::cob(id).item).unwrap())), } } @@ -57,34 +57,33 @@ pub fn run( let mut spinner = term::spinner("Performing checkout..."); let patch_branch = opts.branch(patch_id)?; - let commit = - match working.find_branch(patch_branch.as_str(), radicle::git::raw::BranchType::Local) { - Ok(branch) if opts.force => { - let commit = find_patch_commit(revision, stored, working)?; - let mut r = branch.into_reference(); - r.set_target(commit.id(), &format!("force update '{patch_branch}'"))?; - commit + let commit = match working.find_branch(patch_branch.as_str(), git::raw::BranchType::Local) { + Ok(branch) if opts.force => { + let commit = find_patch_commit(revision, stored, working)?; + let mut r = branch.into_reference(); + r.set_target(commit.id(), &format!("force update '{patch_branch}'"))?; + commit + } + Ok(branch) => { + let head = branch.get().peel_to_commit()?; + if revision.head() != head.id() { + anyhow::bail!( + "branch '{patch_branch}' already exists (use `--force` to overwrite)" + ); } - Ok(branch) => { - let head = branch.get().peel_to_commit()?; - if head.id() != *revision.head() { - anyhow::bail!( - "branch '{patch_branch}' already exists (use `--force` to overwrite)" - ); - } - head - } - Err(e) if e.is_not_found() => { - let commit = find_patch_commit(revision, stored, working)?; - // Create patch branch and switch to it. - working.branch(patch_branch.as_str(), &commit, true)?; - commit - } - Err(e) => return Err(e.into()), - }; + head + } + Err(e) if e.is_not_found() => { + let commit = find_patch_commit(revision, stored, working)?; + // Create patch branch and switch to it. + working.branch(patch_branch.as_str(), &commit, true)?; + commit + } + Err(e) => return Err(e.into()), + }; if opts.force { - let mut builder = radicle::git::raw::build::CheckoutBuilder::new(); + let mut builder = git::raw::build::CheckoutBuilder::new(); builder.force(); working.checkout_tree(commit.as_object(), Some(&mut builder))?; } else { @@ -125,7 +124,7 @@ fn find_patch_commit<'a>( stored: &Repository, working: &'a git::raw::Repository, ) -> anyhow::Result> { - let head = *revision.head(); + let head = revision.head().into(); match working.find_commit(head) { Ok(commit) => Ok(commit), diff --git a/crates/radicle-cli/src/commands/patch/review/builder.rs b/crates/radicle-cli/src/commands/patch/review/builder.rs index fe7f2448..c7f0a339 100644 --- a/crates/radicle-cli/src/commands/patch/review/builder.rs +++ b/crates/radicle-cli/src/commands/patch/review/builder.rs @@ -23,10 +23,10 @@ use radicle::cob::patch::{PatchId, Revision, Verdict}; use radicle::cob::{CodeLocation, CodeRange}; use radicle::crypto; use radicle::git; +use radicle::git::Oid; use radicle::node::device::Device; use radicle::prelude::*; use radicle::storage::git::{cob::DraftStore, Repository}; -use radicle_git_ext::Oid; use radicle_surf::diff::*; use radicle_term::{Element, VStack}; @@ -196,25 +196,28 @@ impl ReviewItem { fn paths(&self) -> (Option<(&Path, Oid)>, Option<(&Path, Oid)>) { match self { - Self::FileAdded { path, new, .. } => (None, Some((path, new.oid))), - Self::FileDeleted { path, old, .. } => (Some((path, old.oid)), None), + Self::FileAdded { path, new, .. } => (None, Some((path, Oid::from(*new.oid)))), + Self::FileDeleted { path, old, .. } => (Some((path, Oid::from(*old.oid))), None), Self::FileMoved { moved } => ( - Some((&moved.old_path, moved.old.oid)), - Some((&moved.new_path, moved.new.oid)), + Some((&moved.old_path, Oid::from(*moved.old.oid))), + Some((&moved.new_path, Oid::from(*moved.new.oid))), ), Self::FileCopied { copied } => ( - Some((&copied.old_path, copied.old.oid)), - Some((&copied.new_path, copied.new.oid)), + Some((&copied.old_path, Oid::from(*copied.old.oid))), + Some((&copied.new_path, Oid::from(*copied.new.oid))), + ), + Self::FileModified { path, old, new, .. } => ( + Some((path, Oid::from(*old.oid))), + Some((path, Oid::from(*new.oid))), + ), + Self::FileEofChanged { path, old, new, .. } => ( + Some((path, Oid::from(*old.oid))), + Some((path, Oid::from(*new.oid))), + ), + Self::FileModeChanged { path, old, new, .. } => ( + Some((path, Oid::from(*old.oid))), + Some((path, Oid::from(*new.oid))), ), - Self::FileModified { path, old, new, .. } => { - (Some((path, old.oid)), Some((path, new.oid))) - } - Self::FileEofChanged { path, old, new, .. } => { - (Some((path, old.oid)), Some((path, new.oid))) - } - Self::FileModeChanged { path, old, new, .. } => { - (Some((path, old.oid)), Some((path, new.oid))) - } } } @@ -479,7 +482,7 @@ impl FileReviewBuilder { /// of changes introduced by a patch. pub struct Brain<'a> { /// Where the review draft is being stored. - refname: git::Namespaced<'a>, + refname: git::fmt::Namespaced<'a>, /// The commit pointed to by the ref. head: git::raw::Commit<'a>, /// The tree of accepted changes pointed to by the head commit. @@ -565,7 +568,7 @@ impl<'a> Brain<'a> { } /// Get the brain's refname given the patch and remote. - fn refname(patch: &PatchId, remote: &NodeId) -> git::Namespaced<'a> { + fn refname(patch: &PatchId, remote: &NodeId) -> git::fmt::Namespaced<'a> { git::refs::storage::draft::review(remote, patch) } } diff --git a/crates/radicle-cli/src/commands/patch/update.rs b/crates/radicle-cli/src/commands/patch/update.rs index b2cc6f7e..52cac82a 100644 --- a/crates/radicle-cli/src/commands/patch/update.rs +++ b/crates/radicle-cli/src/commands/patch/update.rs @@ -1,5 +1,6 @@ use radicle::cob::patch; use radicle::git; +use radicle::git::Oid; use radicle::prelude::*; use radicle::storage::git::Repository; @@ -9,7 +10,7 @@ use crate::terminal::patch::*; /// Run patch update. pub fn run( patch_id: patch::PatchId, - base_id: Option, + base_id: Option, message: term::patch::Message, profile: &Profile, repository: &Repository, @@ -27,22 +28,25 @@ pub fn run( let head_oid = branch_oid(&head_branch)?; let base_oid = match base_id { Some(oid) => oid, - None => repository.backend.merge_base(*target_oid, *head_oid)?, + None => repository + .backend + .merge_base(target_oid.into(), head_oid.into())? + .into(), }; // N.b. we don't update if both the head and base are the same as // any previous revision if patch .revisions() - .any(|(_, revision)| revision.head() == head_oid && **revision.base() == base_oid) + .any(|(_, revision)| revision.head() == head_oid && *revision.base() == base_oid) { return Ok(()); } let (_, revision) = patch.latest(); - let message = term::patch::get_update_message(message, workdir, revision, &head_oid)?; + let message = term::patch::get_update_message(message, workdir, revision, &head_oid.into())?; let signer = term::signer(profile)?; - let revision = patch.update(message, base_oid, *head_oid, &signer)?; + let revision = patch.update(message, base_oid, head_oid, &signer)?; term::print(revision); diff --git a/crates/radicle-cli/src/commands/remote.rs b/crates/radicle-cli/src/commands/remote.rs index 3cae7441..77705470 100644 --- a/crates/radicle-cli/src/commands/remote.rs +++ b/crates/radicle-cli/src/commands/remote.rs @@ -8,7 +8,7 @@ use std::ffi::OsString; use anyhow::anyhow; -use radicle::git::RefString; +use radicle::git::fmt::RefString; use radicle::prelude::NodeId; use radicle::storage::ReadStorage; diff --git a/crates/radicle-cli/src/commands/remote/add.rs b/crates/radicle-cli/src/commands/remote/add.rs index 89562e86..7634eff3 100644 --- a/crates/radicle-cli/src/commands/remote/add.rs +++ b/crates/radicle-cli/src/commands/remote/add.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use radicle::git; -use radicle::git::RefString; +use radicle::git::fmt::RefString; use radicle::prelude::*; use radicle::Profile; use radicle_crypto::PublicKey; diff --git a/crates/radicle-cli/src/commands/stats.rs b/crates/radicle-cli/src/commands/stats.rs index 87ac5cd2..386cee66 100644 --- a/crates/radicle-cli/src/commands/stats.rs +++ b/crates/radicle-cli/src/commands/stats.rs @@ -71,7 +71,7 @@ pub fn run(_args: Args, ctx: impl term::Context) -> anyhow::Result<()> { let remote = remote?; let sigrefs = repo.reference_oid(&remote, &git::refs::storage::SIGREFS_BRANCH)?; let mut walk = repo.raw().revwalk()?; - walk.push(*sigrefs)?; + walk.push(sigrefs.into())?; stats.local.pushes += walk.count(); stats.local.forks += 1; diff --git a/crates/radicle-cli/src/commands/watch.rs b/crates/radicle-cli/src/commands/watch.rs index c854ccb9..b5084d8f 100644 --- a/crates/radicle-cli/src/commands/watch.rs +++ b/crates/radicle-cli/src/commands/watch.rs @@ -41,7 +41,7 @@ Options pub struct Options { rid: Option, - refstr: git::RefString, + refstr: git::fmt::RefString, target: Option, nid: Option, interval: time::Duration, @@ -56,7 +56,7 @@ impl Args for Options { let mut rid = None; let mut nid: Option = None; let mut target: Option = None; - let mut refstr: Option = None; + let mut refstr: Option = None; let mut interval: Option = None; let mut timeout: time::Duration = time::Duration::MAX; @@ -166,7 +166,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { fn reference( repo: &R, nid: &NodeId, - qual: &git::Qualified, + qual: &git::fmt::Qualified, ) -> Result, git::raw::Error> { match repo.reference_oid(nid, qual) { Ok(oid) => Ok(Some(oid)), diff --git a/crates/radicle-cli/src/git.rs b/crates/radicle-cli/src/git.rs index 199f60d5..1d885a49 100644 --- a/crates/radicle-cli/src/git.rs +++ b/crates/radicle-cli/src/git.rs @@ -20,14 +20,15 @@ use thiserror::Error; use radicle::crypto::ssh; use radicle::git; -use radicle::git::raw as git2; use radicle::git::{Version, VERSION_REQUIRED}; use radicle::prelude::{NodeId, RepoId}; use radicle::storage::git::transport; +pub use radicle::git::Oid; + pub use radicle::git::raw::{ - build::CheckoutBuilder, AnnotatedCommit, Commit, Direction, ErrorCode, MergeAnalysis, - MergeOptions, Oid, Reference, Repository, Signature, + build::CheckoutBuilder, AnnotatedCommit, Commit, Direction, ErrorCode, ErrorExt as _, + MergeAnalysis, MergeOptions, Reference, Repository, Signature, }; pub const CONFIG_COMMIT_GPG_SIGN: &str = "commit.gpgsign"; @@ -46,10 +47,10 @@ impl Rev { &self.0 } - /// Resolve the revision to an [`From`]. - pub fn resolve(&self, repo: &git2::Repository) -> Result + /// Resolve the revision to an [`From`]. + pub fn resolve(&self, repo: &Repository) -> Result where - T: From, + T: From, { let object = repo.revparse_single(self.as_str())?; Ok(object.id().into()) @@ -84,13 +85,13 @@ pub struct Remote<'a> { pub url: radicle::git::Url, pub pushurl: Option, - inner: git2::Remote<'a>, + inner: git::raw::Remote<'a>, } -impl<'a> TryFrom> for Remote<'a> { +impl<'a> TryFrom> for Remote<'a> { type Error = RemoteError; - fn try_from(value: git2::Remote<'a>) -> Result { + fn try_from(value: git::raw::Remote<'a>) -> Result { let url = value.url().map_or(Err(RemoteError::MissingUrl), |url| { Ok(radicle::git::Url::from_str(url)?) })?; @@ -110,7 +111,7 @@ impl<'a> TryFrom> for Remote<'a> { } impl<'a> Deref for Remote<'a> { - type Target = git2::Remote<'a>; + type Target = git::raw::Remote<'a>; fn deref(&self) -> &Self::Target { &self.inner @@ -250,7 +251,7 @@ pub fn is_signing_configured(repo: &Path) -> Result { } /// Return the list of radicle remotes for the given repository. -pub fn rad_remotes(repo: &git2::Repository) -> anyhow::Result> { +pub fn rad_remotes(repo: &Repository) -> anyhow::Result> { let remotes: Vec<_> = repo .remotes()? .iter() @@ -263,16 +264,16 @@ pub fn rad_remotes(repo: &git2::Repository) -> anyhow::Result> { } /// Check if the git remote is configured for the `Repository`. -pub fn is_remote(repo: &git2::Repository, alias: &str) -> anyhow::Result { +pub fn is_remote(repo: &Repository, alias: &str) -> anyhow::Result { match repo.find_remote(alias) { Ok(_) => Ok(true), - Err(err) if err.code() == git2::ErrorCode::NotFound => Ok(false), + Err(err) if err.is_not_found() => Ok(false), Err(err) => Err(err.into()), } } /// Get the repository's "rad" remote. -pub fn rad_remote(repo: &Repository) -> anyhow::Result<(git2::Remote, RepoId)> { +pub fn rad_remote(repo: &Repository) -> anyhow::Result<(git::raw::Remote, RepoId)> { match radicle::rad::remote(repo) { Ok((remote, id)) => Ok((remote, id)), Err(radicle::rad::RemoteError::NotFound(_)) => Err(anyhow!( @@ -352,12 +353,12 @@ pub fn parse_remote(refspec: &str) -> Option<(NodeId, &str)> { } pub fn add_tag( - repo: &git2::Repository, + repo: &Repository, message: &str, patch_tag_name: &str, -) -> anyhow::Result { +) -> anyhow::Result { let head = repo.head()?; - let commit = head.peel(git2::ObjectType::Commit).unwrap(); + let commit = head.peel(git::raw::ObjectType::Commit).unwrap(); let oid = repo.tag(patch_tag_name, &commit, &repo.signature()?, message, false)?; Ok(oid) diff --git a/crates/radicle-cli/src/git/pretty_diff.rs b/crates/radicle-cli/src/git/pretty_diff.rs index dd5d77bc..a5319cfa 100644 --- a/crates/radicle-cli/src/git/pretty_diff.rs +++ b/crates/radicle-cli/src/git/pretty_diff.rs @@ -2,7 +2,7 @@ use std::fs; use std::path::{Path, PathBuf}; use radicle::git; -use radicle_git_ext::Oid; +use radicle::git::Oid; use radicle_surf::diff; use radicle_surf::diff::{Added, Copied, Deleted, FileStats, Hunks, Modified, Moved}; use radicle_surf::diff::{Diff, DiffContent, FileDiff, Hunk, Modification}; @@ -33,7 +33,7 @@ pub trait Repo { impl Repo for git::raw::Repository { fn blob(&self, oid: git::Oid) -> Result { - let blob = self.find_blob(*oid)?; + let blob = self.find_blob(oid.into())?; if blob.is_binary() { Ok(Blob::Binary) @@ -338,7 +338,7 @@ impl ToPretty for Added { repo: &R, ) -> Self::Output { let old = None; - let new = Some((self.path.as_path(), self.new.oid)); + let new = Some((self.path.as_path(), Oid::from(*self.new.oid))); pretty_modification(header, &self.diff, old, new, repo, hi) } @@ -354,7 +354,7 @@ impl ToPretty for Deleted { header: &Self::Context, repo: &R, ) -> Self::Output { - let old = Some((self.path.as_path(), self.old.oid)); + let old = Some((self.path.as_path(), Oid::from(*self.old.oid))); let new = None; pretty_modification(header, &self.diff, old, new, repo, hi) @@ -371,8 +371,8 @@ impl ToPretty for Modified { header: &Self::Context, repo: &R, ) -> Self::Output { - let old = Some((self.path.as_path(), self.old.oid)); - let new = Some((self.path.as_path(), self.new.oid)); + let old = Some((self.path.as_path(), Oid::from(*self.old.oid))); + let new = Some((self.path.as_path(), Oid::from(*self.new.oid))); pretty_modification(header, &self.diff, old, new, repo, hi) } @@ -595,8 +595,8 @@ mod test { use term::Element; use super::*; - use radicle::git::raw::RepositoryOpenFlags; - use radicle::git::raw::{Oid, Repository}; + use git::raw::RepositoryOpenFlags; + use git::raw::{Oid, Repository}; #[test] #[ignore] diff --git a/crates/radicle-cli/src/git/unified_diff.rs b/crates/radicle-cli/src/git/unified_diff.rs index 666ad4fb..d37cdcbf 100644 --- a/crates/radicle-cli/src/git/unified_diff.rs +++ b/crates/radicle-cli/src/git/unified_diff.rs @@ -7,7 +7,6 @@ use radicle_surf::diff::FileStats; use thiserror::Error; use radicle::git; -use radicle::git::raw::Oid; use radicle_surf::diff; use radicle_surf::diff::{Diff, DiffContent, DiffFile, FileDiff, Hunk, Hunks, Line, Modification}; @@ -307,8 +306,8 @@ impl Encode for FileHeader { if old.mode == new.mode { w.meta(format!( "index {}..{} {:o}", - term::format::oid(old.oid), - term::format::oid(new.oid), + term::format::oid(*old.oid), + term::format::oid(*new.oid), u32::from(old.mode.clone()), ))?; } else { @@ -316,8 +315,8 @@ impl Encode for FileHeader { w.meta(format!("new mode {:o}", u32::from(new.mode.clone())))?; w.meta(format!( "index {}..{}", - term::format::oid(old.oid), - term::format::oid(new.oid) + term::format::oid(*old.oid), + term::format::oid(*new.oid) ))?; } @@ -334,8 +333,8 @@ impl Encode for FileHeader { w.meta(format!("new file mode {:o}", u32::from(new.mode.clone())))?; w.meta(format!( "index {}..{}", - term::format::oid(Oid::zero()), - term::format::oid(new.oid), + term::format::oid(git::Oid::sha1_zero()), + term::format::oid(*new.oid), ))?; w.meta("--- /dev/null")?; @@ -355,8 +354,8 @@ impl Encode for FileHeader { ))?; w.meta(format!( "index {}..{}", - term::format::oid(old.oid), - term::format::oid(Oid::zero()) + term::format::oid(*old.oid), + term::format::oid(git::Oid::sha1_zero()) ))?; w.meta(format!("--- a/{}", path.display()))?; diff --git a/crates/radicle-cli/src/project.rs b/crates/radicle-cli/src/project.rs index 37708d13..c7df9945 100644 --- a/crates/radicle-cli/src/project.rs +++ b/crates/radicle-cli/src/project.rs @@ -1,7 +1,7 @@ use radicle::prelude::*; use crate::git; -use radicle::git::RefStr; +use radicle::git::fmt::RefStr; use radicle::node::NodeId; /// Setup a repository remote and tracking branch. diff --git a/crates/radicle-cli/src/terminal/args.rs b/crates/radicle-cli/src/terminal/args.rs index 90f74d4e..c3c511d1 100644 --- a/crates/radicle-cli/src/terminal/args.rs +++ b/crates/radicle-cli/src/terminal/args.rs @@ -7,7 +7,7 @@ use anyhow::anyhow; use radicle::cob::{self, issue, patch}; use radicle::crypto; -use radicle::git::{Oid, RefString}; +use radicle::git::{fmt::RefString, Oid}; use radicle::node::{Address, Alias}; use radicle::prelude::{Did, NodeId, RepoId}; diff --git a/crates/radicle-cli/src/terminal/patch.rs b/crates/radicle-cli/src/terminal/patch.rs index 67d149a2..daf823a8 100644 --- a/crates/radicle-cli/src/terminal/patch.rs +++ b/crates/radicle-cli/src/terminal/patch.rs @@ -188,8 +188,8 @@ fn message_from_commits(name: &str, commits: Vec) -> Result( repo: &'a git::raw::Repository, - base: &git::Oid, - head: &git::Oid, + base: &git::raw::Oid, + head: &git::raw::Oid, ) -> Result>, git::raw::Error> { let mut commits = Vec::new(); let mut revwalk = repo.revwalk()?; @@ -205,8 +205,8 @@ pub fn patch_commits<'a>( /// The message shown in the editor when creating a `Patch`. fn create_display_message( repo: &git::raw::Repository, - base: &git::Oid, - head: &git::Oid, + base: &git::raw::Oid, + head: &git::raw::Oid, ) -> Result { let commits = patch_commits(repo, base, head)?; if commits.is_empty() { @@ -226,8 +226,8 @@ fn create_display_message( pub fn get_create_message( message: term::patch::Message, repo: &git::raw::Repository, - base: &git::Oid, - head: &git::Oid, + base: &git::raw::Oid, + head: &git::raw::Oid, ) -> Result<(Title, String), Error> { let display_msg = create_display_message(repo, base, head)?; let message = message.get(&display_msg)?; @@ -279,10 +279,10 @@ pub fn get_edit_message( /// The message shown in the editor when updating a `Patch`. fn update_display_message( repo: &git::raw::Repository, - last_rev_head: &git::Oid, - head: &git::Oid, + last_rev_head: &git::raw::Oid, + head: &git::raw::Oid, ) -> Result { - if !repo.graph_descendant_of(**head, **last_rev_head)? { + if !repo.graph_descendant_of(*head, *last_rev_head)? { return Ok(REVISION_MSG.trim_start().to_string()); } @@ -302,9 +302,9 @@ pub fn get_update_message( message: term::patch::Message, repo: &git::raw::Repository, latest: &patch::Revision, - head: &git::Oid, + head: &git::raw::Oid, ) -> Result { - let display_msg = update_display_message(repo, &latest.head(), head)?; + let display_msg = update_display_message(repo, &latest.head().into(), head)?; let message = message.get(&display_msg)?; let message = message.trim(); @@ -366,11 +366,8 @@ pub fn show( } else { vec![] }; - let ahead_behind = common::ahead_behind( - stored.raw(), - *revision.head(), - *patch.target().head(stored)?, - )?; + let ahead_behind = + common::ahead_behind(stored.raw(), revision.head(), patch.target().head(stored)?)?; let author = patch.author(); let author = term::format::Author::new(author.id(), profile, verbose); let labels = patch.labels().map(|l| l.to_string()).collect::>(); @@ -468,7 +465,7 @@ fn patch_commit_lines( let (from, to) = patch.range()?; let mut lines = Vec::new(); - for commit in patch_commits(stored.raw(), &from, &to)? { + for commit in patch_commits(stored.raw(), &from.into(), &to.into())? { lines.push(term::Line::spaced([ term::label(term::format::secondary::( term::format::oid(commit.id()).into(), @@ -484,37 +481,36 @@ fn patch_commit_lines( #[cfg(test)] mod test { use super::*; - use radicle::git::refname; + use radicle::git::fmt::refname; use radicle::test::fixtures; use std::path; fn commit( repo: &git::raw::Repository, - branch: &git::RefStr, - parent: &git::Oid, + branch: &git::fmt::RefStr, + parent: &git::raw::Oid, msg: &str, - ) -> git::Oid { + ) -> git::raw::Oid { let sig = git::raw::Signature::new( "anonymous", "anonymous@radicle.example.com", &git::raw::Time::new(0, 0), ) .unwrap(); - let head = repo.find_commit(**parent).unwrap(); + let head = repo.find_commit(*parent).unwrap(); let tree = git::write_tree(path::Path::new("README"), "Hello World!\n".as_bytes(), repo).unwrap(); let branch = git::refs::branch(branch); let commit = git::commit(repo, &head, &branch, msg, &sig, &tree).unwrap(); - commit.id().into() + commit.id() } #[test] fn test_create_display_message() { let tmpdir = tempfile::tempdir().unwrap(); let (repo, commit_0) = fixtures::repository(&tmpdir); - let commit_0 = commit_0.into(); let commit_1 = commit( &repo, &refname!("feature"), @@ -625,7 +621,6 @@ mod test { fn test_update_display_message() { let tmpdir = tempfile::tempdir().unwrap(); let (repo, commit_0) = fixtures::repository(&tmpdir); - let commit_0 = commit_0.into(); let commit_1 = commit(&repo, &refname!("feature"), &commit_0, "commit 1\n"); let commit_2 = commit(&repo, &refname!("feature"), &commit_1, "commit 2\n"); diff --git a/crates/radicle-cli/src/terminal/patch/common.rs b/crates/radicle-cli/src/terminal/patch/common.rs index 35e1dcf3..2e9e4217 100644 --- a/crates/radicle-cli/src/terminal/patch/common.rs +++ b/crates/radicle-cli/src/terminal/patch/common.rs @@ -1,7 +1,7 @@ use anyhow::anyhow; use radicle::git; -use radicle::git::raw::Oid; +use radicle::git::Oid; use radicle::prelude::*; use radicle::storage::git::Repository; @@ -9,7 +9,7 @@ use crate::terminal as term; /// Give the oid of the branch or an appropriate error. #[inline] -pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result { +pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result { let oid = branch .get() .target() @@ -18,7 +18,7 @@ pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result { } #[inline] -fn get_branch(git_ref: git::Qualified) -> git::RefString { +fn get_branch(git_ref: git::fmt::Qualified) -> git::fmt::RefString { let (_, _, head, tail) = git_ref.non_empty_components(); std::iter::once(head).chain(tail).collect() } @@ -28,16 +28,18 @@ fn get_branch(git_ref: git::Qualified) -> git::RefString { pub fn get_merge_target( storage: &Repository, head_branch: &git::raw::Branch, -) -> anyhow::Result<(git::RefString, git::Oid)> { +) -> anyhow::Result<(git::fmt::RefString, git::Oid)> { let (qualified_ref, target_oid) = storage.canonical_head()?; let head_oid = branch_oid(head_branch)?; - let merge_base = storage.raw().merge_base(*head_oid, *target_oid)?; + let merge_base = storage + .raw() + .merge_base(head_oid.into(), target_oid.into())?; - if head_oid == merge_base.into() { + if head_oid == merge_base { anyhow::bail!("commits are already included in the target branch; nothing to do"); } - Ok((get_branch(qualified_ref), (*target_oid).into())) + Ok((get_branch(qualified_ref), (target_oid))) } /// Get the diff stats between two commits. @@ -47,8 +49,8 @@ pub fn diff_stats( old: &Oid, new: &Oid, ) -> Result { - let old = repo.find_commit(*old)?; - let new = repo.find_commit(*new)?; + let old = repo.find_commit(old.into())?; + let new = repo.find_commit(new.into())?; let old_tree = old.tree()?; let new_tree = new.tree()?; let mut diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None)?; @@ -64,7 +66,7 @@ pub fn ahead_behind( revision_oid: Oid, head_oid: Oid, ) -> anyhow::Result { - let (a, b) = repo.graph_ahead_behind(revision_oid, head_oid)?; + let (a, b) = repo.graph_ahead_behind(revision_oid.into(), head_oid.into())?; if a == 0 && b == 0 { return Ok(term::Line::new(term::format::dim("up to date"))); } @@ -88,7 +90,7 @@ pub fn branches(target: &Oid, repo: &git::raw::Repository) -> anyhow::Result (radicle_cli::git::Repository, radicle_cli::git::Oid) { + ) -> (radicle_cli::git::Repository, git::raw::Oid) { radicle::test::fixtures::repository(self.work(has_alias).as_path()) } diff --git a/crates/radicle-cob/Cargo.toml b/crates/radicle-cob/Cargo.toml index 15bffd7c..a598acb5 100644 --- a/crates/radicle-cob/Cargo.toml +++ b/crates/radicle-cob/Cargo.toml @@ -17,15 +17,18 @@ rust-version.workspace = true default = [] # Only used for testing. Ensures that commit ids are stable. stable-commit-ids = [] +test = [] [dependencies] fastrand = { workspace = true } -git2 = { workspace = true, features = ["vendored-libgit2"] } +git-ref-format-core = { workspace = true } +git2 = { workspace = true, optional = true, features = ["vendored-libgit2"] } log = { workspace = true } nonempty = { workspace = true, features = ["serialize"] } radicle-crypto = { workspace = true, features = ["ssh"] } radicle-dag = { workspace = true } -radicle-git-ext = { workspace = true, features = ["serde"] } +radicle-git-metadata = { workspace = true } +radicle-oid = { workspace = true, features = ["git2", "serde", "std"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } signature = { workspace = true } @@ -36,4 +39,6 @@ fastrand = { workspace = true } qcheck = { workspace = true } qcheck-macros = { workspace = true } radicle-crypto = { workspace = true, features = ["test", "git-ref-format-core"] } +radicle-git-ref-format = { workspace = true, features = ["macro"] } +radicle-oid = { workspace = true, features = ["qcheck"] } tempfile = { workspace = true } diff --git a/crates/radicle-cob/src/backend.rs b/crates/radicle-cob/src/backend.rs index 8baeba42..22ab4ad9 100644 --- a/crates/radicle-cob/src/backend.rs +++ b/crates/radicle-cob/src/backend.rs @@ -1,3 +1,7 @@ // Copyright © 2022 The Radicle Link Contributors +#[cfg(feature = "git2")] pub mod git; + +#[cfg(feature = "stable-commit-ids")] +pub mod stable; diff --git a/crates/radicle-cob/src/backend/git.rs b/crates/radicle-cob/src/backend/git.rs index 0cf18802..b4a37489 100644 --- a/crates/radicle-cob/src/backend/git.rs +++ b/crates/radicle-cob/src/backend/git.rs @@ -1,9 +1,8 @@ // Copyright © 2022 The Radicle Team -pub mod change; +mod commit; -#[cfg(feature = "stable-commit-ids")] -pub mod stable; +pub mod change; /// Environment variable to set to overwrite the commit date for both the author and the committer. /// diff --git a/crates/radicle-cob/src/backend/git/change.rs b/crates/radicle-cob/src/backend/git/change.rs index 85fff00c..9d36dfe7 100644 --- a/crates/radicle-cob/src/backend/git/change.rs +++ b/crates/radicle-cob/src/backend/git/change.rs @@ -5,11 +5,11 @@ use std::convert::TryFrom; use std::path::PathBuf; use std::sync::LazyLock; -use git_ext::author::Author; -use git_ext::commit::{headers::Headers, Commit}; -use git_ext::Oid; +use metadata::author::Author; +use metadata::commit::headers::Headers; +use metadata::commit::trailers::OwnedTrailer; use nonempty::NonEmpty; -use radicle_git_ext::commit::trailers::OwnedTrailer; +use oid::Oid; use crate::change::store::Version; use crate::signatures; @@ -21,6 +21,8 @@ use crate::{ trailers, Embed, }; +use super::commit::Commit; + /// Name of the COB manifest file. pub const MANIFEST_BLOB_NAME: &str = "manifest"; /// Path under which COB embeds are kept. @@ -30,8 +32,7 @@ pub mod error { use std::str::Utf8Error; use std::string::FromUtf8Error; - use git_ext::commit; - use git_ext::Oid; + use oid::Oid; use thiserror::Error; use crate::signatures::error::Signatures; @@ -39,7 +40,7 @@ pub mod error { #[derive(Debug, Error)] pub enum Create { #[error(transparent)] - WriteCommit(#[from] commit::error::Write), + WriteCommit(#[from] super::super::commit::error::Write), #[error(transparent)] FromUtf8(#[from] FromUtf8Error), #[error(transparent)] @@ -55,7 +56,7 @@ pub mod error { #[derive(Debug, Error)] pub enum Load { #[error(transparent)] - Read(#[from] commit::error::Read), + Read(#[from] super::super::commit::error::Read), #[error(transparent)] Signatures(#[from] Signatures), #[error(transparent)] @@ -123,7 +124,7 @@ impl change::Storage for git2::Repository { let (id, timestamp) = write_commit( self, - resource.map(|o| *o), + resource.map(|o| o.into()), // Commit to tips, extra parents and resource. tips.iter() .cloned() @@ -134,7 +135,7 @@ impl change::Storage for git2::Repository { signature.clone(), related .iter() - .map(|p| trailers::CommitTrailer::Related(**p).into()), + .map(|p| trailers::CommitTrailer::Related(*p).into()), tree, )?; @@ -153,40 +154,34 @@ impl change::Storage for git2::Repository { fn parents_of(&self, id: &Oid) -> Result, Self::LoadError> { Ok(self - .find_commit(**id)? + .find_commit(id.into())? .parent_ids() .map(Oid::from) .collect::>()) } fn manifest_of(&self, id: &Oid) -> Result { - let commit = self.find_commit(**id)?; + let commit = self.find_commit(id.into())?; let tree = commit.tree()?; load_manifest(self, &tree) } fn load(&self, id: Self::ObjectId) -> Result { - let commit = Commit::read(self, id.into())?; - let timestamp = git2::Time::from(commit.committer().time).seconds() as u64; + let commit = super::commit::Commit::read(self, id.into())?; + let timestamp = commit.committer().time.seconds() as u64; let trailers = parse_trailers(commit.trailers())?; let (resources, related): (Vec<_>, Vec<_>) = trailers.iter().partition(|t| match t { CommitTrailer::Resource(_) => true, CommitTrailer::Related(_) => false, }); - let mut resources = resources - .into_iter() - .map(|r| r.oid().into()) - .collect::>(); - let related = related - .into_iter() - .map(|r| r.oid().into()) - .collect::>(); + let mut resources = resources.into_iter().map(|r| r.oid()).collect::>(); + let related = related.into_iter().map(|r| r.oid()).collect::>(); let parents = commit .parents() .map(Oid::from) .filter(|p| !resources.contains(p) && !related.contains(p)) .collect(); - let mut signatures = Signatures::try_from(&commit)? + let mut signatures = Signatures::try_from(&*commit)? .into_iter() .collect::>(); let Some((key, sig)) = signatures.pop() else { @@ -285,7 +280,7 @@ fn write_commit( ) -> Result<(Oid, Timestamp), error::Create> { let trailers: Vec = trailers .into_iter() - .chain(resource.map(|r| trailers::CommitTrailer::Resource(r).into())) + .chain(resource.map(|r| trailers::CommitTrailer::Resource(r.into()).into())) .collect(); let author = repo.signature()?; #[allow(unused_variables)] @@ -299,29 +294,37 @@ fn write_commit( .map_err(signatures::error::Signatures::from)? .as_str(), ); - let author = Author::try_from(&author)?; + + let author = Author { + name: String::from_utf8(author.name_bytes().to_vec())?, + email: String::from_utf8(author.email_bytes().to_vec())?, + time: { + let when = author.when(); + metadata::author::Time::new(when.seconds(), when.offset_minutes()) + }, + }; #[cfg(feature = "stable-commit-ids")] // Ensures the commit id doesn't change on every run. let (author, timestamp) = { - let stable = crate::git::stable::read_timestamp(); + let stable = crate::stable::read_timestamp(); ( Author { - time: git_ext::author::Time::new(stable, 0), + time: metadata::author::Time::new(stable, 0), ..author }, stable, ) }; - let (author, timestamp) = if let Ok(s) = std::env::var(crate::git::GIT_COMMITTER_DATE) { + let (author, timestamp) = if let Ok(s) = std::env::var(super::GIT_COMMITTER_DATE) { let Ok(timestamp) = s.trim().parse::() else { panic!( "Invalid timestamp value {s:?} for `{}`", - crate::git::GIT_COMMITTER_DATE + super::GIT_COMMITTER_DATE ); }; let author = Author { - time: git_ext::author::Time::new(timestamp, 0), + time: metadata::author::Time::new(timestamp, 0), ..author }; (author, timestamp) @@ -380,7 +383,7 @@ fn write_manifest( let oid = embed.content; let path = PathBuf::from(embed.name); - embeds_tree.insert(path, *oid, git2::FileMode::Blob.into())?; + embeds_tree.insert(path, oid.into(), git2::FileMode::Blob.into())?; } let oid = embeds_tree.write()?; diff --git a/crates/radicle-cob/src/backend/git/commit.rs b/crates/radicle-cob/src/backend/git/commit.rs new file mode 100644 index 00000000..462b455b --- /dev/null +++ b/crates/radicle-cob/src/backend/git/commit.rs @@ -0,0 +1,180 @@ +mod trailers; + +use std::fmt; +use std::str::{self, FromStr}; + +use git2::{ObjectType, Oid}; + +use metadata::author::Author; +use metadata::commit::headers::Headers; +use metadata::commit::trailers::OwnedTrailer; +use metadata::commit::CommitData; + +use trailers::Trailers; + +#[repr(transparent)] +pub(super) struct Commit(metadata::commit::CommitData); + +impl Commit { + pub fn new( + tree: git2::Oid, + parents: P, + author: Author, + committer: Author, + headers: Headers, + message: String, + trailers: I, + ) -> Self + where + P: IntoIterator, + I: IntoIterator, + OwnedTrailer: From, + { + Self(CommitData::new( + tree, parents, author, committer, headers, message, trailers, + )) + } +} + +impl Commit { + /// Read the [`Commit`] from the `repo` that is expected to be found at + /// `oid`. + pub fn read(repo: &git2::Repository, oid: Oid) -> Result { + let odb = repo.odb()?; + let object = odb.read(oid)?; + Ok(Commit::try_from(object.data())?) + } + + /// Write the given [`Commit`] to the `repo`. The resulting `Oid` + /// is the identifier for this commit. + pub fn write(&self, repo: &git2::Repository) -> Result { + let odb = repo.odb().map_err(error::Write::Odb)?; + self.verify_for_write(&odb)?; + Ok(odb.write(ObjectType::Commit, self.to_string().as_bytes())?) + } + + fn verify_for_write(&self, odb: &git2::Odb) -> Result<(), error::Write> { + for parent in self.0.parents() { + verify_object(odb, &parent, ObjectType::Commit)?; + } + verify_object(odb, self.0.tree(), ObjectType::Tree)?; + + Ok(()) + } +} + +fn verify_object(odb: &git2::Odb, oid: &Oid, expected: ObjectType) -> Result<(), error::Write> { + use git2::{Error, ErrorClass, ErrorCode}; + + let (_, kind) = odb + .read_header(*oid) + .map_err(|err| error::Write::OdbRead { oid: *oid, err })?; + if kind != expected { + Err(error::Write::NotCommit { + oid: *oid, + err: Error::new( + ErrorCode::NotFound, + ErrorClass::Object, + format!("Object '{oid}' is not expected object type {expected}"), + ), + }) + } else { + Ok(()) + } +} + +pub mod error { + use std::str; + + use thiserror::Error; + + #[derive(Debug, Error)] + pub enum Write { + #[error(transparent)] + Git(#[from] git2::Error), + #[error("the parent '{oid}' provided is not a commit object")] + NotCommit { + oid: git2::Oid, + #[source] + err: git2::Error, + }, + #[error("failed to access git odb")] + Odb(#[source] git2::Error), + #[error("failed to read '{oid}' from git odb")] + OdbRead { + oid: git2::Oid, + #[source] + err: git2::Error, + }, + } + + #[derive(Debug, Error)] + pub enum Read { + #[error(transparent)] + Git(#[from] git2::Error), + #[error(transparent)] + Parse(#[from] Parse), + } + + #[derive(Debug, Error)] + pub enum Parse { + #[error(transparent)] + Git(#[from] git2::Error), + #[error(transparent)] + Header(#[from] metadata::commit::headers::ParseError), + #[error(transparent)] + Utf8(#[from] str::Utf8Error), + } +} + +impl TryFrom<&[u8]> for Commit { + type Error = error::Parse; + + fn try_from(data: &[u8]) -> Result { + Commit::from_str(str::from_utf8(data)?) + } +} + +impl FromStr for Commit { + type Err = error::Parse; + + fn from_str(buffer: &str) -> Result { + let (header, message) = buffer + .split_once("\n\n") + .ok_or(metadata::commit::headers::ParseError::InvalidFormat)?; + + let (tree, parents, author, committer, headers) = + metadata::commit::headers::parse_commit_header(header)?; + + let trailers = Trailers::parse(message)?; + + let message = message + .strip_suffix(&trailers.to_string(": ")) + .unwrap_or(message) + .to_string(); + + Ok(Self(CommitData::new( + tree, + parents, + author, + committer, + headers, + message, + trailers.iter(), + ))) + } +} + +impl fmt::Display for Commit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::ops::Deref for Commit { + type Target = CommitData; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/crates/radicle-cob/src/backend/git/commit/trailers.rs b/crates/radicle-cob/src/backend/git/commit/trailers.rs new file mode 100644 index 00000000..b1b5238f --- /dev/null +++ b/crates/radicle-cob/src/backend/git/commit/trailers.rs @@ -0,0 +1,110 @@ +use std::{borrow::Cow, fmt, fmt::Write, str::FromStr}; + +use git2::{MessageTrailersStrs, MessageTrailersStrsIterator}; + +use metadata::commit::trailers::Separator; + +/// A Git commit's set of trailers that are left in the commit's +/// message. +/// +/// Trailers are key/value pairs in the last paragraph of a message, +/// not including any patches or conflicts that may be present. +/// +/// # Usage +/// +/// To construct `Trailers`, you can use [`Trailers::parse`] or its +/// `FromStr` implementation. +/// +/// To iterate over the trailers, you can use [`Trailers::iter`]. +/// +/// To render the trailers to a `String`, you can use +/// [`Trailers::to_string`] or its `Display` implementation (note that +/// it will default to using `": "` as the separator. +/// +/// # Examples +/// +/// ```text +/// Add new functionality +/// +/// Making code better with new functionality. +/// +/// X-Signed-Off-By: Alex Sellier +/// X-Co-Authored-By: Fintan Halpenny +/// ``` +/// +/// The trailers in the above example are: +/// +/// ```text +/// X-Signed-Off-By: Alex Sellier +/// X-Co-Authored-By: Fintan Halpenny +/// ``` +pub struct Trailers { + inner: MessageTrailersStrs, +} + +impl Trailers { + pub fn parse(message: &str) -> Result { + Ok(Self { + inner: git2::message_trailers_strs(message)?, + }) + } + + pub fn iter(&self) -> Iter<'_> { + Iter { + inner: self.inner.iter(), + } + } + + pub fn to_string<'a, S>(&self, sep: S) -> String + where + S: Separator<'a>, + { + let mut buf = String::new(); + for (i, trailer) in self.iter().enumerate() { + if i > 0 { + writeln!(buf).ok(); + } + + write!(buf, "{}", trailer.display(sep.sep_for(&trailer.token))).ok(); + } + writeln!(buf).ok(); + buf + } +} + +impl fmt::Display for Trailers { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.to_string(": ")) + } +} + +impl FromStr for Trailers { + type Err = git2::Error; + + fn from_str(s: &str) -> Result { + Self::parse(s) + } +} + +pub struct Iter<'a> { + inner: MessageTrailersStrsIterator<'a>, +} + +impl<'a> Iterator for Iter<'a> { + type Item = metadata::commit::trailers::Trailer<'a>; + + fn next(&mut self) -> Option { + let (token, value) = self.inner.next()?; + Some(metadata::commit::trailers::Trailer { + token: { + // This code used to live in the same module with `Token`, + // but was separated because it depends on `git2`. + // We have no way of directly constructing a `Token`, anymore + // but `git2` still guarantees that the trailer is well-formed. + metadata::commit::trailers::Token::try_from(token) + .expect("token from `git2` must be valid") + }, + value: Cow::Borrowed(value), + }) + } +} diff --git a/crates/radicle-cob/src/backend/git/stable.rs b/crates/radicle-cob/src/backend/stable.rs similarity index 100% rename from crates/radicle-cob/src/backend/git/stable.rs rename to crates/radicle-cob/src/backend/stable.rs diff --git a/crates/radicle-cob/src/change.rs b/crates/radicle-cob/src/change.rs index 8458064d..70b23bdb 100644 --- a/crates/radicle-cob/src/change.rs +++ b/crates/radicle-cob/src/change.rs @@ -1,6 +1,6 @@ // Copyright © 2021 The Radicle Link Contributors -use git_ext::Oid; +use oid::Oid; pub mod store; pub use store::{Contents, EntryId, Storage, Template, Timestamp}; diff --git a/crates/radicle-cob/src/change/store.rs b/crates/radicle-cob/src/change/store.rs index 7e8f147c..feffd98b 100644 --- a/crates/radicle-cob/src/change/store.rs +++ b/crates/radicle-cob/src/change/store.rs @@ -3,14 +3,15 @@ use std::{error::Error, fmt, num::NonZeroUsize}; use nonempty::NonEmpty; -use radicle_git_ext::Oid; +use oid::Oid; use serde::{Deserialize, Serialize}; +use crate::object::collaboration::error::{Create, Update}; use crate::{signatures, TypeName}; /// Change entry storage. pub trait Storage { - type StoreError: Error + Send + Sync + 'static; + type StoreError: Error + Send + Sync + 'static + Into + Into; type LoadError: Error + Send + Sync + 'static; type ObjectId; @@ -194,6 +195,7 @@ pub struct Embed> { pub content: T, } +#[cfg(feature = "git2")] impl> Embed { /// Create a new embed. pub fn store( @@ -210,6 +212,7 @@ impl> Embed { } } +#[cfg(feature = "git2")] impl Embed> { /// Get the object id of the embedded content. pub fn oid(&self) -> Oid { diff --git a/crates/radicle-cob/src/change_graph.rs b/crates/radicle-cob/src/change_graph.rs index 4db6e120..488521a3 100644 --- a/crates/radicle-cob/src/change_graph.rs +++ b/crates/radicle-cob/src/change_graph.rs @@ -3,8 +3,8 @@ use std::ops::ControlFlow; use std::{cmp::Ordering, collections::BTreeSet}; -use git_ext::Oid; -use radicle_dag::Dag; +use dag::Dag; +use oid::Oid; use crate::{ change, object, object::collaboration::Evaluate, signatures::ExtendedSignature, diff --git a/crates/radicle-cob/src/history.rs b/crates/radicle-cob/src/history.rs index cfcc3748..e130d367 100644 --- a/crates/radicle-cob/src/history.rs +++ b/crates/radicle-cob/src/history.rs @@ -2,8 +2,8 @@ #![allow(clippy::too_many_arguments)] use std::{cmp::Ordering, collections::BTreeSet, ops::ControlFlow}; -use git_ext::Oid; -use radicle_dag::Dag; +use dag::Dag; +use oid::Oid; pub use crate::change::{Contents, Entry, EntryId, Timestamp}; @@ -77,7 +77,7 @@ impl History { self.graph.node(id, change); for tip in tips { - self.graph.dependency(id, (*tip).into()); + self.graph.dependency(id, tip); } } diff --git a/crates/radicle-cob/src/lib.rs b/crates/radicle-cob/src/lib.rs index 198b0977..d49135b7 100644 --- a/crates/radicle-cob/src/lib.rs +++ b/crates/radicle-cob/src/lib.rs @@ -59,12 +59,20 @@ extern crate qcheck; #[macro_use(quickcheck)] extern crate qcheck_macros; +extern crate git_ref_format_core as fmt; extern crate radicle_crypto as crypto; -extern crate radicle_git_ext as git_ext; +extern crate radicle_dag as dag; +extern crate radicle_git_metadata as metadata; +extern crate radicle_oid as oid; mod backend; + +#[cfg(all(any(test, feature = "test"), feature = "git2"))] pub use backend::git; +#[cfg(feature = "stable-commit-ids")] +pub use backend::stable; + mod change_graph; mod trailers; @@ -105,19 +113,9 @@ mod tests; /// /// * [`object::Storage`] /// -/// **Note**: [`change::Storage`] is already implemented for -/// [`git2::Repository`]. It is expected that the underlying storage -/// for `object::Storage` will also be `git2::Repository`, but if not -/// please open an issue to change the definition of `Store` :) pub trait Store where Self: object::Storage - + change::Storage< - StoreError = git::change::error::Create, - LoadError = git::change::error::Load, - ObjectId = git_ext::Oid, - Parent = git_ext::Oid, - Signatures = ExtendedSignature, - >, + + change::Storage, { } diff --git a/crates/radicle-cob/src/object.rs b/crates/radicle-cob/src/object.rs index f368ed9d..dfb50a0c 100644 --- a/crates/radicle-cob/src/object.rs +++ b/crates/radicle-cob/src/object.rs @@ -1,9 +1,9 @@ // Copyright © 2022 The Radicle Link Contributors -use std::{convert::TryFrom as _, fmt, ops::Deref, str::FromStr}; +use std::{convert::TryFrom as _, ops::Deref, str::FromStr}; -use git_ext::ref_format::{Component, RefString}; -use git_ext::Oid; +use fmt::{Component, RefString}; +use oid::Oid; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -19,7 +19,7 @@ pub use storage::{Commit, Objects, Reference, Storage}; #[derive(Debug, Error)] pub enum ParseObjectId { #[error(transparent)] - Git(#[from] git2::Error), + Git(#[from] oid::str::ParseOidError), } /// The id of an object @@ -48,12 +48,14 @@ impl From<&Oid> for ObjectId { } } +#[cfg(feature = "git2")] impl From for ObjectId { fn from(oid: git2::Oid) -> Self { Oid::from(oid).into() } } +#[cfg(feature = "git2")] impl From<&git2::Oid> for ObjectId { fn from(oid: &git2::Oid) -> Self { ObjectId(Oid::from(*oid)) @@ -68,8 +70,8 @@ impl Deref for ObjectId { } } -impl fmt::Display for ObjectId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl std::fmt::Display for ObjectId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } diff --git a/crates/radicle-cob/src/object/collaboration.rs b/crates/radicle-cob/src/object/collaboration.rs index 2236f97e..11cc2dbc 100644 --- a/crates/radicle-cob/src/object/collaboration.rs +++ b/crates/radicle-cob/src/object/collaboration.rs @@ -2,8 +2,8 @@ use std::convert::Infallible; use std::fmt::Debug; -use git_ext::Oid; use nonempty::NonEmpty; +use oid::Oid; use crate::change::store::{Manifest, Version}; use crate::{change, Entry, History, ObjectId, TypeName}; @@ -101,11 +101,11 @@ impl Evaluate for NonEmpty { /// [`TypeName`] and [`ObjectId`] from it. /// /// This assumes that the `refname` is in a -/// [`git_ext::ref_format::Qualified`] format. If it has any +/// [`fmt::Qualified`] format. If it has any /// `refs/namespaces`, they will be stripped to access the underlying -/// [`git_ext::ref_format::Qualified`] format. +/// [`fmt::Qualified`] format. /// -/// In the [`git_ext::ref_format::Qualified`] format it assumes that the +/// In the [`fmt::Qualified`] format it assumes that the /// reference name is of the form: /// /// `refs///[/*]` @@ -115,14 +115,14 @@ impl Evaluate for NonEmpty { /// /// Also note that this will return `None` if: /// -/// * The `refname` is not [`git_ext::ref_format::Qualified`] +/// * The `refname` is not [`fmt::Qualified`] /// * The parsing of the [`ObjectId`] fails /// * The parsing of the [`TypeName`] fails pub fn parse_refstr(name: &R) -> Option<(TypeName, ObjectId)> where - R: AsRef, + R: AsRef, { - use git_ext::ref_format::Qualified; + use fmt::Qualified; let name = name.as_ref(); let refs_cobs = match name.to_namespaced() { None => Qualified::from_refstr(name)?, diff --git a/crates/radicle-cob/src/object/collaboration/create.rs b/crates/radicle-cob/src/object/collaboration/create.rs index c2a2b5ea..7d194a4a 100644 --- a/crates/radicle-cob/src/object/collaboration/create.rs +++ b/crates/radicle-cob/src/object/collaboration/create.rs @@ -4,7 +4,6 @@ use nonempty::NonEmpty; use crate::Embed; use crate::Evaluate; -use crate::Store; use super::*; @@ -23,7 +22,7 @@ pub struct Create { } impl Create { - fn template(self) -> change::Template { + fn template(self) -> change::Template { change::Template { type_name: self.type_name, tips: Vec::new(), @@ -38,7 +37,7 @@ impl Create { /// /// The `storage` is the backing storage for storing /// [`crate::Entry`]s at content-addressable locations. Please see -/// [`Store`] for further information. +/// [`crate::Store`] for further information. /// /// The `signer` is expected to be a cryptographic signing key. This /// ensures that the objects origin is cryptographically verifiable. @@ -57,19 +56,24 @@ pub fn create( signer: &G, resource: Option, related: Vec, - identifier: &S::Namespace, + identifier: &::Namespace, args: Create, ) -> Result, error::Create> where T: Evaluate, - S: Store, + S: crate::object::Storage + + crate::change::Storage< + ObjectId = crate::object::Oid, + Parent = crate::object::Oid, + Signatures = crate::ExtendedSignature, + >, G: signature::Signer, { let type_name = args.type_name.clone(); let version = args.version; let init_change = storage .store(resource, related, signer, args.template()) - .map_err(error::Create::from)?; + .map_err(Into::::into)?; let object_id = init_change.id().into(); let object = T::init(&init_change, storage).map_err(error::Create::evaluate)?; diff --git a/crates/radicle-cob/src/object/collaboration/error.rs b/crates/radicle-cob/src/object/collaboration/error.rs index 4b85b4c9..24ddf547 100644 --- a/crates/radicle-cob/src/object/collaboration/error.rs +++ b/crates/radicle-cob/src/object/collaboration/error.rs @@ -2,14 +2,13 @@ use thiserror::Error; -use crate::git; - #[derive(Debug, Error)] pub enum Create { #[error(transparent)] Evaluate(Box), #[error(transparent)] - CreateChange(#[from] git::change::error::Create), + #[cfg(feature = "git2")] + CreateChange(#[from] crate::backend::git::change::error::Create), #[error("failed to updated references for during object creation: {err}")] Refs { #[source] @@ -39,6 +38,7 @@ pub enum Retrieve { #[error(transparent)] Evaluate(Box), #[error(transparent)] + #[cfg(feature = "git2")] Git(#[from] git2::Error), #[error("failed to get references during object retrieval: {err}")] Refs { @@ -62,13 +62,15 @@ pub enum Update { #[error("no object found")] NoSuchObject, #[error(transparent)] - CreateChange(#[from] git::change::error::Create), + #[cfg(feature = "git2")] + CreateChange(#[from] crate::backend::git::change::error::Create), #[error("failed to get references during object update: {err}")] Refs { #[source] err: Box, }, #[error(transparent)] + #[cfg(feature = "git2")] Git(#[from] git2::Error), #[error(transparent)] Io(#[from] std::io::Error), diff --git a/crates/radicle-cob/src/object/collaboration/get.rs b/crates/radicle-cob/src/object/collaboration/get.rs index 281f86c9..ab7beddb 100644 --- a/crates/radicle-cob/src/object/collaboration/get.rs +++ b/crates/radicle-cob/src/object/collaboration/get.rs @@ -1,6 +1,8 @@ // Copyright © 2022 The Radicle Link Contributors -use crate::{change_graph::ChangeGraph, CollaborativeObject, Evaluate, ObjectId, Store, TypeName}; +use crypto::ssh::ExtendedSignature; + +use crate::{change_graph::ChangeGraph, CollaborativeObject, Evaluate, ObjectId, TypeName}; use super::error; @@ -8,7 +10,7 @@ use super::error; /// /// The `storage` is the backing storage for storing /// [`crate::Entry`]s at content-addressable locations. Please see -/// [`Store`] for further information. +/// [`crate::Store`] for further information. /// /// The `typename` is the type of object to be found, while the /// `object_id` is the identifier for the particular object under that @@ -20,7 +22,12 @@ pub fn get( ) -> Result>, error::Retrieve> where T: Evaluate, - S: Store, + S: crate::object::Storage, + S: crate::change::Storage< + ObjectId = crate::object::Oid, + Parent = crate::object::Oid, + Signatures = ExtendedSignature, + >, { let tip_refs = storage .objects(typename, oid) diff --git a/crates/radicle-cob/src/object/collaboration/info.rs b/crates/radicle-cob/src/object/collaboration/info.rs index 5e885c9f..08822cde 100644 --- a/crates/radicle-cob/src/object/collaboration/info.rs +++ b/crates/radicle-cob/src/object/collaboration/info.rs @@ -6,9 +6,10 @@ use std::collections::BTreeSet; -use git_ext::Oid; +use crypto::ssh::ExtendedSignature; +use oid::Oid; -use crate::{change_graph::ChangeGraph, ObjectId, Store, TypeName}; +use crate::{change_graph::ChangeGraph, ObjectId, TypeName}; use super::error; @@ -28,7 +29,7 @@ pub struct ChangeGraphInfo { /// /// The `storage` is the backing storage for storing /// [`crate::Entry`]s at content-addressable locations. Please see -/// [`Store`] for further information. +/// [`crate::Store`] for further information. /// /// The `typename` is the type of object to be found, while the `oid` /// is the identifier for the particular object under that type. @@ -38,7 +39,12 @@ pub fn changegraph( oid: &ObjectId, ) -> Result, error::Retrieve> where - S: Store, + S: crate::object::Storage, + S: crate::change::Storage< + ObjectId = crate::object::Oid, + Parent = crate::object::Oid, + Signatures = ExtendedSignature, + >, { let tip_refs = storage .objects(typename, oid) diff --git a/crates/radicle-cob/src/object/collaboration/list.rs b/crates/radicle-cob/src/object/collaboration/list.rs index 5f3a0cca..33d9c9bb 100644 --- a/crates/radicle-cob/src/object/collaboration/list.rs +++ b/crates/radicle-cob/src/object/collaboration/list.rs @@ -1,6 +1,6 @@ // Copyright © 2022 The Radicle Link Contributors -use crate::{change_graph::ChangeGraph, CollaborativeObject, Evaluate, Store, TypeName}; +use crate::{change_graph::ChangeGraph, CollaborativeObject, Evaluate, TypeName}; use super::error; @@ -8,7 +8,7 @@ use super::error; /// /// The `storage` is the backing storage for storing /// [`crate::Entry`]s at content-addressable locations. Please see -/// [`Store`] for further information. +/// [`crate::Store`] for further information. /// /// The `typename` is the type of objects to be listed. pub fn list( @@ -17,7 +17,12 @@ pub fn list( ) -> Result>, error::Retrieve> where T: Evaluate, - S: Store, + S: crate::object::Storage, + S: crate::change::Storage< + ObjectId = crate::object::Oid, + Parent = crate::object::Oid, + Signatures = crate::ExtendedSignature, + >, { let references = storage .types(typename) diff --git a/crates/radicle-cob/src/object/collaboration/remove.rs b/crates/radicle-cob/src/object/collaboration/remove.rs index 0b0a798d..d3b96d8f 100644 --- a/crates/radicle-cob/src/object/collaboration/remove.rs +++ b/crates/radicle-cob/src/object/collaboration/remove.rs @@ -1,6 +1,6 @@ // Copyright © 2022 The Radicle Link Contributors -use crate::{ObjectId, Store, TypeName}; +use crate::{ObjectId, TypeName}; use super::error; @@ -8,7 +8,7 @@ use super::error; /// /// The `storage` is the backing storage for storing /// [`crate::Entry`]s at content-addressable locations. Please see -/// [`Store`] for further information. +/// [`crate::Store`] for further information. /// /// The `typename` is the type of object to be found, while the /// `object_id` is the identifier for the particular object under that @@ -20,7 +20,7 @@ pub fn remove( oid: &ObjectId, ) -> Result<(), error::Remove> where - S: Store, + S: crate::object::Storage, { storage .remove(identifier, typename, oid) diff --git a/crates/radicle-cob/src/object/collaboration/update.rs b/crates/radicle-cob/src/object/collaboration/update.rs index 3c6a7c43..d05a3473 100644 --- a/crates/radicle-cob/src/object/collaboration/update.rs +++ b/crates/radicle-cob/src/object/collaboration/update.rs @@ -1,12 +1,12 @@ // Copyright © 2022 The Radicle Link Contributors use std::iter; -use git_ext::Oid; use nonempty::NonEmpty; +use oid::Oid; use crate::{ change, change_graph::ChangeGraph, history::EntryId, CollaborativeObject, Embed, Evaluate, - ExtendedSignature, ObjectId, Store, TypeName, + ExtendedSignature, ObjectId, TypeName, }; use super::error; @@ -40,7 +40,7 @@ pub struct Update { /// /// The `storage` is the backing storage for storing /// [`crate::Entry`]s at content-addressable locations. Please see -/// [`Store`] for further information. +/// [`crate::Store`] for further information. /// /// The `signer` is expected to be a cryptographic signing key. This /// ensures that the objects origin is cryptographically verifiable. @@ -65,7 +65,8 @@ pub fn update( ) -> Result, error::Update> where T: Evaluate, - S: Store, + S: crate::object::Storage, + S: change::Storage, G: signature::Signer, { let Update { @@ -86,18 +87,20 @@ where graph.evaluate(storage).map_err(error::Update::evaluate)?; // Create a commit for this change, but don't update any references yet. - let entry = storage.store( - resource, - related, - signer, - change::Template { - tips: object.history.tips().into_iter().collect(), - embeds, - contents: changes, - type_name: typename.clone(), - message, - }, - )?; + let entry = storage + .store( + resource, + related, + signer, + change::Template { + tips: object.history.tips().into_iter().collect(), + embeds, + contents: changes, + type_name: typename.clone(), + message, + }, + ) + .map_err(Into::::into)?; let head = entry.id; let parents = entry.parents.to_vec(); diff --git a/crates/radicle-cob/src/object/storage.rs b/crates/radicle-cob/src/object/storage.rs index 6779f5a5..a23d468a 100644 --- a/crates/radicle-cob/src/object/storage.rs +++ b/crates/radicle-cob/src/object/storage.rs @@ -2,8 +2,8 @@ use std::{collections::BTreeMap, error::Error}; -use git_ext::ref_format::RefString; -use git_ext::Oid; +use fmt::RefString; +use oid::Oid; use crate::change::EntryId; use crate::{ObjectId, TypeName}; @@ -93,41 +93,43 @@ pub trait Storage { pub mod convert { use std::str; - use git_ext::ref_format::RefString; + use fmt::RefString; use thiserror::Error; - use super::{Commit, Reference}; - #[derive(Debug, Error)] pub enum Error { #[error("the reference '{name}' does not point to a commit object")] NotCommit { name: RefString, + #[cfg(feature = "git2")] #[source] err: git2::Error, }, #[error(transparent)] - Ref(#[from] git_ext::ref_format::Error), + Ref(#[from] fmt::Error), #[error(transparent)] Utf8(#[from] str::Utf8Error), } - impl<'a> TryFrom> for Reference { + #[cfg(feature = "git2")] + impl<'a> TryFrom> for super::Reference { type Error = Error; fn try_from(value: git2::Reference<'a>) -> Result { let name = RefString::try_from(str::from_utf8(value.name_bytes())?)?; - let target = Commit::from(value.peel_to_commit().map_err(|err| Error::NotCommit { - name: name.clone(), - err, - })?); + let target = + super::Commit::from(value.peel_to_commit().map_err(|err| Error::NotCommit { + name: name.clone(), + err, + })?); Ok(Self { name, target }) } } - impl<'a> From> for Commit { + #[cfg(feature = "git2")] + impl<'a> From> for super::Commit { fn from(commit: git2::Commit<'a>) -> Self { - Commit { + Self { id: commit.id().into(), } } diff --git a/crates/radicle-cob/src/signatures.rs b/crates/radicle-cob/src/signatures.rs index 967eb1e7..643cdd21 100644 --- a/crates/radicle-cob/src/signatures.rs +++ b/crates/radicle-cob/src/signatures.rs @@ -8,9 +8,9 @@ use std::{ }; use crypto::{ssh, PublicKey}; -use git_ext::commit::{ +use metadata::commit::{ headers::Signature::{Pgp, Ssh}, - Commit, + CommitData, }; pub use ssh::ExtendedSignature; @@ -55,10 +55,10 @@ impl From for BTreeMap { } } -impl TryFrom<&Commit> for Signatures { +impl TryFrom<&CommitData> for Signatures { type Error = error::Signatures; - fn try_from(value: &Commit) -> Result { + fn try_from(value: &CommitData) -> Result { value .signatures() .filter_map(|signature| { diff --git a/crates/radicle-cob/src/signatures/error.rs b/crates/radicle-cob/src/signatures/error.rs index 45e5cacc..e10e8e66 100644 --- a/crates/radicle-cob/src/signatures/error.rs +++ b/crates/radicle-cob/src/signatures/error.rs @@ -1,6 +1,6 @@ // Copyright © 2019-2020 The Radicle Foundation -use radicle_crypto::ssh::ExtendedSignatureError; +use crypto::ssh::ExtendedSignatureError; use thiserror::Error; #[derive(Debug, Error)] diff --git a/crates/radicle-cob/src/test.rs b/crates/radicle-cob/src/test.rs index 2c1164d3..7562ae42 100644 --- a/crates/radicle-cob/src/test.rs +++ b/crates/radicle-cob/src/test.rs @@ -1,7 +1,11 @@ +#[cfg(feature = "git2")] pub mod identity; +#[cfg(feature = "git2")] pub use identity::{Person, Project, RemoteProject}; +#[cfg(feature = "git2")] pub mod storage; +#[cfg(feature = "git2")] pub use storage::Storage; pub mod arbitrary; diff --git a/crates/radicle-cob/src/test/arbitrary.rs b/crates/radicle-cob/src/test/arbitrary.rs index 123166a9..4a85f063 100644 --- a/crates/radicle-cob/src/test/arbitrary.rs +++ b/crates/radicle-cob/src/test/arbitrary.rs @@ -26,11 +26,7 @@ impl Arbitrary for TypeName { impl Arbitrary for ObjectId { fn arbitrary(g: &mut qcheck::Gen) -> Self { - let mut rng = fastrand::Rng::with_seed(u64::arbitrary(g)); - let bytes = iter::repeat_with(|| rng.u8(..)) - .take(20) - .collect::>(); - Self::from(git_ext::Oid::try_from(bytes.as_slice()).unwrap()) + Self::from(oid::Oid::arbitrary(g)) } } diff --git a/crates/radicle-cob/src/test/identity.rs b/crates/radicle-cob/src/test/identity.rs index 6ce679bd..3df7b869 100644 --- a/crates/radicle-cob/src/test/identity.rs +++ b/crates/radicle-cob/src/test/identity.rs @@ -1,7 +1,9 @@ pub mod project; pub use project::{Project, RemoteProject}; +#[cfg(feature = "git2")] pub mod person; +#[cfg(feature = "git2")] pub use person::Person; #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/radicle-cob/src/test/identity/person.rs b/crates/radicle-cob/src/test/identity/person.rs index 2ec89c02..5e85176f 100644 --- a/crates/radicle-cob/src/test/identity/person.rs +++ b/crates/radicle-cob/src/test/identity/person.rs @@ -1,4 +1,4 @@ -use git_ext::Oid; +use oid::Oid; use serde::{Deserialize, Serialize}; use crate::test::storage::{self, Storage}; diff --git a/crates/radicle-cob/src/test/identity/project.rs b/crates/radicle-cob/src/test/identity/project.rs index 1b813ee0..06ec1f25 100644 --- a/crates/radicle-cob/src/test/identity/project.rs +++ b/crates/radicle-cob/src/test/identity/project.rs @@ -1,6 +1,6 @@ use std::collections::BTreeSet; -use git_ext::Oid; +use oid::Oid; use serde::{Deserialize, Serialize}; use crate::test; diff --git a/crates/radicle-cob/src/test/storage.rs b/crates/radicle-cob/src/test/storage.rs index af084c40..78f1f697 100644 --- a/crates/radicle-cob/src/test/storage.rs +++ b/crates/radicle-cob/src/test/storage.rs @@ -1,6 +1,8 @@ use std::{collections::BTreeMap, convert::TryFrom as _}; -use radicle_git_ext::ref_format::{refname, Component}; +use radicle_git_ref_format::refname; + +use fmt::Component; use tempfile::TempDir; use crate::{ @@ -29,7 +31,7 @@ pub mod error { #[error(transparent)] Git(#[from] git2::Error), #[error(transparent)] - Format(#[from] git_ext::ref_format::Error), + Format(#[from] fmt::Error), } } @@ -93,16 +95,16 @@ impl change::Storage for Storage { self.as_raw().load(id) } - fn parents_of(&self, id: &git_ext::Oid) -> Result, Self::LoadError> { + fn parents_of(&self, id: &oid::Oid) -> Result, Self::LoadError> { Ok(self .as_raw() - .find_commit(**id)? + .find_commit(id.into())? .parent_ids() - .map(git_ext::Oid::from) + .map(oid::Oid::from) .collect::>()) } - fn manifest_of(&self, id: &git_ext::Oid) -> Result { + fn manifest_of(&self, id: &oid::Oid) -> Result { self.as_raw().manifest_of(id) } } diff --git a/crates/radicle-cob/src/tests.rs b/crates/radicle-cob/src/tests.rs index 683e23f2..8e08166a 100644 --- a/crates/radicle-cob/src/tests.rs +++ b/crates/radicle-cob/src/tests.rs @@ -1,238 +1,274 @@ -use std::ops::ControlFlow; +use fmt::{Component, RefString}; -use crypto::test::signer::MockSigner; -use crypto::{PublicKey, Signer}; -use git_ext::ref_format::{refname, Component, RefString}; -use nonempty::{nonempty, NonEmpty}; -use qcheck::Arbitrary; +use radicle_git_ref_format::refname; -use crate::{ - create, get, list, object, test::arbitrary::Invalid, update, Create, Entry, ObjectId, TypeName, - Update, Updated, Version, -}; +use crate::{object, test::arbitrary::Invalid, ObjectId, TypeName}; -use super::test; +#[cfg(feature = "git2")] +mod git { + use std::ops::ControlFlow; -#[test] -fn roundtrip() { - let storage = test::Storage::new(); - let signer = gen::(1); - let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap(); - let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap(); - let proj = test::RemoteProject { - project: proj, - person: terry, + use crypto::test::signer::MockSigner; + use crypto::{PublicKey, Signer}; + use nonempty::{nonempty, NonEmpty}; + use qcheck::Arbitrary; + + use crate::{ + create, get, list, update, Create, Entry, ObjectId, TypeName, Update, Updated, Version, }; - let typename = "xyz.rad.issue".parse::().unwrap(); - let cob = create::, _, _>( - &storage, - &signer, - Some(proj.project.content_id), - vec![], - signer.public_key(), - Create { - contents: nonempty!(Vec::new()), - type_name: typename.clone(), - message: "creating xyz.rad.issue".to_string(), - embeds: vec![], - version: Version::default(), - }, - ) - .unwrap(); - let expected = get(&storage, &typename, cob.id()) - .unwrap() - .expect("BUG: cob was missing"); + use crate::test; - assert_eq!(cob, expected); -} + #[test] + fn roundtrip() { + let storage = test::Storage::new(); + let signer = gen::(1); + let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap(); + let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap(); + let proj = test::RemoteProject { + project: proj, + person: terry, + }; + let typename = "xyz.rad.issue".parse::().unwrap(); + let cob = create::, _, _>( + &storage, + &signer, + Some(proj.project.content_id), + vec![], + signer.public_key(), + Create { + contents: nonempty!(Vec::new()), + type_name: typename.clone(), + message: "creating xyz.rad.issue".to_string(), + embeds: vec![], + version: Version::default(), + }, + ) + .unwrap(); -#[test] -fn list_cobs() { - let storage = test::Storage::new(); - let signer = gen::(1); - let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap(); - let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap(); - let proj = test::RemoteProject { - project: proj, - person: terry, - }; - let typename = "xyz.rad.issue".parse::().unwrap(); - let issue_1 = create::, _, _>( - &storage, - &signer, - Some(proj.project.content_id), - vec![], - signer.public_key(), - Create { - contents: nonempty!(b"issue 1".to_vec()), - type_name: typename.clone(), - message: "creating xyz.rad.issue".to_string(), - embeds: vec![], - version: Version::default(), - }, - ) - .unwrap(); + let expected = get(&storage, &typename, cob.id()) + .unwrap() + .expect("BUG: cob was missing"); - let issue_2 = create( - &storage, - &signer, - Some(proj.project.content_id), - vec![], - signer.public_key(), - Create { - contents: nonempty!(b"issue 2".to_vec()), - type_name: typename.clone(), - message: "commenting xyz.rad.issue".to_string(), - embeds: vec![], - version: Version::default(), - }, - ) - .unwrap(); + assert_eq!(cob, expected); + } - let mut expected = list(&storage, &typename).unwrap(); - expected.sort_by(|x, y| x.id().cmp(y.id())); + #[test] + fn list_cobs() { + let storage = test::Storage::new(); + let signer = gen::(1); + let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap(); + let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap(); + let proj = test::RemoteProject { + project: proj, + person: terry, + }; + let typename = "xyz.rad.issue".parse::().unwrap(); + let issue_1 = create::, _, _>( + &storage, + &signer, + Some(proj.project.content_id), + vec![], + signer.public_key(), + Create { + contents: nonempty!(b"issue 1".to_vec()), + type_name: typename.clone(), + message: "creating xyz.rad.issue".to_string(), + embeds: vec![], + version: Version::default(), + }, + ) + .unwrap(); - let mut actual = vec![issue_1, issue_2]; - actual.sort_by(|x, y| x.id().cmp(y.id())); + let issue_2 = create( + &storage, + &signer, + Some(proj.project.content_id), + vec![], + signer.public_key(), + Create { + contents: nonempty!(b"issue 2".to_vec()), + type_name: typename.clone(), + message: "commenting xyz.rad.issue".to_string(), + embeds: vec![], + version: Version::default(), + }, + ) + .unwrap(); - assert_eq!(actual, expected); -} + let mut expected = list(&storage, &typename).unwrap(); + expected.sort_by(|x, y| x.id().cmp(y.id())); -#[test] -fn update_cob() { - let storage = test::Storage::new(); - let signer = gen::(1); - let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap(); - let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap(); - let proj = test::RemoteProject { - project: proj, - person: terry, - }; - let typename = "xyz.rad.issue".parse::().unwrap(); - let cob = create::, _, _>( - &storage, - &signer, - Some(proj.project.content_id), - vec![], - signer.public_key(), - Create { - contents: nonempty!(Vec::new()), - type_name: typename.clone(), - message: "creating xyz.rad.issue".to_string(), - embeds: vec![], - version: Version::default(), - }, - ) - .unwrap(); + let mut actual = vec![issue_1, issue_2]; + actual.sort_by(|x, y| x.id().cmp(y.id())); - let not_expected = get::, _>(&storage, &typename, cob.id()) - .unwrap() - .expect("BUG: cob was missing"); + assert_eq!(actual, expected); + } - let Updated { object, .. } = update( - &storage, - &signer, - Some(proj.project.content_id), - vec![], - signer.public_key(), - Update { - changes: nonempty!(b"issue 1".to_vec()), - object_id: *cob.id(), - type_name: typename.clone(), - embeds: vec![], - message: "commenting xyz.rad.issue".to_string(), - }, - ) - .unwrap(); + #[test] + fn update_cob() { + let storage = test::Storage::new(); + let signer = gen::(1); + let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap(); + let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap(); + let proj = test::RemoteProject { + project: proj, + person: terry, + }; + let typename = "xyz.rad.issue".parse::().unwrap(); + let cob = create::, _, _>( + &storage, + &signer, + Some(proj.project.content_id), + vec![], + signer.public_key(), + Create { + contents: nonempty!(Vec::new()), + type_name: typename.clone(), + message: "creating xyz.rad.issue".to_string(), + embeds: vec![], + version: Version::default(), + }, + ) + .unwrap(); - let expected = get(&storage, &typename, object.id()) - .unwrap() - .expect("BUG: cob was missing"); + let not_expected = get::, _>(&storage, &typename, cob.id()) + .unwrap() + .expect("BUG: cob was missing"); - assert_ne!(object, not_expected); - assert_eq!(object, expected, "{object:#?} {expected:#?}"); -} + let Updated { object, .. } = update( + &storage, + &signer, + Some(proj.project.content_id), + vec![], + signer.public_key(), + Update { + changes: nonempty!(b"issue 1".to_vec()), + object_id: *cob.id(), + type_name: typename.clone(), + embeds: vec![], + message: "commenting xyz.rad.issue".to_string(), + }, + ) + .unwrap(); -#[test] -fn traverse_cobs() { - let storage = test::Storage::new(); - let neil_signer = gen::(2); - let neil = test::Person::new(&storage, "gaiman", *neil_signer.public_key()).unwrap(); - let terry_signer = gen::(1); - let terry = test::Person::new(&storage, "pratchett", *terry_signer.public_key()).unwrap(); - let proj = test::Project::new(&storage, "discworld", *terry_signer.public_key()).unwrap(); - let terry_proj = test::RemoteProject { - project: proj.clone(), - person: terry, - }; - let neil_proj = test::RemoteProject { - project: proj, - person: neil, - }; - let typename = "xyz.rad.issue".parse::().unwrap(); - let cob = create::, _, _>( - &storage, - &terry_signer, - Some(terry_proj.project.content_id), - vec![], - terry_signer.public_key(), - Create { - contents: nonempty!(b"issue 1".to_vec()), - type_name: typename.clone(), - message: "creating xyz.rad.issue".to_string(), - embeds: vec![], - version: Version::default(), - }, - ) - .unwrap(); - copy_to( - storage.as_raw(), - terry_signer.public_key(), - &neil_proj, - &typename, - *cob.id(), - ) - .unwrap(); + let expected = get(&storage, &typename, object.id()) + .unwrap() + .expect("BUG: cob was missing"); - let Updated { object, .. } = update::, _, _>( - &storage, - &neil_signer, - Some(neil_proj.project.content_id), - vec![], - neil_signer.public_key(), - Update { - changes: nonempty!(b"issue 2".to_vec()), - object_id: *cob.id(), - type_name: typename, - embeds: vec![], - message: "commenting on xyz.rad.issue".to_string(), - }, - ) - .unwrap(); + assert_ne!(object, not_expected); + assert_eq!(object, expected, "{object:#?} {expected:#?}"); + } - let root = object.history.root().id; - // traverse over the history and filter by changes that were only authorized by terry - let contents = object - .history() - .traverse(Vec::new(), &[root], |mut acc, _, entry| { - if entry.author() == terry_signer.public_key() { + #[test] + fn traverse_cobs() { + let storage = test::Storage::new(); + let neil_signer = gen::(2); + let neil = test::Person::new(&storage, "gaiman", *neil_signer.public_key()).unwrap(); + let terry_signer = gen::(1); + let terry = test::Person::new(&storage, "pratchett", *terry_signer.public_key()).unwrap(); + let proj = test::Project::new(&storage, "discworld", *terry_signer.public_key()).unwrap(); + let terry_proj = test::RemoteProject { + project: proj.clone(), + person: terry, + }; + let neil_proj = test::RemoteProject { + project: proj, + person: neil, + }; + let typename = "xyz.rad.issue".parse::().unwrap(); + let cob = create::, _, _>( + &storage, + &terry_signer, + Some(terry_proj.project.content_id), + vec![], + terry_signer.public_key(), + Create { + contents: nonempty!(b"issue 1".to_vec()), + type_name: typename.clone(), + message: "creating xyz.rad.issue".to_string(), + embeds: vec![], + version: Version::default(), + }, + ) + .unwrap(); + copy_to( + storage.as_raw(), + terry_signer.public_key(), + &neil_proj, + &typename, + *cob.id(), + ) + .unwrap(); + + let Updated { object, .. } = update::, _, _>( + &storage, + &neil_signer, + Some(neil_proj.project.content_id), + vec![], + neil_signer.public_key(), + Update { + changes: nonempty!(b"issue 2".to_vec()), + object_id: *cob.id(), + type_name: typename, + embeds: vec![], + message: "commenting on xyz.rad.issue".to_string(), + }, + ) + .unwrap(); + + let root = object.history.root().id; + // traverse over the history and filter by changes that were only authorized by terry + let contents = object + .history() + .traverse(Vec::new(), &[root], |mut acc, _, entry| { + if entry.author() == terry_signer.public_key() { + acc.push(entry.contents().head.clone()); + } + ControlFlow::Continue(acc) + }); + + assert_eq!(contents, vec![b"issue 1".to_vec()]); + + // traverse over the history and filter by changes that were only authorized by neil + let contents = object + .history() + .traverse(Vec::new(), &[root], |mut acc, _, entry| { acc.push(entry.contents().head.clone()); - } - ControlFlow::Continue(acc) - }); + ControlFlow::Continue(acc) + }); - assert_eq!(contents, vec![b"issue 1".to_vec()]); + assert_eq!(contents, vec![b"issue 1".to_vec(), b"issue 2".to_vec()]); + } - // traverse over the history and filter by changes that were only authorized by neil - let contents = object - .history() - .traverse(Vec::new(), &[root], |mut acc, _, entry| { - acc.push(entry.contents().head.clone()); - ControlFlow::Continue(acc) - }); + fn copy_to( + repo: &git2::Repository, + from: &PublicKey, + to: &test::RemoteProject, + typename: &TypeName, + object: ObjectId, + ) -> Result<(), git2::Error> { + let original = { + let name = format!("refs/rad/{from}/cobs/{typename}/{object}"); + let r = repo.find_reference(&name)?; + r.target().unwrap() + }; - assert_eq!(contents, vec![b"issue 1".to_vec(), b"issue 2".to_vec()]); + let name = format!( + "refs/rad/{}/cobs/{}/{}", + to.identifier().to_path(), + typename, + object + ); + repo.reference(&name, original, false, "copying object reference")?; + Ok(()) + } + + fn gen(size: usize) -> T { + let mut gen = qcheck::Gen::new(size); + + T::arbitrary(&mut gen) + } } #[quickcheck] @@ -297,32 +333,3 @@ fn invalid_parse_refstr(oid: Invalid, typename: TypeName) { None ); } - -fn gen(size: usize) -> T { - let mut gen = qcheck::Gen::new(size); - - T::arbitrary(&mut gen) -} - -fn copy_to( - repo: &git2::Repository, - from: &PublicKey, - to: &test::RemoteProject, - typename: &TypeName, - object: ObjectId, -) -> Result<(), git2::Error> { - let original = { - let name = format!("refs/rad/{from}/cobs/{typename}/{object}"); - let r = repo.find_reference(&name)?; - r.target().unwrap() - }; - - let name = format!( - "refs/rad/{}/cobs/{}/{}", - to.identifier().to_path(), - typename, - object - ); - repo.reference(&name, original, false, "copying object reference")?; - Ok(()) -} diff --git a/crates/radicle-cob/src/trailers.rs b/crates/radicle-cob/src/trailers.rs index 2403a8b5..ed65b4b5 100644 --- a/crates/radicle-cob/src/trailers.rs +++ b/crates/radicle-cob/src/trailers.rs @@ -1,6 +1,6 @@ // Copyright © 2019-2020 The Radicle Foundation -use git_ext::commit::trailers::{OwnedTrailer, Token, Trailer}; +use metadata::commit::trailers::{OwnedTrailer, Token, Trailer}; use std::ops::Deref as _; pub mod error { @@ -12,21 +12,22 @@ pub mod error { WrongToken, #[error("no value for Rad-Resource")] NoValue, - #[error("invalid git OID")] - InvalidOid, + /// Invalid object ID. + #[error("invalid oid: {0}")] + InvalidOid(#[from] radicle_oid::str::ParseOidError), } } /// Commit trailer for COB commits. pub enum CommitTrailer { /// Points to the owning resource. - Resource(git2::Oid), + Resource(oid::Oid), /// Points to a related change. - Related(git2::Oid), + Related(oid::Oid), } impl CommitTrailer { - pub fn oid(&self) -> git2::Oid { + pub fn oid(&self) -> oid::Oid { match self { Self::Resource(oid) => *oid, Self::Related(oid) => *oid, @@ -38,12 +39,11 @@ impl TryFrom<&Trailer<'_>> for CommitTrailer { type Error = error::InvalidResourceTrailer; fn try_from(Trailer { value, token }: &Trailer<'_>) -> Result { - let ext_oid = - git_ext::Oid::try_from(value.as_ref()).map_err(|_| Self::Error::InvalidOid)?; + let oid = value.as_ref().parse::()?; if token.deref() == "Rad-Resource" { - Ok(CommitTrailer::Resource(ext_oid.into())) + Ok(CommitTrailer::Resource(oid)) } else if token.deref() == "Rad-Related" { - Ok(CommitTrailer::Related(ext_oid.into())) + Ok(CommitTrailer::Related(oid)) } else { Err(Self::Error::WrongToken) } diff --git a/crates/radicle-cob/src/type_name.rs b/crates/radicle-cob/src/type_name.rs index 20a50314..3e548aa2 100644 --- a/crates/radicle-cob/src/type_name.rs +++ b/crates/radicle-cob/src/type_name.rs @@ -1,8 +1,8 @@ // Copyright © 2022 The Radicle Link Contributors -use std::{fmt, str::FromStr}; +use std::str::FromStr; -use git_ext::ref_format::{Component, RefString}; +use fmt::{Component, RefString}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -24,8 +24,8 @@ impl TypeName { } } -impl fmt::Display for TypeName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl std::fmt::Display for TypeName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.0.as_str()) } } diff --git a/crates/radicle-fetch/Cargo.toml b/crates/radicle-fetch/Cargo.toml index deaab254..2385bd28 100644 --- a/crates/radicle-fetch/Cargo.toml +++ b/crates/radicle-fetch/Cargo.toml @@ -20,5 +20,6 @@ gix-transport = { version = "0.44.0", features = ["blocking-client"] } log = { workspace = true, features = ["std"] } nonempty = { workspace = true } radicle = { workspace = true } -radicle-git-ext = { workspace = true, features = ["bstr"] } +radicle-oid = { workspace = true, features = ["gix"] } +radicle-git-ref-format = { workspace = true, features = ["bstr"] } thiserror = { workspace = true } \ No newline at end of file diff --git a/crates/radicle-fetch/src/git.rs b/crates/radicle-fetch/src/git.rs index 7b721b95..5d4bf04b 100644 --- a/crates/radicle-fetch/src/git.rs +++ b/crates/radicle-fetch/src/git.rs @@ -3,21 +3,3 @@ pub(crate) mod packfile; pub(crate) mod repository; pub mod refs; - -pub(crate) mod oid { - //! Helper functions for converting to/from [`radicle::git::Oid`] and - //! [`ObjectId`]. - - use gix_hash::ObjectId; - use radicle::git::Oid; - - /// Convert from an [`ObjectId`] to an [`Oid`]. - pub fn to_oid(oid: ObjectId) -> Oid { - Oid::try_from(oid.as_bytes()).expect("invalid gix Oid") - } - - /// Convert from an [`Oid`] to an [`ObjectId`]. - pub fn to_object_id(oid: Oid) -> ObjectId { - ObjectId::from(gix_hash::oid::from_bytes_unchecked(oid.as_ref())) - } -} diff --git a/crates/radicle-fetch/src/git/mem.rs b/crates/radicle-fetch/src/git/mem.rs index 598c93c3..8849b973 100644 --- a/crates/radicle-fetch/src/git/mem.rs +++ b/crates/radicle-fetch/src/git/mem.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; -use radicle::git::{Component, Oid, Qualified, RefString}; +use radicle::git::fmt::{Component, Qualified, RefString}; +use radicle::git::Oid; use radicle::prelude::PublicKey; use super::refs::{Applied, RefUpdate, Update}; diff --git a/crates/radicle-fetch/src/git/refs/update.rs b/crates/radicle-fetch/src/git/refs/update.rs index e3f54e2e..46e2ede6 100644 --- a/crates/radicle-fetch/src/git/refs/update.rs +++ b/crates/radicle-fetch/src/git/refs/update.rs @@ -18,7 +18,8 @@ use std::collections::BTreeMap; use either::Either; -use radicle::git::{Namespaced, Oid, Qualified}; +use radicle::git::fmt::{Namespaced, Qualified}; +use radicle::git::Oid; use radicle::prelude::PublicKey; pub use radicle::storage::RefUpdate; diff --git a/crates/radicle-fetch/src/git/repository.rs b/crates/radicle-fetch/src/git/repository.rs index e4e8ca8f..92f1a49a 100644 --- a/crates/radicle-fetch/src/git/repository.rs +++ b/crates/radicle-fetch/src/git/repository.rs @@ -2,7 +2,11 @@ pub mod error; use either::Either; use radicle::git::raw::ErrorExt as _; -use radicle::git::{self, Namespaced, Oid, Qualified}; +use radicle::git::{ + self, + fmt::{Namespaced, Qualified}, + Oid, +}; use radicle::storage::git::Repository; use super::refs::{Applied, Policy, RefUpdate, Update}; @@ -48,7 +52,7 @@ pub fn contains(repo: &Repository, oid: Oid) -> Result { /// - The object does not peel to a commit /// - Attempting to find the object fails fn find_and_peel(repo: &Repository, oid: Oid) -> Result { - match repo.backend.find_object(*oid, None) { + match repo.backend.find_object(oid.into(), None) { Ok(object) => Ok(object .peel(git::raw::ObjectType::Commit) .map_err(|err| error::Ancestry::Peel { oid, err })? @@ -80,7 +84,7 @@ pub fn ahead_behind( let (ahead, behind) = repo .backend - .graph_ahead_behind(*new_commit, *old_commit) + .graph_ahead_behind(new_commit.into(), old_commit.into()) .map_err(|err| error::Ancestry::Check { old: old_commit, new: new_commit, @@ -100,7 +104,7 @@ pub fn refname_to_id<'a, N>(repo: &Repository, refname: N) -> Result where N: Into>, { - use radicle::git::raw::ErrorCode::NotFound; + use git::raw::ErrorCode::NotFound; let refname = refname.into(); match repo.backend.refname_to_id(refname.as_ref()) { @@ -170,7 +174,7 @@ fn direct<'a>( }); }; - if prev == *target { + if target == prev { // If the two objects are identical, their ancestry does not matter, // we can always skip the update. return Ok(RefUpdate::Skipped { @@ -193,7 +197,7 @@ fn direct<'a>( let target = repo .backend - .find_object(*target, ANY_KIND) + .find_object(target.into(), ANY_KIND) .map_err(|err| error::Update::Ancestry(error::Ancestry::Object { oid: target, err }))?; match (prev.kind(), target.kind()) { @@ -276,7 +280,7 @@ fn prune<'a>( name: Namespaced<'a>, prev: Either>, ) -> Result, error::Update> { - use radicle::git::raw::ObjectType; + use git::raw::ObjectType; match find(repo, &name)? { Some(mut r) => { diff --git a/crates/radicle-fetch/src/git/repository/error.rs b/crates/radicle-fetch/src/git/repository/error.rs index 268e8f6f..49f5f3d0 100644 --- a/crates/radicle-fetch/src/git/repository/error.rs +++ b/crates/radicle-fetch/src/git/repository/error.rs @@ -1,9 +1,13 @@ -use radicle::git::{ext, raw, Namespaced, Oid, Qualified}; +use radicle::git::{ + self, + fmt::{Namespaced, Qualified}, + Oid, +}; use thiserror::Error; #[derive(Debug, Error)] #[error("could not open Git ODB")] -pub struct Contains(#[source] pub raw::Error); +pub struct Contains(#[source] pub git::raw::Error); #[derive(Debug, Error)] pub enum Ancestry { @@ -14,19 +18,19 @@ pub enum Ancestry { old: Oid, new: Oid, #[source] - err: raw::Error, + err: git::raw::Error, }, #[error("failed to peel object to commit {oid}: {err}")] Peel { oid: Oid, #[source] - err: raw::Error, + err: git::raw::Error, }, #[error("failed to find object {oid}: {err}")] Object { oid: Oid, #[source] - err: raw::Error, + err: git::raw::Error, }, } @@ -35,15 +39,15 @@ pub enum Ancestry { pub struct Resolve { pub name: Qualified<'static>, #[source] - pub err: raw::Error, + pub err: git::raw::Error, } #[derive(Debug, Error)] #[error("failed to scan for refs matching {pattern}")] pub struct Scan { - pub pattern: radicle::git::PatternString, + pub pattern: radicle::git::fmt::refspec::PatternString, #[source] - pub err: ext::Error, + pub err: git::raw::Error, } #[derive(Debug, Error)] @@ -55,19 +59,19 @@ pub enum Update { name: Namespaced<'static>, target: Oid, #[source] - err: raw::Error, + err: git::raw::Error, }, #[error("failed to delete reference {name}")] Delete { name: Namespaced<'static>, #[source] - err: raw::Error, + err: git::raw::Error, }, #[error("failed to find ref {name}")] Find { name: Namespaced<'static>, #[source] - err: raw::Error, + err: git::raw::Error, }, #[error("non-fast-forward update of {name} (current: {cur}, new: {new})")] NonFF { @@ -76,7 +80,7 @@ pub enum Update { cur: Oid, }, #[error("failed to peel ref to object")] - Peel(#[source] raw::Error), + Peel(#[source] git::raw::Error), #[error(transparent)] Resolve(#[from] Resolve), diff --git a/crates/radicle-fetch/src/refs.rs b/crates/radicle-fetch/src/refs.rs index f24ef3af..504730bb 100644 --- a/crates/radicle-fetch/src/refs.rs +++ b/crates/radicle-fetch/src/refs.rs @@ -1,7 +1,11 @@ use bstr::{BString, ByteSlice}; use either::Either; use radicle::crypto::PublicKey; -use radicle::git::{self, Component, Namespaced, Oid, Qualified}; +use radicle::git::{ + self, + fmt::{Component, Namespaced, Qualified}, + Oid, +}; use thiserror::Error; pub use radicle::git::refs::storage::Special; @@ -26,7 +30,6 @@ pub enum Error { pub(crate) fn unpack_ref<'a>( r: gix_protocol::handshake::Ref, ) -> Result<(ReceivedRefname<'a>, Oid), Error> { - use crate::git::oid; use gix_protocol::handshake::Ref; match r { @@ -43,7 +46,7 @@ pub(crate) fn unpack_ref<'a>( full_ref_name, object, .. - } => ReceivedRefname::try_from(full_ref_name).map(|name| (name, oid::to_oid(object))), + } => ReceivedRefname::try_from(full_ref_name).map(|name| (name, object.into())), Ref::Unborn { full_ref_name, .. } => { unreachable!("BUG: unborn ref {}", full_ref_name) } diff --git a/crates/radicle-fetch/src/stage.rs b/crates/radicle-fetch/src/stage.rs index 88b7cfbd..d5ff1753 100644 --- a/crates/radicle-fetch/src/stage.rs +++ b/crates/radicle-fetch/src/stage.rs @@ -37,7 +37,7 @@ use either::Either; use gix_protocol::handshake::Ref; use nonempty::NonEmpty; use radicle::crypto::PublicKey; -use radicle::git::{refname, Component, Namespaced, Qualified}; +use radicle::git::fmt::{refname, Component, Namespaced, Qualified}; use radicle::storage::git::Repository; use radicle::storage::refs::{RefsAt, Special}; use radicle::storage::ReadRepository; @@ -52,7 +52,7 @@ use crate::{policy, refs}; pub mod error { use radicle::crypto::PublicKey; - use radicle::git::RefString; + use radicle::git::fmt::RefString; use thiserror::Error; use crate::transport::WantsHavesError; diff --git a/crates/radicle-fetch/src/state.rs b/crates/radicle-fetch/src/state.rs index e8c4c948..2449b603 100644 --- a/crates/radicle-fetch/src/state.rs +++ b/crates/radicle-fetch/src/state.rs @@ -3,7 +3,7 @@ use std::time::Instant; use gix_protocol::handshake; use radicle::crypto::PublicKey; -use radicle::git::{Oid, Qualified}; +use radicle::git::{fmt::Qualified, Oid}; use radicle::identity::{Did, Doc, DocError}; use radicle::prelude::Verified; diff --git a/crates/radicle-fetch/src/transport.rs b/crates/radicle-fetch/src/transport.rs index 8f7bf2d2..d46c7ec5 100644 --- a/crates/radicle-fetch/src/transport.rs +++ b/crates/radicle-fetch/src/transport.rs @@ -13,12 +13,11 @@ use gix_protocol::handshake; use gix_transport::client; use gix_transport::Protocol; use gix_transport::Service; +use radicle::git::fmt::Qualified; use radicle::git::Oid; -use radicle::git::Qualified; use radicle::storage::git::Repository; use thiserror::Error; -use crate::git::oid; use crate::git::packfile::Keepfile; use crate::git::repository; @@ -163,7 +162,7 @@ where let idx = File::at(pack_path, gix_hash::Kind::Sha1)?; for oid in wants_haves.wants { - if idx.lookup(oid::to_object_id(oid)).is_none() { + if idx.lookup(oid).is_none() { return Err(Error::NotFound(oid)); } } diff --git a/crates/radicle-fetch/src/transport/fetch.rs b/crates/radicle-fetch/src/transport/fetch.rs index 5a038b34..61538ffa 100644 --- a/crates/radicle-fetch/src/transport/fetch.rs +++ b/crates/radicle-fetch/src/transport/fetch.rs @@ -9,7 +9,7 @@ use gix_protocol::fetch::negotiate::one_round::State; use gix_protocol::handshake; use gix_protocol::handshake::Ref; -use crate::git::{oid, packfile}; +use crate::git::packfile; use super::{agent_name, Connection, WantsHaves}; @@ -109,7 +109,7 @@ impl fetch::Negotiate for Negotiate { ) -> bool { let mut has_want = false; for oid in &self.wants_haves.wants { - arguments.want(oid::to_object_id(*oid)); + arguments.want(oid); has_want = true; } has_want @@ -126,7 +126,7 @@ impl fetch::Negotiate for Negotiate { _previous_response: Option<&fetch::Response>, ) -> Result<(fetch::negotiate::Round, bool), fetch::negotiate::Error> { for oid in &self.wants_haves.haves { - arguments.have(oid::to_object_id(*oid)); + arguments.have(oid); } let round = fetch::negotiate::Round { diff --git a/crates/radicle-node/Cargo.toml b/crates/radicle-node/Cargo.toml index b059dbe1..910607da 100644 --- a/crates/radicle-node/Cargo.toml +++ b/crates/radicle-node/Cargo.toml @@ -31,9 +31,6 @@ nonempty = { workspace = true, features = ["serialize"] } qcheck = { workspace = true, optional = true } radicle = { workspace = true, features = ["logger"] } radicle-fetch = { workspace = true } -# N.b. this is required to use macros, even though it's re-exported -# through radicle -radicle-git-ext = { workspace = true, features = ["serde"] } radicle-protocol = { workspace = true } radicle-signals = { workspace = true } sqlite = { workspace = true, features = ["bundled"] } diff --git a/crates/radicle-node/src/test/handle.rs b/crates/radicle-node/src/test/handle.rs index 12cb6735..0c5b6e23 100644 --- a/crates/radicle-node/src/test/handle.rs +++ b/crates/radicle-node/src/test/handle.rs @@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex}; use std::time; use radicle::crypto::PublicKey; -use radicle::git; +use radicle::git::Oid; use radicle::storage::refs::RefsAt; use crate::identity::RepoId; @@ -108,7 +108,7 @@ impl radicle::node::Handle for Handle { Ok(RefsAt { remote: self.nid()?, - at: git::raw::Oid::zero().into(), + at: Oid::sha1_zero(), }) } diff --git a/crates/radicle-node/src/test/node.rs b/crates/radicle-node/src/test/node.rs index 0b6ef093..f15109d7 100644 --- a/crates/radicle-node/src/test/node.rs +++ b/crates/radicle-node/src/test/node.rs @@ -16,7 +16,7 @@ use radicle::crypto::ssh::keystore::MemorySigner; use radicle::crypto::test::signer::MockSigner; use radicle::crypto::Signature; use radicle::git; -use radicle::git::refname; +use radicle::git::fmt::refname; use radicle::identity::{RepoId, Visibility}; use radicle::node::config::ConnectAddress; use radicle::node::policy::store as policy; @@ -363,7 +363,7 @@ impl + cyphernet::Ecdh> NodeHandle { /// of the new commit, and the reference will be updated. /// /// The `rad/sigrefs` are then updated to reflect the new change. - pub fn commit_to(&self, rid: RepoId, refname: impl AsRef) { + pub fn commit_to(&self, rid: RepoId, refname: impl AsRef) { use radicle::test::arbitrary; let refname = refname.as_ref(); @@ -516,14 +516,14 @@ impl + Signer + Clone> Node { ); // Push local branches to storage. - let mut refs = Vec::<(git::Qualified, git::Qualified)>::new(); + let mut refs = Vec::<(git::fmt::Qualified, git::fmt::Qualified)>::new(); for branch in repo.branches(Some(git::raw::BranchType::Local)).unwrap() { let (branch, _) = branch.unwrap(); - let name = git::RefString::try_from(branch.name().unwrap().unwrap()).unwrap(); + let name = git::fmt::RefString::try_from(branch.name().unwrap().unwrap()).unwrap(); refs.push(( - git::lit::refs_heads(&name).into(), - git::lit::refs_heads(&name).into(), + git::fmt::lit::refs_heads(&name).into(), + git::fmt::lit::refs_heads(&name).into(), )); } git::push(repo, "rad", refs.iter().map(|(a, b)| (a, b))).unwrap(); diff --git a/crates/radicle-node/src/test/peer.rs b/crates/radicle-node/src/test/peer.rs index 59ffe1e8..72f44993 100644 --- a/crates/radicle-node/src/test/peer.rs +++ b/crates/radicle-node/src/test/peer.rs @@ -138,7 +138,7 @@ impl> Peer { &repo, name.try_into().unwrap(), description, - radicle::git::refname!("master"), + radicle::git::fmt::refname!("master"), Visibility::default(), self.signer(), self.storage(), diff --git a/crates/radicle-node/src/tests.rs b/crates/radicle-node/src/tests.rs index bb232db7..5b6ffff5 100644 --- a/crates/radicle-node/src/tests.rs +++ b/crates/radicle-node/src/tests.rs @@ -1567,7 +1567,7 @@ fn test_queued_fetch_from_ann_same_rid() { let refname = carol .id() .to_namespace() - .join(git::refname!("refs/sigrefs")); + .join(git::fmt::refname!("refs/sigrefs")); // Finish the 1st fetch. // Ensure the ref is in the storage and cache. @@ -1749,7 +1749,7 @@ fn test_init_and_seed() { &repo, "alice".try_into().unwrap(), "alice's repo", - git::refname!("master"), + git::fmt::refname!("master"), Visibility::default(), alice.signer(), alice.storage(), diff --git a/crates/radicle-node/src/tests/e2e.rs b/crates/radicle-node/src/tests/e2e.rs index 6a8b6807..3082ffe1 100644 --- a/crates/radicle-node/src/tests/e2e.rs +++ b/crates/radicle-node/src/tests/e2e.rs @@ -3,6 +3,7 @@ use std::{collections::HashSet, thread, time}; use radicle::cob::Title; use test_log::test; +use radicle::git::raw::ErrorExt as _; use radicle::node::device::Device; use radicle::node::policy::Scope; use radicle::node::Event; @@ -248,7 +249,7 @@ fn test_replication_ref_in_sigrefs() { bob.storage .repository_mut(acme) .unwrap() - .reference(&bob.id, &git::qualified!("refs/heads/master")) + .reference(&bob.id, &git::fmt::qualified!("refs/heads/master")) .unwrap() .delete() .unwrap(); @@ -271,7 +272,7 @@ fn test_replication_ref_in_sigrefs() { .storage .repository(acme) .unwrap() - .reference(&bob.id, &git::qualified!("refs/heads/master")) + .reference(&bob.id, &git::fmt::qualified!("refs/heads/master")) .is_ok(), "refs/namespaces/{}/refs/heads/master does not exist", bob.id @@ -292,8 +293,8 @@ fn test_replication_invalid() { // Create some unsigned refs for Carol in Bob's storage. repo.raw() .reference( - &git::qualified!("refs/heads/carol").with_namespace(carol.public_key().into()), - *head, + &git::fmt::qualified!("refs/heads/carol").with_namespace(carol.public_key().into()), + head.into(), true, &String::default(), ) @@ -579,7 +580,7 @@ fn test_clone() { .canonical_head() .unwrap(); - assert_eq!(oid, *canonical); + assert_eq!(canonical, oid); // Make sure that bob has refs/rad/id set assert!(bob @@ -1193,7 +1194,6 @@ fn missing_default_branch() { #[test] fn missing_delegate_default_branch() { - use radicle::git::raw; use radicle::identity::Identity; use radicle::storage::git::Repository; let tmp = tempfile::tempdir().unwrap(); @@ -1238,7 +1238,7 @@ fn missing_delegate_default_branch() { ); assert!(matches!( default_branch, - Err(radicle::git::Error::Git(e)) if e.code() == raw::ErrorCode::NotFound + Err(e) if e.is_not_found() )); }; @@ -1528,10 +1528,10 @@ fn test_fetch_emits_canonical_ref_update() { let result = bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap(); assert!(result.is_success()); - let default_branch: git::Qualified = { + let default_branch: git::fmt::Qualified = { let repo = alice.storage.repository(rid).unwrap(); let proj = repo.project().unwrap(); - git::lit::refs_heads(proj.default_branch()).into() + git::fmt::lit::refs_heads(proj.default_branch()).into() }; alice.commit_to(rid, &default_branch); diff --git a/crates/radicle-node/src/worker/fetch.rs b/crates/radicle-node/src/worker/fetch.rs index a52f255a..da4256a3 100644 --- a/crates/radicle-node/src/worker/fetch.rs +++ b/crates/radicle-node/src/worker/fetch.rs @@ -196,7 +196,7 @@ fn notify( // for sigref verification. continue; } - if let Some(rest) = r.strip_prefix(git::refname!("refs/heads/patches")) { + if let Some(rest) = r.strip_prefix(git::fmt::refname!("refs/heads/patches")) { if radicle::cob::ObjectId::from_str(rest.as_str()).is_ok() { // Don't notify about patch branches, since we already get // notifications about patch updates. @@ -400,7 +400,7 @@ fn set_canonical_refs( let oid = object.id(); if let Err(e) = repo.backend.reference( refname.clone().as_str(), - *oid, + oid.into(), true, "set-canonical-reference from fetch (radicle)", ) { diff --git a/crates/radicle-protocol/Cargo.toml b/crates/radicle-protocol/Cargo.toml index 7d306d04..d8aeff0e 100644 --- a/crates/radicle-protocol/Cargo.toml +++ b/crates/radicle-protocol/Cargo.toml @@ -23,7 +23,6 @@ nonempty = { workspace = true, features = ["serialize"] } qcheck = { workspace = true, optional = true } radicle = { workspace = true, features = ["logger"] } radicle-fetch = { workspace = true } -radicle-git-ext = { workspace = true, features = ["serde"] } sqlite = { workspace = true, features = ["bundled"] } scrypt = { version = "0.11.0", default-features = false } serde = { workspace = true, features = ["derive"] } diff --git a/crates/radicle-protocol/src/service.rs b/crates/radicle-protocol/src/service.rs index 39ddaa90..c0202a45 100644 --- a/crates/radicle-protocol/src/service.rs +++ b/crates/radicle-protocol/src/service.rs @@ -186,8 +186,6 @@ pub enum Error { #[error(transparent)] Git(#[from] radicle::git::raw::Error), #[error(transparent)] - GitExt(#[from] radicle::git::ext::Error), - #[error(transparent)] Storage(#[from] storage::Error), #[error(transparent)] Gossip(#[from] gossip::Error), diff --git a/crates/radicle-protocol/src/wire.rs b/crates/radicle-protocol/src/wire.rs index 577218e8..9f949536 100644 --- a/crates/radicle-protocol/src/wire.rs +++ b/crates/radicle-protocol/src/wire.rs @@ -20,6 +20,7 @@ use cyphernet::addr::tor; use radicle::crypto::{PublicKey, Signature, Unverified}; use radicle::git; use radicle::git::fmt; +use radicle::git::raw; use radicle::identity::RepoId; use radicle::node; use radicle::node::Alias; @@ -285,7 +286,7 @@ where } } -impl Encode for git::RefString { +impl Encode for git::fmt::RefString { fn encode(&self, buf: &mut impl BufMut) { self.as_str().encode(buf) } @@ -300,7 +301,8 @@ impl Encode for Signature { impl Encode for git::Oid { fn encode(&self, buf: &mut impl BufMut) { // Nb. We use length-encoding here to support future SHA-2 object ids. - self.as_bytes().encode(buf) + let bytes: &[u8] = self.as_ref(); + bytes.encode(buf) } } @@ -321,7 +323,7 @@ impl Decode for Refs { for _ in 0..len { let name = String::decode(buf)?; - let name = git::RefString::try_from(name).map_err(Invalid::from)?; + let name = git::fmt::RefString::try_from(name).map_err(Invalid::from)?; let oid = git::Oid::decode(buf)?; refs.insert(name, oid); @@ -330,10 +332,10 @@ impl Decode for Refs { } } -impl Decode for git::RefString { +impl Decode for git::fmt::RefString { fn decode(buf: &mut impl Buf) -> Result { let ref_str = String::decode(buf)?; - Ok(git::RefString::try_from(ref_str).map_err(Invalid::from)?) + Ok(git::fmt::RefString::try_from(ref_str).map_err(Invalid::from)?) } } @@ -365,7 +367,7 @@ where impl Decode for git::Oid { fn decode(buf: &mut impl Buf) -> Result { - const LEN_EXPECTED: usize = mem::size_of::(); + const LEN_EXPECTED: usize = mem::size_of::(); let len = Size::decode(buf)? as usize; @@ -378,7 +380,7 @@ impl Decode for git::Oid { } let buf: [u8; LEN_EXPECTED] = Decode::decode(buf)?; - let oid = git::raw::Oid::from_bytes(&buf).expect("the buffer is exactly the right size"); + let oid = raw::Oid::from_bytes(&buf).expect("the buffer is exactly the right size"); let oid = git::Oid::from(oid); Ok(oid) @@ -627,7 +629,7 @@ mod tests { #[quickcheck] fn prop_oid(input: [u8; 20]) { - roundtrip(git::Oid::try_from(input.as_slice()).unwrap()); + roundtrip(git::Oid::from_sha1(input)); } #[test] diff --git a/crates/radicle-protocol/src/worker/fetch.rs b/crates/radicle-protocol/src/worker/fetch.rs index 1ca13f4b..f91c54bb 100644 --- a/crates/radicle-protocol/src/worker/fetch.rs +++ b/crates/radicle-protocol/src/worker/fetch.rs @@ -36,12 +36,12 @@ impl FetchResult { /// corresponding targets. #[derive(Clone, Default, Debug)] pub struct UpdatedCanonicalRefs { - inner: BTreeMap, git::Oid>, + inner: BTreeMap, git::Oid>, } impl IntoIterator for UpdatedCanonicalRefs { - type Item = (git::Qualified<'static>, git::Oid); - type IntoIter = std::collections::btree_map::IntoIter, git::Oid>; + type Item = (git::fmt::Qualified<'static>, git::Oid); + type IntoIter = std::collections::btree_map::IntoIter, git::Oid>; fn into_iter(self) -> Self::IntoIter { self.inner.into_iter() @@ -51,12 +51,12 @@ impl IntoIterator for UpdatedCanonicalRefs { 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) { + pub fn updated(&mut self, refname: git::fmt::Qualified<'static>, target: git::Oid) { self.inner.insert(refname, target); } /// Return an iterator of all the updates. - pub fn iter(&self) -> impl Iterator, &git::Oid)> { + pub fn iter(&self) -> impl Iterator, &git::Oid)> { self.inner.iter() } } diff --git a/crates/radicle-protocol/src/worker/fetch/error.rs b/crates/radicle-protocol/src/worker/fetch/error.rs index 6c9d753f..20fd310a 100644 --- a/crates/radicle-protocol/src/worker/fetch/error.rs +++ b/crates/radicle-protocol/src/worker/fetch/error.rs @@ -2,7 +2,7 @@ use std::io; use thiserror::Error; -use radicle::{cob, git, identity, storage}; +use radicle::{cob, git::raw, identity, storage}; use radicle_fetch as fetch; #[derive(Debug, Error)] @@ -10,7 +10,7 @@ pub enum Fetch { #[error(transparent)] Run(#[from] fetch::Error), #[error(transparent)] - Git(#[from] git::raw::Error), + Git(#[from] raw::Error), #[error(transparent)] Storage(#[from] storage::Error), #[error(transparent)] diff --git a/crates/radicle-remote-helper/Cargo.toml b/crates/radicle-remote-helper/Cargo.toml index b33bdce2..41895685 100644 --- a/crates/radicle-remote-helper/Cargo.toml +++ b/crates/radicle-remote-helper/Cargo.toml @@ -19,5 +19,4 @@ log = { workspace = true } radicle = { workspace = true } radicle-cli = { workspace = true } radicle-crypto = { workspace = true } -radicle-git-ext = { workspace = true } thiserror = { workspace = true } \ No newline at end of file diff --git a/crates/radicle-remote-helper/src/fetch.rs b/crates/radicle-remote-helper/src/fetch.rs index 06e43299..1c3829dc 100644 --- a/crates/radicle-remote-helper/src/fetch.rs +++ b/crates/radicle-remote-helper/src/fetch.rs @@ -19,9 +19,9 @@ pub enum Error { /// Invalid reference name. #[error("invalid ref: {0}")] InvalidRef(#[from] radicle::git::fmt::Error), - /// Git error. - #[error("git: {0}")] - InvalidOid(#[source] git::raw::Error), + /// Invalid object ID. + #[error("invalid oid: {0}")] + InvalidOid(#[from] radicle::git::ParseOidError), /// Error fetching pack from storage to working copy. #[error("`git fetch-pack` failed with exit status {status}, stderr and stdout follow:\n{stderr}\n{stdout}")] @@ -34,7 +34,7 @@ pub enum Error { /// Run a git fetch command. pub fn run( - mut refs: Vec<(git::Oid, git::RefString)>, + mut refs: Vec<(git::Oid, git::fmt::RefString)>, stored: R, stdin: &io::Stdin, verbosity: Verbosity, @@ -45,8 +45,8 @@ pub fn run( let tokens = read_line(stdin, &mut line)?; match tokens.as_slice() { ["fetch", oid, refstr] => { - let oid = git::Oid::from_str(oid).map_err(Error::InvalidOid)?; - let refstr = git::RefString::try_from(*refstr)?; + let oid = git::Oid::from_str(oid)?; + let refstr = git::fmt::RefString::try_from(*refstr)?; refs.push((oid, refstr)); } diff --git a/crates/radicle-remote-helper/src/list.rs b/crates/radicle-remote-helper/src/list.rs index 4d435a26..3a34698c 100644 --- a/crates/radicle-remote-helper/src/list.rs +++ b/crates/radicle-remote-helper/src/list.rs @@ -19,7 +19,7 @@ pub enum Error { Identity(#[from] radicle::identity::DocError), /// Git error. #[error(transparent)] - Git(#[from] radicle::git::ext::Error), + Git(#[from] radicle::git::raw::Error), /// Profile error. #[error(transparent)] Profile(#[from] profile::Error), @@ -56,8 +56,8 @@ pub fn for_fetch + 'static>( // List canonical references. // Skip over `refs/rad/*`, since those are not meant to be fetched into a working copy. for glob in [ - git::refspec::pattern!("refs/heads/*"), - git::refspec::pattern!("refs/tags/*"), + git::fmt::pattern!("refs/heads/*"), + git::fmt::pattern!("refs/tags/*"), ] { for (name, oid) in stored.references_glob(&glob)? { println!("{oid} {name}"); @@ -81,8 +81,8 @@ pub fn for_push(profile: &Profile, stored: &R) -> Result<(), // Only our own refs can be pushed to. for (name, oid) in stored.references_of(profile.id())? { // Only branches and tags can be pushed to. - if name.starts_with(git::refname!("refs/heads").as_str()) - || name.starts_with(git::refname!("refs/tags").as_str()) + if name.starts_with(git::fmt::refname!("refs/heads").as_str()) + || name.starts_with(git::fmt::refname!("refs/tags").as_str()) { println!("{oid} {name}"); } diff --git a/crates/radicle-remote-helper/src/main.rs b/crates/radicle-remote-helper/src/main.rs index 3f5cd923..e5240fa7 100644 --- a/crates/radicle-remote-helper/src/main.rs +++ b/crates/radicle-remote-helper/src/main.rs @@ -117,6 +117,9 @@ pub enum Error { /// List error. #[error(transparent)] List(#[from] list::Error), + /// Invalid object ID. + #[error("invalid oid: {0}")] + InvalidOid(#[from] radicle::git::ParseOidError), } /// Models values for the `verbosity` option, see @@ -162,13 +165,13 @@ pub enum Branch { /// Create a branch with the same name as the upstream branch (i.e. `patches/`). MirrorUpstream, /// Create a branch with the provided name. - Provided(git::RefString), + Provided(git::fmt::RefString), } impl Branch { /// Return the branch name to be used for the local branch when creating a /// patch. - pub fn to_branch_name(self, object: &radicle::patch::PatchId) -> Option { + pub fn to_branch_name(self, object: &radicle::patch::PatchId) -> Option { match self { Self::None => None, Self::MirrorUpstream => Some(git::refs::patch(object)), @@ -207,12 +210,15 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> { // module is aware of that. cli::Paint::set_terminal(cli::TerminalFile::Stderr); - let (remote, url): (Option, Url) = { + let (remote, url): (Option, Url) = { let args = env::args().skip(1).take(2).collect::>(); match args.as_slice() { [url] => (None, url.parse()?), - [remote, url] => (git::RefString::try_from(remote.as_str()).ok(), url.parse()?), + [remote, url] => ( + git::fmt::RefString::try_from(remote.as_str()).ok(), + url.parse()?, + ), _ => { return Err(Error::InvalidArguments(args)); @@ -274,7 +280,7 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> { } ["fetch", oid, refstr] => { let oid = git::Oid::from_str(oid)?; - let refstr = git::RefString::try_from(*refstr)?; + let refstr = git::fmt::RefString::try_from(*refstr)?; return Ok(fetch::run( vec![(oid, refstr)], @@ -347,7 +353,9 @@ fn push_option(args: &[&str], opts: &mut Options) -> Result<(), Error> { opts.base = Some(git::Oid::from(commit.id())); } - "patch.branch" => opts.branch = Branch::Provided(git::RefString::try_from(val)?), + "patch.branch" => { + opts.branch = Branch::Provided(git::fmt::RefString::try_from(val)?) + } other => { return Err(Error::UnsupportedPushOption(other.to_owned())); } diff --git a/crates/radicle-remote-helper/src/push.rs b/crates/radicle-remote-helper/src/push.rs index 0a86d519..b45f38df 100644 --- a/crates/radicle-remote-helper/src/push.rs +++ b/crates/radicle-remote-helper/src/push.rs @@ -43,7 +43,7 @@ pub enum Error { NoKey, /// User tried to delete the canonical branch. #[error("refusing to delete default branch ref '{0}'")] - DeleteForbidden(git::RefString), + DeleteForbidden(git::fmt::RefString), /// Identity document error. #[error("doc: {0}")] Doc(#[from] radicle::identity::doc::DocError), @@ -65,9 +65,6 @@ pub enum Error { /// Git error. #[error("git: {0}")] Git(#[from] git::raw::Error), - /// Git extension error. - #[error("git: {0}")] - GitExt(#[from] git::ext::Error), /// Storage error. #[error(transparent)] Storage(#[from] radicle::storage::Error), @@ -136,9 +133,9 @@ pub enum Error { /// Push command. enum Command { /// Update ref. - Push(git::Refspec), + Push(git::fmt::refspec::Refspec), /// Delete ref. - Delete(git::RefString), + Delete(git::fmt::RefString), } #[derive(Debug, thiserror::Error)] @@ -173,7 +170,7 @@ impl Command { let Some((src, dst)) = s.split_once(':') else { return Err(CommandError::Empty { rev: s.to_string() }); }; - let dst = git::RefString::try_from(dst).map_err(|err| CommandError::Delete { + let dst = git::fmt::RefString::try_from(dst).map_err(|err| CommandError::Delete { rev: dst.to_string(), err, })?; @@ -195,12 +192,12 @@ impl Command { .id() .into(); - Ok(Self::Push(git::Refspec { src, dst, force })) + Ok(Self::Push(git::fmt::refspec::Refspec { src, dst, force })) } } /// Return the destination refname. - fn dst(&self) -> &git::RefStr { + fn dst(&self) -> &git::fmt::RefStr { match self { Self::Push(rs) => rs.dst.as_refstr(), Self::Delete(rs) => rs, @@ -211,30 +208,30 @@ impl Command { enum PushAction { OpenPatch, UpdatePatch { - dst: git::Qualified<'static>, + dst: git::fmt::Qualified<'static>, patch: patch::PatchId, }, PushRef { - dst: git::Qualified<'static>, + dst: git::fmt::Qualified<'static>, }, } impl PushAction { - fn new(dst: &git::RefString) -> Result { + fn new(dst: &git::fmt::RefString) -> Result { if dst == &*rad::PATCHES_REFNAME { Ok(Self::OpenPatch) } else { - let dst = git::Qualified::from_refstr(dst) + let dst = git::fmt::Qualified::from_refstr(dst) .ok_or_else(|| error::PushAction::InvalidRef { refname: dst.clone(), })? .to_owned(); - if let Some(oid) = dst.strip_prefix(git::refname!("refs/heads/patches")) { + if let Some(oid) = dst.strip_prefix(git::fmt::refname!("refs/heads/patches")) { let patch = git::Oid::from_str(oid) - .map_err(|err| error::PushAction::InvalidPatchId { + .map_err(|source| error::PushAction::InvalidPatchId { suffix: oid.to_string(), - source: err, + source, }) .map(patch::PatchId::from)?; Ok(Self::UpdatePatch { dst, patch }) @@ -248,7 +245,7 @@ impl PushAction { /// Run a git push command. pub fn run( mut specs: Vec, - remote: Option, + remote: Option, url: Url, stored: &storage::git::Repository, profile: &Profile, @@ -290,7 +287,7 @@ pub fn run( let identity = stored.identity()?; let project = identity.project()?; let canonical_ref = git::refs::branch(project.default_branch()); - let mut set_canonical_refs: Vec<(git::Qualified, git::canonical::Object)> = + let mut set_canonical_refs: Vec<(git::fmt::Qualified, git::canonical::Object)> = Vec::with_capacity(specs.len()); // Rely on the environment variable `GIT_DIR`. @@ -317,7 +314,7 @@ pub fn run( .map(|_| None) .map_err(Error::from) } - Command::Push(git::Refspec { src, dst, force }) => { + Command::Push(git::fmt::refspec::Refspec { src, dst, force }) => { let patches = crate::patches_mut(profile, stored)?; let action = PushAction::new(dst)?; @@ -373,7 +370,7 @@ pub fn run( // canonical branch. if let Some(canonical) = rules.canonical(dst.clone(), stored) { let object = working - .find_object(**src, None) + .find_object(src.into(), None) .map(|obj| git::canonical::Object::new(&obj))? .ok_or(Error::UnknownObjectType { oid: *src })?; @@ -429,10 +426,10 @@ pub fn run( } match stored.backend.refname_to_id(refname.as_str()) { - Ok(new) if new != *oid => { + Ok(new) if oid != new => { stored.backend.reference( refname.as_str(), - *oid, + oid.into(), true, "set-canonical-reference from git-push (radicle)", )?; @@ -441,7 +438,7 @@ pub fn run( Err(e) if e.code() == git::raw::ErrorCode::NotFound => { stored.backend.reference( refname.as_str(), - *oid, + oid.into(), true, "set-canonical-reference from git-push (radicle)", )?; @@ -505,7 +502,7 @@ fn patch_base( /// [`Drop::drop`]. struct TempPatchRef<'a> { stored: &'a storage::git::Repository, - reference: git::Namespaced<'a>, + reference: git::fmt::Namespaced<'a>, } impl<'a> TempPatchRef<'a> { @@ -539,7 +536,7 @@ impl<'a> Drop for TempPatchRef<'a> { /// Open a new patch. fn patch_open( head: &git::Oid, - upstream: &Option, + upstream: &Option, nid: &NodeId, working: &git::raw::Repository, stored: &storage::git::Repository, @@ -563,7 +560,7 @@ where } let (title, description) = - term::patch::get_create_message(opts.message, &stored.backend, &base, head)?; + term::patch::get_create_message(opts.message, &stored.backend, &base.into(), &head.into())?; let patch = if opts.draft { patches.draft( @@ -607,24 +604,29 @@ where let refname = git::refs::patch(&patch).with_namespace(nid.into()); let _ = stored.raw().reference( refname.as_str(), - **head, + head.into(), true, "Create reference for patch head", )?; if let Some(upstream) = upstream { if let Some(local_branch) = opts.branch.to_branch_name(&patch) { - fn strip_refs_heads(qualified: git::Qualified) -> git::RefString { + fn strip_refs_heads(qualified: git::fmt::Qualified) -> git::fmt::RefString { let (_refs, _heads, x, xs) = qualified.non_empty_components(); std::iter::once(x).chain(xs).collect() } - working.reference(&local_branch, **head, true, "Create local branch for patch")?; + working.reference( + &local_branch, + head.into(), + true, + "Create local branch for patch", + )?; let remote_branch = git::refs::workdir::patch_upstream(&patch); let remote_branch = working.reference( &remote_branch, - **head, + head.into(), true, "Create remote tracking branch for patch", )?; @@ -667,7 +669,7 @@ where #[allow(clippy::too_many_arguments)] fn patch_update( head: &git::Oid, - dst: &git::Qualified, + dst: &git::fmt::Qualified, force: bool, patch_id: patch::PatchId, nid: &NodeId, @@ -703,7 +705,8 @@ where let (latest_id, latest) = patch.latest(); let latest = latest.clone(); - let message = term::patch::get_update_message(opts.message, &stored.backend, &latest, head)?; + let message = + term::patch::get_update_message(opts.message, &stored.backend, &latest, &head.into())?; let dst = dst.with_namespace(nid.into()); push_ref(head, &dst, force, stored.raw(), opts.verbosity)?; @@ -744,7 +747,7 @@ where fn push( src: &git::Oid, - dst: &git::Qualified, + dst: &git::fmt::Qualified, force: bool, nid: &NodeId, working: &git::raw::Repository, @@ -768,7 +771,7 @@ where if let Some(old) = old { let proj = stored.project()?; - let master = &*git::Qualified::from(git::lit::refs_heads(proj.default_branch())); + let master = &*git::fmt::Qualified::from(git::fmt::lit::refs_heads(proj.default_branch())); // If we're pushing to the project's default branch, we want to see if any patches got // merged or reverted, and if so, update the patch COB. @@ -800,8 +803,8 @@ where { // Find all commits reachable from the old OID but not from the new OID. let mut revwalk = stored.revwalk()?; - revwalk.push(*old)?; - revwalk.hide(*new)?; + revwalk.push(old.into())?; + revwalk.hide(new.into())?; // List of commits that have been dropped. let dropped = revwalk @@ -943,7 +946,7 @@ where /// Push a single reference to storage. fn push_ref( src: &git::Oid, - dst: &git::Namespaced, + dst: &git::fmt::Namespaced, force: bool, stored: &git::raw::Repository, verbosity: Verbosity, @@ -951,7 +954,7 @@ fn push_ref( let path = dunce::canonicalize(stored.path())?.display().to_string(); // Nb. The *force* indicator (`+`) is processed by Git tooling before we even reach this code. // This happens during the `list for-push` phase. - let refspec = git::Refspec { src, dst, force }; + let refspec = git::fmt::refspec::Refspec { src, dst, force }; let mut args = vec!["send-pack".to_string()]; diff --git a/crates/radicle-remote-helper/src/push/canonical.rs b/crates/radicle-remote-helper/src/push/canonical.rs index 4073a090..09ab7cc9 100644 --- a/crates/radicle-remote-helper/src/push/canonical.rs +++ b/crates/radicle-remote-helper/src/push/canonical.rs @@ -40,7 +40,7 @@ where /// copy, and that checks that any two commits are related in the graph. /// /// Ensures that the new head and the canonical commit do not diverge. - pub fn quorum(self) -> Result<(git::Qualified<'a>, canonical::Object), QuorumError> { + pub fn quorum(self) -> Result<(git::fmt::Qualified<'a>, canonical::Object), QuorumError> { self.canonical .quorum() .map(|QuorumWithConvergence { quorum, .. }| (quorum.refname, quorum.object)) diff --git a/crates/radicle-remote-helper/src/push/error.rs b/crates/radicle-remote-helper/src/push/error.rs index e65be9e1..47222f7d 100644 --- a/crates/radicle-remote-helper/src/push/error.rs +++ b/crates/radicle-remote-helper/src/push/error.rs @@ -37,10 +37,10 @@ pub struct HeadsDiverge { #[derive(Debug, Error)] pub enum PushAction { #[error("invalid reference {refname}, expected qualified reference starting with `refs/`")] - InvalidRef { refname: git::RefString }, - #[error("found refs/heads/patches/{suffix} where {suffix} was an invalid Patch ID")] + InvalidRef { refname: git::fmt::RefString }, + #[error("found refs/heads/patches/{suffix} where {suffix} was an invalid Patch ID: {source}")] InvalidPatchId { suffix: String, - source: git::raw::Error, + source: radicle::git::ParseOidError, }, } diff --git a/crates/radicle/CHANGELOG.md b/crates/radicle/CHANGELOG.md index e4a0623e..7e1b130f 100644 --- a/crates/radicle/CHANGELOG.md +++ b/crates/radicle/CHANGELOG.md @@ -18,6 +18,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Re-exports from `git2` at `radicle::git::raw` were limited, using + the heartwood workspace as a filter. Dependents that require members that + are not exported anymore will have to depend on `git2` directly. +- Some re-exports from `git-ref-format-core` were moved from `radicle::git` + to `radicle::fmt`. +- The crate now re-exports `radicle::git::Oid` from a new `radicle-oid` crate, + in an effort to decrease dependence on `git2` via `radicle-git-ext`. This + new object identifier type does not implement `Deref` anymore. Use `Into` + to convert to a `git2::Oid` as necessary. +- Re-exports of `radicle-git-ext` were removed, as this dependency is removed. + Instead of `radicle_git_ext::Error`, use `git2::Error` (re-exported as + `radicle::git::raw::Error`) together with the new extension trait + `radicle::git::raw::ErrorExt`. + ### Deprecated - `radicle::node::Handle::announce_refs` is deprecated in favor of diff --git a/crates/radicle/Cargo.toml b/crates/radicle/Cargo.toml index 4135fc49..ef646bd6 100644 --- a/crates/radicle/Cargo.toml +++ b/crates/radicle/Cargo.toml @@ -11,8 +11,9 @@ rust-version.workspace = true [features] default = [] -test = ["tempfile", "qcheck", "radicle-crypto/test"] +test = ["tempfile", "qcheck", "radicle-crypto/test", "radicle-cob/test"] logger = ["colored", "chrono"] +schemars = ["radicle-oid/schemars", "dep:schemars"] [dependencies] amplify = { workspace = true, features = ["std"] } @@ -32,9 +33,10 @@ log = { workspace = true, features = ["std"] } multibase = { workspace = true } nonempty = { workspace = true, features = ["serialize"] } qcheck = { workspace = true, optional = true } -radicle-cob = { workspace = true } +radicle-cob = { workspace = true, features = ["git2"] } radicle-crypto = { workspace = true, features = ["git-ref-format-core", "ssh", "sqlite", "cyphernet"] } -radicle-git-ext = { workspace = true, features = ["serde"] } +radicle-git-ref-format = { workspace = true, features = ["macro", "serde"] } +radicle-oid = { workspace = true, features = ["git2", "serde", "std", "sha1"] } radicle-ssh = { workspace = true } schemars = { workspace = true, optional = true, features = ["derive", "std"] } serde = { workspace = true, features = ["derive"] } @@ -58,6 +60,7 @@ jsonschema = { version = "0.30", default-features = false } pretty_assertions = { workspace = true } qcheck = { workspace = true } qcheck-macros = { workspace = true } -radicle-cob = { workspace = true, features = ["stable-commit-ids"] } +radicle-cob = { workspace = true, features = ["stable-commit-ids", "test"] } radicle-crypto = { workspace = true, features = ["test"] } +radicle-git-metadata = { workspace = true } tempfile = { workspace = true } diff --git a/crates/radicle/src/cob.rs b/crates/radicle/src/cob.rs index 64cae77c..7892bd41 100644 --- a/crates/radicle/src/cob.rs +++ b/crates/radicle/src/cob.rs @@ -13,6 +13,9 @@ pub mod thread; #[cfg(test)] pub mod test; +#[cfg(test)] +pub use radicle_cob::stable; + pub use cache::{migrate, MigrateCallback}; pub use common::*; pub use op::{ActorId, Op}; @@ -21,7 +24,7 @@ pub use radicle_cob::{ CollaborativeObject, Contents, Create, Embed, Entry, Evaluate, History, Manifest, ObjectId, Store, TypeName, Update, Updated, Version, }; -pub use radicle_cob::{create, get, git, list, remove, update}; +pub use radicle_cob::{create, get, list, remove, update}; /// The exact identifier for a particular COB. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)] @@ -66,17 +69,17 @@ impl TypedId { self.type_name == *identity::TYPENAME } - /// Parse a [`crate::git::Namespaced`] refname into a [`TypedId`]. + /// Parse a [`crate::git::fmt::Namespaced`] refname into a [`TypedId`]. /// /// All namespaces are stripped before parsing the suffix for the /// [`TypedId`] (see [`TypedId::from_qualified`]). pub fn from_namespaced( - n: &crate::git::Namespaced, + n: &crate::git::fmt::Namespaced, ) -> Result, ParseIdentifierError> { Self::from_qualified(&n.strip_namespace_recursive()) } - /// Parse a [`crate::git::Qualified`] refname into a [`TypedId`]. + /// Parse a [`crate::git::fmt::Qualified`] refname into a [`TypedId`]. /// /// The refname is expected to be of the form: /// `refs/cobs//` @@ -87,7 +90,9 @@ impl TypedId { /// /// This will fail if the refname is of the correct form, but the /// type name or object id fail to parse. - pub fn from_qualified(q: &crate::git::Qualified) -> Result, ParseIdentifierError> { + pub fn from_qualified( + q: &crate::git::fmt::Qualified, + ) -> Result, ParseIdentifierError> { match q.non_empty_iter() { ("refs", "cobs", type_name, mut id) => { let Some(id) = id.next() else { diff --git a/crates/radicle/src/cob/common.rs b/crates/radicle/src/cob/common.rs index 7bb7289b..91df7396 100644 --- a/crates/radicle/src/cob/common.rs +++ b/crates/radicle/src/cob/common.rs @@ -355,7 +355,7 @@ impl From for Uri { } } -impl TryFrom<&Uri> for Oid { +impl TryFrom<&Uri> for crate::git::raw::Oid { type Error = Uri; fn try_from(value: &Uri) -> Result { @@ -368,6 +368,14 @@ impl TryFrom<&Uri> for Oid { } } +impl TryFrom<&Uri> for crate::git::Oid { + type Error = Uri; + + fn try_from(value: &Uri) -> Result { + crate::git::raw::Oid::try_from(value).map(crate::git::Oid::from) + } +} + impl std::fmt::Display for Uri { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) diff --git a/crates/radicle/src/cob/external.rs b/crates/radicle/src/cob/external.rs index 69bd49ec..9014b46f 100644 --- a/crates/radicle/src/cob/external.rs +++ b/crates/radicle/src/cob/external.rs @@ -225,7 +225,7 @@ impl Evaluate for External { Self::from_root(Op::try_from(entry)?, store) } - fn apply<'a, I: Iterator>( + fn apply<'a, I: Iterator>( &mut self, entry: &radicle_cob::Entry, concurrent: I, diff --git a/crates/radicle/src/cob/identity.rs b/crates/radicle/src/cob/identity.rs index 37eab954..1d0e81fd 100644 --- a/crates/radicle/src/cob/identity.rs +++ b/crates/radicle/src/cob/identity.rs @@ -4,11 +4,11 @@ use std::{fmt, ops::Deref, str::FromStr}; use crypto::{PublicKey, Signature}; use radicle_cob::{Embed, ObjectId, TypeName}; -use radicle_git_ext as git_ext; -use radicle_git_ext::Oid; use serde::{Deserialize, Serialize}; use thiserror::Error; +use crate::git; +use crate::git::Oid; use crate::identity::doc::Doc; use crate::node::device::Device; use crate::node::NodeId; @@ -126,9 +126,7 @@ pub enum ApplyError { #[error("document does not contain any changes to current identity")] DocUnchanged, #[error("git: {0}")] - Git(#[from] crate::git::raw::Error), - #[error("git: {0}")] - GitExt(#[from] git_ext::Error), + Git(#[from] git::raw::Error), #[error("identity document error: {0}")] Doc(#[from] DocError), } @@ -1323,16 +1321,15 @@ mod test { .unwrap(); bob.repo.fetch(alice); - let a3 = cob::git::stable::with_advanced_timestamp(|| { + let a3 = cob::stable::with_advanced_timestamp(|| { alice_identity.redact(a2, &alice.signer).unwrap() }); assert!(alice_identity.revision(&a1).is_some()); assert_eq!(alice_identity.timeline, vec![a0, a1, a2, a3]); let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap(); - let b1 = cob::git::stable::with_advanced_timestamp(|| { - bob_identity.accept(&a2, &bob.signer).unwrap() - }); + let b1 = + cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap()); assert_eq!(bob_identity.timeline, vec![a0, a1, a2, b1]); assert_eq!(bob_identity.revision(&a2).unwrap().state, State::Accepted); @@ -1381,15 +1378,14 @@ mod test { eve.repo.fetch(bob); let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap(); - let b1 = cob::git::stable::with_advanced_timestamp(|| { - bob_identity.accept(&a2, &bob.signer).unwrap() - }); + let b1 = + cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap()); assert_eq!(bob_identity.current, a2); let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap(); let mut eve_doc = eve_identity.doc().clone().edit(); eve_doc.visibility = Visibility::private([eve.signer.public_key().into()]); - let e1 = cob::git::stable::with_advanced_timestamp(|| { + let e1 = cob::stable::with_advanced_timestamp(|| { eve_identity .update( cob::Title::new("Change visibility").unwrap(), @@ -1458,15 +1454,13 @@ mod test { // Bob accepts alice's revision. let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap(); - let b1 = cob::git::stable::with_advanced_timestamp(|| { - bob_identity.accept(&a2, &bob.signer).unwrap() - }); + let b1 = + cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap()); // Eve rejects the revision, not knowing. let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap(); - let e1 = cob::git::stable::with_advanced_timestamp(|| { - eve_identity.reject(a2, &eve.signer).unwrap() - }); + let e1 = + cob::stable::with_advanced_timestamp(|| eve_identity.reject(a2, &eve.signer).unwrap()); assert!(eve_identity.revision(&a2).unwrap().is_active()); // Then she submits a new revision. @@ -1576,7 +1570,7 @@ mod test { assert_eq!(eve_identity.revision(&e1).unwrap().state, State::Active); alice_identity.reload().unwrap(); - let a2 = cob::git::stable::with_advanced_timestamp(|| { + let a2 = cob::stable::with_advanced_timestamp(|| { alice_identity.accept(&b1, &alice.signer).unwrap() }); diff --git a/crates/radicle/src/cob/issue.rs b/crates/radicle/src/cob/issue.rs index f771301c..1958bbf9 100644 --- a/crates/radicle/src/cob/issue.rs +++ b/crates/radicle/src/cob/issue.rs @@ -1765,7 +1765,7 @@ mod test { .unwrap(); // Comments require references, so adding two of them to the same transaction errors. - let mut tx: Transaction = + let mut tx: Transaction = Transaction::::default(); tx.comment("First reply", *issue.id, vec![]).unwrap(); let err = tx.comment("Second reply", *issue.id, vec![]).unwrap_err(); diff --git a/crates/radicle/src/cob/op.rs b/crates/radicle/src/cob/op.rs index bd6cd02e..b9cbbd3b 100644 --- a/crates/radicle/src/cob/op.rs +++ b/crates/radicle/src/cob/op.rs @@ -8,9 +8,10 @@ use radicle_crypto::PublicKey; use crate::cob; use crate::cob::Timestamp; +use crate::git; +use crate::identity; use crate::identity::DocAt; use crate::storage::ReadRepository; -use crate::{git, identity}; /// The author of an [`Op`]. pub type ActorId = PublicKey; diff --git a/crates/radicle/src/cob/patch.rs b/crates/radicle/src/cob/patch.rs index 64422550..65f6304b 100644 --- a/crates/radicle/src/cob/patch.rs +++ b/crates/radicle/src/cob/patch.rs @@ -130,7 +130,7 @@ pub enum Error { Payload(#[from] PayloadError), /// Git error. #[error("git: {0}")] - Git(#[from] git::ext::Error), + Git(#[from] git::raw::Error), /// Store error. #[error("store: {0}")] Store(#[from] store::Error), @@ -601,12 +601,15 @@ impl Patch { } /// Get the merge base of this patch. - pub fn merge_base(&self, repo: &R) -> Result { + pub fn merge_base( + &self, + repo: &R, + ) -> Result { repo.merge_base(self.base(), self.head()) } /// Get the commit range of this patch. - pub fn range(&self) -> Result<(git::Oid, git::Oid), git::ext::Error> { + pub fn range(&self) -> Result<(crate::git::Oid, crate::git::Oid), crate::git::raw::Error> { Ok((*self.base(), *self.head())) } @@ -3334,7 +3337,7 @@ mod test { }, &alice, ); - let patch = Patch::from_history(&h0, &repo).unwrap(); + let patch: Patch = Patch::from_history(&h0, &repo).unwrap(); assert_eq!(patch.revisions().count(), 2); let mut h1 = h0.clone(); diff --git a/crates/radicle/src/cob/store.rs b/crates/radicle/src/cob/store.rs index cc3b1c36..c1695b0c 100644 --- a/crates/radicle/src/cob/store.rs +++ b/crates/radicle/src/cob/store.rs @@ -125,7 +125,7 @@ pub enum Error { Git(git::raw::Error), #[error("failed to find reference '{name}': {err}")] RefLookup { - name: git::RefString, + name: git::fmt::RefString, #[source] err: git::raw::Error, }, diff --git a/crates/radicle/src/cob/stream/iter.rs b/crates/radicle/src/cob/stream/iter.rs index 66d3d7ae..767c0cab 100644 --- a/crates/radicle/src/cob/stream/iter.rs +++ b/crates/radicle/src/cob/stream/iter.rs @@ -3,7 +3,9 @@ use std::marker::PhantomData; use serde::Deserialize; use crate::cob::{Op, TypeName}; -use crate::git::{self, Oid, PatternString}; +use crate::git; +use crate::git::fmt::refspec::PatternString; +use crate::git::Oid; use super::error; use super::CobRange; @@ -83,7 +85,7 @@ impl Walk { match self.until { Until::Tip(tip) => walk.push_range(&format!("{}..{}", self.from, tip))?, Until::Glob(glob) => { - walk.push(*self.from)?; + walk.push(self.from.into())?; walk.push_glob(glob.as_str())? } } @@ -103,7 +105,7 @@ impl<'a> Iterator for WalkIter<'a> { // N.b. ensure that we start using the `from` commit and use the revwalk // after that. if let Some(from) = self.from.take() { - return Some(self.repo.find_commit(*from)); + return Some(self.repo.find_commit(from.into())); } let oid = self.inner.next()?; Some(oid.and_then(|oid| self.repo.find_commit(oid))) @@ -132,7 +134,7 @@ where let commit = self.walk.next()?; match commit { Ok(commit) => { - let entry = git::Oid::from(commit.id()); + let entry = crate::git::Oid::from(commit.id()); // N.b. mark this commit as seen, so that it is not walked again self.walk.inner.hide(commit.id()).ok(); // Skip any Op that do not match the manifest @@ -155,7 +157,7 @@ impl<'a, A> OpsIter<'a, A> { /// Load the `Op` for the given `entry`, ensuring that manifest matches with /// the expected manifest. - fn load(&self, entry: git::Oid) -> Result>, error::Ops> + fn load(&self, entry: crate::git::Oid) -> Result>, error::Ops> where A: for<'de> Deserialize<'de>, { diff --git a/crates/radicle/src/cob/test.rs b/crates/radicle/src/cob/test.rs index 30fb221f..8ecad448 100644 --- a/crates/radicle/src/cob/test.rs +++ b/crates/radicle/src/cob/test.rs @@ -12,10 +12,6 @@ use crate::cob::store::encoding; use crate::cob::{patch, Title}; use crate::cob::{Entry, History, Manifest, Timestamp, Version}; use crate::crypto::Signer; -use crate::git; -use crate::git::ext::author::Author; -use crate::git::ext::commit::headers::Headers; -use crate::git::ext::commit::{trailers::OwnedTrailer, Commit}; use crate::git::Oid; use crate::node::device::Device; use crate::prelude::Did; @@ -101,7 +97,7 @@ where self.history.merge(other.history); } - pub fn commit(&mut self, action: &T::Action, signer: &G) -> git::ext::Oid { + pub fn commit(&mut self, action: &T::Action, signer: &G) -> crate::git::Oid { let timestamp = self.time; let tips = self.tips(); let revision = arbitrary::oid(); @@ -182,7 +178,8 @@ impl Actor { "nonce": fastrand::u64(..), })) .unwrap(); - let oid = git::raw::Oid::hash_object(git::raw::ObjectType::Blob, &data).unwrap(); + let oid = + crate::git::raw::Oid::hash_object(crate::git::raw::ObjectType::Blob, &data).unwrap(); let id = oid.into(); let author = *self.signer.public_key(); let actions = NonEmpty::from_vec(actions).unwrap(); @@ -226,8 +223,8 @@ impl Actor { &mut self, title: Title, description: impl ToString, - base: git::Oid, - oid: git::Oid, + base: crate::git::Oid, + oid: crate::git::Oid, repo: &R, ) -> Result { Patch::from_root( @@ -252,21 +249,26 @@ impl Actor { /// /// Doesn't encode in the same way as we do in production, but attempts to include the same data /// that feeds into the hash entropy, so that changing any input will change the resulting oid. -pub fn encoded( +fn encoded( action: &T::Action, timestamp: Timestamp, parents: impl IntoIterator, signer: &G, -) -> (Vec, git::ext::Oid) { +) -> (Vec, crate::git::Oid) { + use radicle_git_metadata::{ + author::{Author, Time}, + commit::{headers::Headers, trailers::OwnedTrailer, CommitData}, + }; + let data = encoding::encode(action).unwrap(); - let oid = git::raw::Oid::hash_object(git::raw::ObjectType::Blob, &data).unwrap(); - let parents = parents.into_iter().map(|o| *o); + let oid = crate::git::raw::Oid::hash_object(crate::git::raw::ObjectType::Blob, &data).unwrap(); + let parents = parents.into_iter().map(|o| o.into()); let author = Author { name: "radicle".to_owned(), email: signer.public_key().to_human(), - time: git_ext::author::Time::new(timestamp.as_secs() as i64, 0), + time: Time::new(timestamp.as_secs() as i64, 0), }; - let commit = Commit::new::<_, _, OwnedTrailer>( + let commit = CommitData::::new::<_, _, OwnedTrailer>( oid, parents, author.clone(), @@ -277,7 +279,9 @@ pub fn encoded( ) .to_string(); - let hash = git::raw::Oid::hash_object(git::raw::ObjectType::Commit, commit.as_bytes()).unwrap(); + let hash = + crate::git::raw::Oid::hash_object(crate::git::raw::ObjectType::Commit, commit.as_bytes()) + .unwrap(); (data, hash.into()) } diff --git a/crates/radicle/src/git.rs b/crates/radicle/src/git.rs index 676e990f..45f1b917 100644 --- a/crates/radicle/src/git.rs +++ b/crates/radicle/src/git.rs @@ -5,33 +5,24 @@ use std::io; use std::path::Path; use std::process::Command; use std::str::FromStr; -use std::sync::LazyLock; -use git_ext::ref_format as format; +pub use radicle_oid::{str::ParseOidError, Oid}; + +pub extern crate radicle_git_ref_format as fmt; use crate::collections::RandomMap; use crate::crypto::PublicKey; use crate::node::Alias; use crate::rad; -use crate::storage; use crate::storage::refs::Refs; use crate::storage::RemoteId; -pub use ext::Error; -pub use ext::NotFound; -pub use ext::Oid; -pub use git_ext::ref_format as fmt; -pub use git_ext::ref_format::{ - component, lit, name, qualified, refname, refspec, - refspec::{PatternStr, PatternString, Refspec}, - Component, Namespaced, Qualified, RefStr, RefString, -}; -pub use radicle_git_ext as ext; -pub use storage::git::transport::local::Url; -pub use storage::BranchName; +pub use crate::storage::git::transport::local::Url; use raw::ErrorExt as _; +pub type BranchName = crate::git::fmt::RefString; + /// Default port of the `git` transport protocol. pub const PROTOCOL_PORT: u16 = 9418; /// Minimum required git version. @@ -160,16 +151,16 @@ pub enum RefError { #[error("ref name is not valid UTF-8")] InvalidName, #[error("unexpected unqualified ref: {0}")] - Unqualified(RefString), + Unqualified(fmt::RefString), #[error("invalid ref format: {0}")] - Format(#[from] format::Error), + Format(#[from] fmt::Error), #[error("reference has no target")] NoTarget, #[error("expected ref to begin with 'refs/namespaces' but found '{0}'")] - MissingNamespace(format::RefString), + MissingNamespace(fmt::RefString), #[error("ref name contains invalid namespace identifier '{name}'")] InvalidNamespace { - name: format::RefString, + name: fmt::RefString, #[source] err: Box, }, @@ -186,9 +177,13 @@ pub enum ListRefsError { } pub mod refs { - use super::*; + use std::sync::LazyLock; + use radicle_cob as cob; + use super::fmt::*; + use super::*; + /// Try to get a qualified reference from a generic reference. pub fn qualified_from<'a>(r: &'a raw::Reference) -> Result<(Qualified<'a>, Oid), RefError> { let name = r.name().ok_or(RefError::InvalidName)?; @@ -214,35 +209,28 @@ pub mod refs { /// pub fn patch<'a>(object_id: &cob::ObjectId) -> Qualified<'a> { Qualified::from_components( - name::component!("heads"), - name::component!("patches"), + component!("heads"), + component!("patches"), Some(object_id.into()), ) } pub mod storage { - use format::{ - lit, - name::component, - refspec::{self, PatternString}, - }; - use super::*; /// Where the repo's identity document is stored. /// /// `refs/rad/id` /// - pub static IDENTITY_BRANCH: LazyLock = LazyLock::new(|| { - Qualified::from_components(name::component!("rad"), name::component!("id"), None) - }); + pub static IDENTITY_BRANCH: LazyLock = + LazyLock::new(|| Qualified::from_components(component!("rad"), component!("id"), None)); /// Where the repo's identity root document is stored. /// /// `refs/rad/root` /// pub static IDENTITY_ROOT: LazyLock = LazyLock::new(|| { - Qualified::from_components(name::component!("rad"), name::component!("root"), None) + Qualified::from_components(component!("rad"), component!("root"), None) }); /// Where the project's signed references are stored. @@ -250,7 +238,7 @@ pub mod refs { /// `refs/rad/sigrefs` /// pub static SIGREFS_BRANCH: LazyLock = LazyLock::new(|| { - Qualified::from_components(name::component!("rad"), name::component!("sigrefs"), None) + Qualified::from_components(component!("rad"), component!("sigrefs"), None) }); /// The set of special references used in the Heartwood protocol. @@ -341,8 +329,8 @@ pub mod refs { /// /// `refs/namespaces/*/refs/cobs//` /// - pub fn cobs(typename: &cob::TypeName, object_id: &cob::ObjectId) -> PatternString { - refspec::pattern!("refs/namespaces/*") + pub fn cobs(typename: &cob::TypeName, object_id: &cob::ObjectId) -> refspec::PatternString { + pattern!("refs/namespaces/*") .join(refname!("refs/cobs")) .join(Component::from(typename)) .join(Component::from(object_id)) @@ -390,8 +378,11 @@ pub mod refs { /// /// `refs/namespaces/*/refs/drafts/cobs//` /// - pub fn cobs(typename: &cob::TypeName, object_id: &cob::ObjectId) -> PatternString { - refspec::pattern!("refs/namespaces/*") + pub fn cobs( + typename: &cob::TypeName, + object_id: &cob::ObjectId, + ) -> refspec::PatternString { + pattern!("refs/namespaces/*") .join(refname!("refs/drafts/cobs")) .join(Component::from(typename)) .join(Component::from(object_id)) @@ -423,7 +414,6 @@ pub mod refs { pub mod workdir { use super::*; - use format::name::component; /// Create a [`RefString`] that corresponds to `refs/heads/`. pub fn branch(branch: &RefStr) -> RefString { @@ -483,7 +473,7 @@ pub fn remote_refs(url: &Url) -> Result, ListRefsError Ok(remotes) } -/// Parse a [`format::Qualified`] reference string while expecting the reference +/// Parse a [`fmt::Qualified`] reference string while expecting the reference /// to start with `refs/namespaces`. If the namespace is not present, then an /// error will be returned. /// @@ -498,7 +488,7 @@ pub fn remote_refs(url: &Url) -> Result, ListRefsError /// The `T` can be specified when calling the function. For example, if you /// wanted to parse the namespace as a `PublicKey`, then you would the function /// like so, `parse_ref_namespaced::(s)`. -pub fn parse_ref_namespaced(s: &str) -> Result<(T, format::Qualified), RefError> +pub fn parse_ref_namespaced(s: &str) -> Result<(T, fmt::Qualified), RefError> where T: FromStr, T::Err: std::error::Error + Send + Sync + 'static, @@ -510,7 +500,7 @@ where } } -/// Parse a [`format::Qualified`] reference string. It will optionally return +/// Parse a [`fmt::Qualified`] reference string. It will optionally return /// the namespace, if present. /// /// The qualified form could be of the form: `refs/heads/main`, @@ -527,15 +517,15 @@ where /// The `T` can be specified when calling the function. For example, if you /// wanted to parse the namespace as a `PublicKey`, then you would the function /// like so, `parse_ref::(s)`. -pub fn parse_ref(s: &str) -> Result<(Option, format::Qualified), RefError> +pub fn parse_ref(s: &str) -> Result<(Option, fmt::Qualified), RefError> where T: FromStr, T::Err: std::error::Error + Send + Sync + 'static, { - let input = format::RefStr::try_from_str(s)?; + let input = fmt::RefStr::try_from_str(s)?; match input.to_namespaced() { None => { - let refname = Qualified::from_refstr(input) + let refname = fmt::Qualified::from_refstr(input) .ok_or_else(|| RefError::Unqualified(input.to_owned()))?; Ok((None, refname)) @@ -573,7 +563,7 @@ pub fn initial_commit<'a>( pub fn commit<'a>( repo: &'a raw::Repository, parent: &'a raw::Commit, - target: &RefStr, + target: &fmt::RefStr, message: &str, sig: &raw::Signature, tree: &raw::Tree, @@ -588,7 +578,7 @@ pub fn commit<'a>( pub fn empty_commit<'a>( repo: &'a raw::Repository, parent: &'a raw::Commit, - target: &RefStr, + target: &fmt::RefStr, message: &str, sig: &raw::Signature, ) -> Result, raw::Error> { @@ -611,7 +601,7 @@ pub fn write_tree<'r>( path: &Path, bytes: &[u8], repo: &'r raw::Repository, -) -> Result, Error> { +) -> Result, raw::Error> { let blob_id = repo.blob(bytes)?; let mut builder = repo.treebuilder(None)?; builder.insert(path, blob_id, 0o100_644)?; @@ -697,7 +687,7 @@ pub fn fetch(repo: &raw::Repository, remote: &str) -> Result<(), raw::Error> { pub fn push<'a>( repo: &raw::Repository, remote: &str, - refspecs: impl IntoIterator, &'a Qualified<'a>)>, + refspecs: impl IntoIterator, &'a fmt::Qualified<'a>)>, ) -> Result<(), raw::Error> { let refspecs = refspecs .into_iter() diff --git a/crates/radicle/src/git/canonical.rs b/crates/radicle/src/git/canonical.rs index aab1d20e..56702310 100644 --- a/crates/radicle/src/git/canonical.rs +++ b/crates/radicle/src/git/canonical.rs @@ -19,11 +19,12 @@ use std::fmt; use std::marker::PhantomData; use std::ops::ControlFlow; -use git_ext::ref_format::Namespaced; +use crate::git::fmt::Namespaced; use crate::prelude::Did; -use super::{Oid, Qualified}; +use super::fmt::Qualified; +use crate::git::Oid; /// A marker for the initial state of [`Canonical`], after construction using /// [`Canonical::new`]. @@ -510,20 +511,19 @@ mod tests { /// Test helper to construct a Canonical and get the quorum fn quorum( - heads: &[git::raw::Oid], + heads: &[crate::git::Oid], threshold: usize, - repo: &git::raw::Repository, + repo: &crate::git::raw::Repository, ) -> Result { - let refname = - git::refs::branch(git_ext::ref_format::RefStr::try_from_str("master").unwrap()); + let refname = git::refs::branch(crate::git::fmt::RefStr::try_from_str("master").unwrap()); let mut delegates = Vec::new(); for (i, head) in heads.iter().enumerate() { let signer = Device::mock_from_seed([(i + 1) as u8; 32]); let did = Did::from(signer.public_key()); delegates.push(did); - let ns = git::Component::from(signer.public_key()); - repo.reference(refname.with_namespace(ns).as_str(), *head, true, "") + let ns = git::fmt::Component::from(signer.public_key()); + repo.reference(refname.with_namespace(ns).as_str(), head.into(), true, "") .unwrap(); } @@ -557,18 +557,18 @@ mod tests { fn test_quorum_properties() { let tmp = tempfile::tempdir().unwrap(); let (repo, c0) = fixtures::repository(tmp.path()); - let c0: git::Oid = c0.into(); - let a1 = fixtures::commit("A1", &[*c0], &repo); - let a2 = fixtures::commit("A2", &[*a1], &repo); - let d1 = fixtures::commit("D1", &[*c0], &repo); - let c1 = fixtures::commit("C1", &[*c0], &repo); - let c2 = fixtures::commit("C2", &[*c1], &repo); - let b2 = fixtures::commit("B2", &[*c1], &repo); - let a1 = fixtures::commit("A1", &[*c0], &repo); - let m1 = fixtures::commit("M1", &[*c2, *b2], &repo); - let m2 = fixtures::commit("M2", &[*a1, *b2], &repo); + let c0: crate::git::Oid = c0.into(); + let a1 = fixtures::commit("A1", &[c0.into()], &repo); + let a2 = fixtures::commit("A2", &[a1.into()], &repo); + let d1 = fixtures::commit("D1", &[c0.into()], &repo); + let c1 = fixtures::commit("C1", &[c0.into()], &repo); + let c2 = fixtures::commit("C2", &[c1.into()], &repo); + let b2 = fixtures::commit("B2", &[c1.into()], &repo); + let a1 = fixtures::commit("A1", &[c0.into()], &repo); + let m1 = fixtures::commit("M1", &[c2.into(), b2.into()], &repo); + let m2 = fixtures::commit("M2", &[a1.into(), b2.into()], &repo); let mut rng = fastrand::Rng::new(); - let choices = [*c0, *c1, *c2, *b2, *a1, *a2, *d1, *m1, *m2]; + let choices = [c0, c1, c2, b2, a1, a2, d1, m1, m2]; for _ in 0..100 { let count = rng.usize(1..=choices.len()); @@ -591,11 +591,10 @@ mod tests { fn test_quorum_different_types() { let tmp = tempfile::tempdir().unwrap(); let (repo, c0) = fixtures::repository(tmp.path()); - let c0: git::Oid = c0.into(); - let t0 = fixtures::tag("v1", "", *c0, &repo); + let t0 = fixtures::tag("v1", "", c0, &repo); assert_matches!( - quorum(&[*c0, *t0], 1, &repo), + quorum(&[c0.into(), t0], 1, &repo), Err(QuorumError::DifferentTypes { .. }) ); } diff --git a/crates/radicle/src/git/canonical/effects.rs b/crates/radicle/src/git/canonical/effects.rs index 54ca20d5..de70f9d5 100644 --- a/crates/radicle/src/git/canonical/effects.rs +++ b/crates/radicle/src/git/canonical/effects.rs @@ -1,8 +1,9 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::git; +use crate::git::fmt::Qualified; use crate::git::raw::ErrorExt as _; -use crate::git::{Oid, Qualified}; +use crate::git::Oid; use crate::prelude::Did; use super::{FoundObjects, GraphAheadBehind, MergeBase, Object}; @@ -40,7 +41,7 @@ pub enum FindObjectsError { }, #[error("failed to find reference {refname} due to: {source}")] FindReference { - refname: git::Namespaced<'static>, + refname: git::fmt::Namespaced<'static>, source: Box, }, #[error("failed to find objects")] @@ -60,7 +61,7 @@ impl FindObjectsError { } } - pub fn find_reference(refname: git::Namespaced<'static>, err: E) -> Self + pub fn find_reference(refname: git::fmt::Namespaced<'static>, err: E) -> Self where E: std::error::Error + Send + Sync + 'static, { @@ -170,7 +171,7 @@ pub struct GraphDescendant { impl FindMergeBase for git::raw::Repository { fn merge_base(&self, a: Oid, b: Oid) -> Result { - self.merge_base(*a, *b) + self.merge_base(a.into(), b.into()) .map_err(|err| MergeBaseError { a, b, @@ -190,7 +191,7 @@ impl Ancestry for git::raw::Repository { commit: Oid, upstream: Oid, ) -> Result { - self.graph_ahead_behind(*commit, *upstream) + self.graph_ahead_behind(commit.into(), upstream.into()) .map_err(|err| GraphDescendant { commit, upstream, @@ -228,7 +229,7 @@ impl FindObjects for git::raw::Repository { log::warn!(target: "radicle", "Missing target for reference `{name}`"); continue; }; - let object = match self.find_object(*oid, None) { + let object = match self.find_object(oid.into(), None) { Ok(object) => Object::new(&object).ok_or_else(|| { FindObjectsError::invalid_object_type( *did, diff --git a/crates/radicle/src/git/canonical/rules.rs b/crates/radicle/src/git/canonical/rules.rs index abfbb12a..9e32928c 100644 --- a/crates/radicle/src/git/canonical/rules.rs +++ b/crates/radicle/src/git/canonical/rules.rs @@ -22,9 +22,9 @@ use thiserror::Error; use crate::git; use crate::git::canonical; use crate::git::canonical::Canonical; +use crate::git::fmt::refspec::QualifiedPattern; +use crate::git::fmt::Qualified; use crate::git::fmt::{refname, RefString}; -use crate::git::refspec::QualifiedPattern; -use crate::git::Qualified; use crate::identity::{doc, Did}; const ASTERISK: char = '*'; @@ -214,7 +214,7 @@ impl Ord for Pattern { } } - use git::refspec::Component; + use git::fmt::refspec::Component; fn cmp_component(lhs: Component<'_>, rhs: Component<'_>) -> ComponentOrdering { let (l, r) = (lhs.as_str(), rhs.as_str()); @@ -401,7 +401,10 @@ impl ValidRule { /// # Errors /// /// If the `name` reference begins with `refs/rad`. - pub fn default_branch(did: Did, name: &git::RefStr) -> Result<(Pattern, Self), PatternError> { + pub fn default_branch( + did: Did, + name: &git::fmt::RefStr, + ) -> Result<(Pattern, Self), PatternError> { let pattern = Pattern::try_from(git::refs::branch(name).to_owned())?; let rule = Self { allow: ResolvedDelegates::Delegates(doc::Delegates::from(did)), @@ -732,9 +735,7 @@ pub enum ValidationError { #[derive(Debug, Error)] pub enum CanonicalError { #[error(transparent)] - Git(#[from] git::raw::Error), - #[error(transparent)] - References(#[from] git::ext::Error), + Git(#[from] crate::git::raw::Error), } #[cfg(test)] @@ -746,8 +747,8 @@ mod tests { use crate::crypto::{test::signer::MockSigner, Signer}; use crate::git; - use crate::git::refspec::qualified_pattern; - use crate::git::RefString; + use crate::git::fmt::qualified_pattern; + use crate::git::fmt::RefString; use crate::identity::doc::Doc; use crate::identity::Visibility; use crate::node::device::Device; @@ -782,7 +783,7 @@ mod tests { fn tag(name: RefString, head: git::raw::Oid, repo: &git::raw::Repository) -> git::Oid { let commit = fixtures::commit(name.as_str(), &[head], repo); - let target = repo.find_object(*commit, None).unwrap(); + let target = repo.find_object(commit.into(), None).unwrap(); let tagger = repo.signature().unwrap(); repo.tag(name.as_str(), &target, &tagger, name.as_str(), false) .unwrap() @@ -1118,7 +1119,7 @@ mod tests { &repo, "heartwood".try_into().unwrap(), "Radicle Heartwood Protocol & Stack", - git::refname!("master"), + git::fmt::refname!("master"), Visibility::default(), &delegate, &storage, @@ -1132,20 +1133,20 @@ mod tests { // Create tags and keep track of their OIDs // // follows the `refs/tags/release/candidates/*` rule - let failing_tag = git::refname!("release/candidates/v1.0"); + let failing_tag = git::fmt::refname!("release/candidates/v1.0"); let tags = [ // follows the `refs/tags/*` rule - git::refname!("v1.0"), + git::fmt::refname!("v1.0"), // follows the `refs/tags/release/*` rule - git::refname!("release/v1.0"), + git::fmt::refname!("release/v1.0"), failing_tag.clone(), // follows the `refs/tags/*` rule - git::refname!("qa/v1.0"), + git::fmt::refname!("qa/v1.0"), ] .into_iter() .map(|name| { ( - git::lit::refs_tags(name.clone()).into(), + git::fmt::lit::refs_tags(name.clone()).into(), tag(name, head, &repo), ) }) @@ -1156,20 +1157,20 @@ mod tests { &rad::REMOTE_NAME, [ ( - &git::qualified!("refs/tags/v1.0"), - &git::qualified!("refs/tags/v1.0"), + &git::fmt::qualified!("refs/tags/v1.0"), + &git::fmt::qualified!("refs/tags/v1.0"), ), ( - &git::qualified!("refs/tags/release/v1.0"), - &git::qualified!("refs/tags/release/v1.0"), + &git::fmt::qualified!("refs/tags/release/v1.0"), + &git::fmt::qualified!("refs/tags/release/v1.0"), ), ( - &git::qualified!("refs/tags/release/candidates/v1.0"), - &git::qualified!("refs/tags/release/candidates/v1.0"), + &git::fmt::qualified!("refs/tags/release/candidates/v1.0"), + &git::fmt::qualified!("refs/tags/release/candidates/v1.0"), ), ( - &git::qualified!("refs/tags/qa/v1.0"), - &git::qualified!("refs/tags/qa/v1.0"), + &git::fmt::qualified!("refs/tags/qa/v1.0"), + &git::fmt::qualified!("refs/tags/qa/v1.0"), ), ], ) @@ -1200,7 +1201,7 @@ mod tests { // All tags should succeed at getting their canonical commit other than the // candidates tag. let stored = storage.repository(rid).unwrap(); - let failing = git::Qualified::from(git::lit::refs_tags(failing_tag)); + let failing = git::fmt::Qualified::from(git::fmt::lit::refs_tags(failing_tag)); for (refname, oid) in tags.into_iter() { let canonical = rules .canonical(refname.clone(), &stored) diff --git a/crates/radicle/src/git/raw.rs b/crates/radicle/src/git/raw.rs index 35b88228..1246d071 100644 --- a/crates/radicle/src/git/raw.rs +++ b/crates/radicle/src/git/raw.rs @@ -49,14 +49,3 @@ impl ErrorExt for git2::Error { self.code() == git2::ErrorCode::NotFound } } - -impl ErrorExt for git_ext::Error { - fn is_not_found(&self) -> bool { - use git_ext::Error::*; - match self { - Git(e) => e.is_not_found(), - NotFound(_) => true, - _ => false, - } - } -} diff --git a/crates/radicle/src/identity/doc.rs b/crates/radicle/src/identity/doc.rs index 6617265e..a28d3726 100644 --- a/crates/radicle/src/identity/doc.rs +++ b/crates/radicle/src/identity/doc.rs @@ -10,9 +10,9 @@ use std::path::Path; use std::str::FromStr; use std::sync::LazyLock; +use crate::git::Oid; use nonempty::NonEmpty; use radicle_cob::type_name::{TypeName, TypeNameParse}; -use radicle_git_ext::Oid; use serde::{de, Deserialize, Serialize}; use thiserror::Error; @@ -53,8 +53,6 @@ pub enum DocError { #[error(transparent)] Threshold(#[from] ThresholdError), #[error("git: {0}")] - GitExt(#[from] git::Error), - #[error("git: {0}")] Git(#[from] git::raw::Error), #[error("missing identity document")] Missing, @@ -72,7 +70,6 @@ impl DocError { /// Whether this error is caused by the document not being found. pub fn is_not_found(&self) -> bool { match self { - Self::GitExt(e) => e.is_not_found(), Self::Git(e) => e.is_not_found(), _ => false, } @@ -810,7 +807,7 @@ impl Doc { if !self.is_delegate(&key.into()) { return Err(*key); } - if key.verify(blob.as_bytes(), signature).is_err() { + if key.verify(AsRef::<[u8]>::as_ref(&blob), signature).is_err() { return Err(*key); } Ok(()) @@ -855,7 +852,7 @@ impl Doc { G: crypto::signature::Signer, { let (oid, bytes) = self.encode()?; - let sig = signer.sign(oid.as_bytes()); + let sig = signer.sign(oid.as_ref()); Ok((oid, bytes, sig)) } @@ -1132,7 +1129,7 @@ mod test { &repo, "heartwood".try_into().unwrap(), "Radicle Heartwood Protocol & Stack", - git::refname!("master"), + git::fmt::refname!("master"), Visibility::default(), &delegate, &storage, @@ -1182,7 +1179,7 @@ mod test { &working, "heartwood".try_into().unwrap(), "Radicle Heartwood Protocol & Stack", - git::refname!("master"), + git::fmt::refname!("master"), Visibility::default(), &delegate, &storage, diff --git a/crates/radicle/src/identity/doc/id.rs b/crates/radicle/src/identity/doc/id.rs index 46eb0c87..9a207170 100644 --- a/crates/radicle/src/identity/doc/id.rs +++ b/crates/radicle/src/identity/doc/id.rs @@ -1,7 +1,7 @@ use std::ops::Deref; use std::{ffi::OsString, fmt, str::FromStr}; -use git_ext::ref_format::{Component, RefString}; +use crate::git::fmt::{Component, RefString}; use thiserror::Error; use crate::git; @@ -12,10 +12,10 @@ pub const RAD_PREFIX: &str = "rad:"; #[derive(Error, Debug)] pub enum IdError { - #[error("invalid git object id: {0}")] - InvalidOid(#[from] git::raw::Error), #[error(transparent)] Multibase(#[from] multibase::Error), + #[error("invalid length: expected {expected} bytes, got {actual} bytes")] + Length { expected: usize, actual: usize }, } /// A repository identifier. @@ -68,14 +68,18 @@ impl RepoId { /// Eg. `z3XncAdkZjeK9mQS5Sdc4qhw98BUX`. /// pub fn canonical(&self) -> String { - multibase::encode(multibase::Base::Base58Btc, self.0.as_bytes()) + multibase::encode(multibase::Base::Base58Btc, AsRef::<[u8]>::as_ref(&self.0)) } pub fn from_canonical(input: &str) -> Result { + const EXPECTED_LEN: usize = 20; let (_, bytes) = multibase::decode(input)?; - let array: git::Oid = bytes.as_slice().try_into()?; - - Ok(Self(array)) + let bytes: [u8; EXPECTED_LEN] = + bytes.try_into().map_err(|bytes: Vec| IdError::Length { + expected: EXPECTED_LEN, + actual: bytes.len(), + })?; + Ok(Self(crate::git::Oid::from_sha1(bytes))) } } diff --git a/crates/radicle/src/identity/doc/update.rs b/crates/radicle/src/identity/doc/update.rs index 5fd2755c..5f3484b9 100644 --- a/crates/radicle/src/identity/doc/update.rs +++ b/crates/radicle/src/identity/doc/update.rs @@ -208,7 +208,8 @@ pub fn verify(raw: RawDoc) -> Result { .map(|rcrefs| rcrefs.and_then(|c| project.map(|p| (c, p)))) { Ok(Some((crefs, project))) => { - let default = git::Qualified::from(git::lit::refs_heads(project.default_branch())); + let default = + git::fmt::Qualified::from(git::fmt::lit::refs_heads(project.default_branch())); let matches = crefs .raw_rules() .matches(&default) @@ -300,7 +301,7 @@ mod test { #[test] fn test_cannot_include_default_branch_rule() { let raw = arbitrary::gen::(1); - let branch = git::Qualified::from(git::lit::refs_heads( + let branch = git::fmt::Qualified::from(git::fmt::lit::refs_heads( raw.project().unwrap().default_branch(), )); let raw = super::payload( @@ -333,7 +334,7 @@ mod test { #[test] fn test_default_branch_rule_exists_after_verification() { let raw = arbitrary::gen::(1); - let branch = git::Qualified::from(git::lit::refs_heads( + let branch = git::fmt::Qualified::from(git::fmt::lit::refs_heads( raw.project().unwrap().default_branch(), )); let raw = super::payload( diff --git a/crates/radicle/src/identity/doc/update/error.rs b/crates/radicle/src/identity/doc/update/error.rs index a9e664f4..05dd3703 100644 --- a/crates/radicle/src/identity/doc/update/error.rs +++ b/crates/radicle/src/identity/doc/update/error.rs @@ -1,7 +1,7 @@ use thiserror::Error; use crate::git; -use crate::git::RefString; +use crate::git::fmt::RefString; use crate::identity::{doc::PayloadId, Did, DocError}; #[derive(Debug, Error)] @@ -31,7 +31,7 @@ pub enum DocVerification { #[error("incompatible payloads: The rule(s) xyz.radicle.crefs.rules.{matches:?} matches the value of xyz.radicle.project.defaultBranch ('{default}'). Possible resolutions: Change the name of the default branch or remove the rule(s).")] DisallowDefault { matches: Vec, - default: git::Qualified<'static>, + default: git::fmt::Qualified<'static>, }, } diff --git a/crates/radicle/src/identity/project.rs b/crates/radicle/src/identity/project.rs index 242288ef..2b90cc6a 100644 --- a/crates/radicle/src/identity/project.rs +++ b/crates/radicle/src/identity/project.rs @@ -7,9 +7,9 @@ use serde::{ use thiserror::Error; use crate::crypto; +use crate::git::BranchName; use crate::identity::doc; use crate::identity::doc::Payload; -use crate::storage::BranchName; pub use crypto::PublicKey; diff --git a/crates/radicle/src/lib.rs b/crates/radicle/src/lib.rs index c2be087c..a83ba98d 100644 --- a/crates/radicle/src/lib.rs +++ b/crates/radicle/src/lib.rs @@ -7,7 +7,6 @@ pub extern crate radicle_crypto as crypto; #[macro_use] extern crate amplify; -extern crate radicle_git_ext as git_ext; mod canonical; @@ -42,10 +41,9 @@ pub mod prelude { use super::*; pub use crypto::{PublicKey, Verified}; + pub use git::BranchName; pub use identity::{project::Project, Did, Doc, RawDoc, RepoId}; pub use node::{Alias, NodeId, Timestamp}; pub use profile::Profile; - pub use storage::{ - BranchName, ReadRepository, ReadStorage, SignRepository, WriteRepository, WriteStorage, - }; + pub use storage::{ReadRepository, ReadStorage, SignRepository, WriteRepository, WriteStorage}; } diff --git a/crates/radicle/src/node.rs b/crates/radicle/src/node.rs index c71de57f..4b108a8c 100644 --- a/crates/radicle/src/node.rs +++ b/crates/radicle/src/node.rs @@ -729,7 +729,7 @@ impl FetchResult { } } - pub fn find_updated(&self, name: &git::RefStr) -> Option { + pub fn find_updated(&self, name: &git::fmt::RefStr) -> Option { let updated = match self { Self::Success { updated, .. } => Some(updated), _ => None, diff --git a/crates/radicle/src/node/events.rs b/crates/radicle/src/node/events.rs index de63491e..a8434c7b 100644 --- a/crates/radicle/src/node/events.rs +++ b/crates/radicle/src/node/events.rs @@ -9,7 +9,8 @@ use std::time; use crossbeam_channel as chan; -use crate::git::{Oid, Qualified}; +use crate::git::fmt::Qualified; +use crate::git::Oid; use crate::node; use crate::prelude::*; use crate::storage::{refs, RefUpdate}; diff --git a/crates/radicle/src/node/notifications.rs b/crates/radicle/src/node/notifications.rs index 23f77d9b..704a2f94 100644 --- a/crates/radicle/src/node/notifications.rs +++ b/crates/radicle/src/node/notifications.rs @@ -7,7 +7,7 @@ use thiserror::Error; use crate::cob; use crate::cob::TypedId; -use crate::git::{BranchName, Qualified}; +use crate::git::{fmt::Qualified, BranchName}; use crate::prelude::RepoId; use crate::storage::{RefUpdate, RemoteId}; @@ -73,7 +73,7 @@ pub enum NotificationKindError { TypedId(#[from] cob::ParseIdentifierError), /// Invalid Git ref format. #[error("invalid ref format: {0}")] - RefFormat(#[from] radicle_git_ext::ref_format::Error), + RefFormat(#[from] crate::git::fmt::Error), } impl TryFrom> for NotificationKind { diff --git a/crates/radicle/src/node/notifications/store.rs b/crates/radicle/src/node/notifications/store.rs index 6fe29870..4700e86e 100644 --- a/crates/radicle/src/node/notifications/store.rs +++ b/crates/radicle/src/node/notifications/store.rs @@ -10,7 +10,9 @@ use sqlite as sql; use thiserror::Error; use crate::git; -use crate::git::{Oid, RefError, RefString}; +use crate::git::fmt::RefString; +use crate::git::Oid; +use crate::git::RefError; use crate::prelude::RepoId; use crate::sql::transaction; use crate::storage::RefUpdate; @@ -40,7 +42,7 @@ pub enum Error { RefName(#[from] RefError), /// Invalid Git ref format. #[error("invalid ref format: {0}")] - RefFormat(#[from] git_ext::ref_format::Error), + RefFormat(#[from] crate::git::fmt::Error), /// Invalid notification kind. #[error("invalid notification kind: {0}")] NotificationKind(#[from] NotificationKindError), @@ -414,10 +416,10 @@ mod parse { #[cfg(test)] #[allow(clippy::unwrap_used)] mod test { - use radicle_git_ext::ref_format::{qualified, refname}; + use crate::git::fmt::{qualified, refname}; + use crate::{cob, node::NodeId, test::arbitrary}; use super::*; - use crate::{cob, node::NodeId, test::arbitrary}; #[test] fn test_clear() { diff --git a/crates/radicle/src/node/refs/store.rs b/crates/radicle/src/node/refs/store.rs index deae273f..c9fe0402 100644 --- a/crates/radicle/src/node/refs/store.rs +++ b/crates/radicle/src/node/refs/store.rs @@ -6,7 +6,8 @@ use localtime::LocalTime; use sqlite as sql; use thiserror::Error; -use crate::git::{Oid, Qualified}; +use crate::git::fmt::Qualified; +use crate::git::Oid; use crate::node::Database; use crate::node::NodeId; use crate::prelude::RepoId; @@ -176,7 +177,7 @@ impl Store for Database { #[allow(clippy::unwrap_used)] mod test { use super::*; - use crate::git::qualified; + use crate::git::fmt::qualified; use crate::test::arbitrary; use localtime::{LocalDuration, LocalTime}; diff --git a/crates/radicle/src/node/seed.rs b/crates/radicle/src/node/seed.rs index 5e2ae4bf..642872e8 100644 --- a/crates/radicle/src/node/seed.rs +++ b/crates/radicle/src/node/seed.rs @@ -3,7 +3,6 @@ pub use store::{Error, Store}; use localtime::LocalTime; -use crate::git; use crate::node::KnownAddress; use crate::prelude::NodeId; use crate::storage::{refs::RefsAt, ReadRepository, RemoteId}; @@ -14,8 +13,7 @@ use crate::storage::{refs::RefsAt, ReadRepository, RemoteId}; #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct SyncedAt { /// Head of `rad/sigrefs`. - #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))] - pub oid: git_ext::Oid, + pub oid: crate::git::Oid, /// When these refs were synced. #[serde(with = "crate::serde_ext::localtime::time")] #[cfg_attr( @@ -27,7 +25,10 @@ pub struct SyncedAt { impl SyncedAt { /// Load a new [`SyncedAt`] for the given remote. - pub fn load(repo: &S, remote: RemoteId) -> Result { + pub fn load( + repo: &S, + remote: RemoteId, + ) -> Result { let refs = RefsAt::new(repo, remote)?; let oid = refs.at; @@ -35,7 +36,10 @@ impl SyncedAt { } /// Create a new [`SyncedAt`] given an OID, by looking up the timestamp in the repo. - pub fn new(oid: git::ext::Oid, repo: &S) -> Result { + pub fn new( + oid: crate::git::Oid, + repo: &S, + ) -> Result { let timestamp = repo.commit(oid)?.time(); let timestamp = LocalTime::from_secs(timestamp.seconds() as u64); diff --git a/crates/radicle/src/rad.rs b/crates/radicle/src/rad.rs index 505a0d64..7b1086a5 100644 --- a/crates/radicle/src/rad.rs +++ b/crates/radicle/src/rad.rs @@ -9,6 +9,7 @@ use thiserror::Error; use crate::cob::ObjectId; use crate::crypto::Verified; use crate::git; +use crate::git::BranchName; use crate::identity::doc; use crate::identity::doc::{DocError, RepoId, Visibility}; use crate::identity::project::{Project, ProjectName}; @@ -17,18 +18,18 @@ use crate::storage::git::transport; use crate::storage::git::Repository; use crate::storage::refs::SignedRefs; use crate::storage::RepositoryError; -use crate::storage::{BranchName, ReadRepository as _, RemoteId, SignRepository as _}; +use crate::storage::{ReadRepository as _, RemoteId, SignRepository as _}; use crate::storage::{WriteRepository, WriteStorage}; use crate::{identity, storage}; /// Name of the radicle storage remote. -pub static REMOTE_NAME: LazyLock = LazyLock::new(|| git::refname!("rad")); +pub static REMOTE_NAME: LazyLock = LazyLock::new(|| git::fmt::refname!("rad")); /// Name of the radicle storage remote. -pub static REMOTE_COMPONENT: LazyLock = - LazyLock::new(|| git::fmt::name::component!("rad")); +pub static REMOTE_COMPONENT: LazyLock = + LazyLock::new(|| git::fmt::component!("rad")); /// Refname used for pushing patches. -pub static PATCHES_REFNAME: LazyLock = - LazyLock::new(|| git::refname!("refs/patches")); +pub static PATCHES_REFNAME: LazyLock = + LazyLock::new(|| git::fmt::refname!("refs/patches")); #[derive(Error, Debug)] pub enum InitError { @@ -110,16 +111,16 @@ where git::configure_repository(repo)?; git::configure_remote(repo, &REMOTE_NAME, url, &url.clone().with_namespace(*pk))?; - let branch = git::Qualified::from(git::fmt::lit::refs_heads(default_branch)); + let branch = git::fmt::Qualified::from(git::fmt::lit::refs_heads(default_branch)); { // Push branch to storage. let stored = dunce::canonicalize(stored.path())?.display().to_string(); // Pushes to default branch to the namespace of the `signer`. - let pushspec = git::Refspec { + let pushspec = git::fmt::refspec::Refspec { src: branch.clone(), - dst: branch.with_namespace(git::Component::from(pk)), + dst: branch.with_namespace(git::fmt::Component::from(pk)), force: false, } .to_string(); @@ -128,8 +129,8 @@ where } // N.b. we need to create the remote branch for the default branch - let rad_remote = - git::Qualified::from(git::lit::refs_remotes(&*REMOTE_COMPONENT)).join(default_branch); + let rad_remote = git::fmt::Qualified::from(git::fmt::lit::refs_remotes(&*REMOTE_COMPONENT)) + .join(default_branch); let oid = repo.refname_to_id(branch.as_str())?; repo.reference( rad_remote.as_str(), @@ -215,7 +216,7 @@ where raw.reference( &canonical_branch.with_namespace(me.into()), - *canonical_head, + canonical_head.into(), true, &format!("creating default branch for {me}"), )?; @@ -281,10 +282,10 @@ pub fn checkout, S: storage::ReadStorage>( { // Fetch remote head to working copy. - let fetchspec = git::Refspec { - src: git::refspec::pattern!("refs/heads/*"), - dst: git::Qualified::from(git::lit::refs_remotes(&*REMOTE_NAME)) - .to_pattern(git::refspec::STAR) + let fetchspec = git::fmt::refspec::Refspec { + src: git::fmt::pattern!("refs/heads/*"), + dst: git::fmt::Qualified::from(git::fmt::lit::refs_remotes(&*REMOTE_NAME)) + .to_pattern(git::fmt::refspec::STAR) .into_patternstring(), force: false, } @@ -456,15 +457,15 @@ pub fn repo_jj_git_root() -> Result { /// Setup patch upstream branch such that `git push` updates the patch. pub fn setup_patch_upstream<'a>( patch: &ObjectId, - patch_head: git::Oid, - working: &'a git::raw::Repository, - remote: &git::RefString, + patch_head: crate::git::Oid, + working: &'a crate::git::raw::Repository, + remote: &git::fmt::RefString, force: bool, -) -> Result>, git::ext::Error> { +) -> Result>, crate::git::raw::Error> { let head = working.head()?; // Don't do anything in case we're not on the patch branch. - if head.peel_to_commit()?.id() != *patch_head { + if patch_head != head.peel_to_commit()?.id() { return Ok(None); } let Ok(r) = head.resolve() else { @@ -476,18 +477,18 @@ pub fn setup_patch_upstream<'a>( return Ok(None); } - let branch = git::raw::Branch::wrap(r); + let branch = crate::git::raw::Branch::wrap(r); // Only set the upstream if it's missing or `force` is `true` if branch.upstream().is_ok() && !force { return Ok(None); } - let name: Option = branch.name()?.and_then(|b| b.try_into().ok()); + let name: Option = branch.name()?.and_then(|b| b.try_into().ok()); let remote_branch = git::refs::workdir::patch_upstream(patch); let remote_branch = working.reference( &remote_branch, - *patch_head, + patch_head.into(), true, "Create remote tracking branch for patch", )?; @@ -498,7 +499,7 @@ pub fn setup_patch_upstream<'a>( git::set_upstream(working, remote, name.as_str(), git::refs::patch(patch))?; } } - Ok(Some(git::raw::Branch::wrap(remote_branch))) + Ok(Some(crate::git::raw::Branch::wrap(remote_branch))) } #[cfg(test)] @@ -508,12 +509,12 @@ mod tests { use pretty_assertions::assert_eq; - use crate::git::{name::component, qualified}; use crate::identity::Did; use crate::storage::git::transport; use crate::storage::git::Storage; use crate::storage::{ReadStorage, RemoteRepository as _}; use crate::test::fixtures; + use git::fmt::{component, qualified}; use super::*; @@ -531,7 +532,7 @@ mod tests { &repo, "acme".try_into().unwrap(), "Acme's repo", - git::refname!("master"), + git::fmt::refname!("master"), Visibility::default(), &signer, &storage, @@ -553,19 +554,19 @@ mod tests { // Test canonical refs. assert_eq!(refs.head(component!("master")).unwrap(), head); - assert_eq!(project_repo.raw().refname_to_id("HEAD").unwrap(), *head); + assert_eq!(head, project_repo.raw().refname_to_id("HEAD").unwrap()); assert_eq!( + head, project_repo .raw() .refname_to_id("refs/heads/master") .unwrap(), - *head ); assert_eq!(remotes[&public_key].refs, refs); assert_eq!(project.name(), "acme"); assert_eq!(project.description(), "Acme's repo"); - assert_eq!(project.default_branch(), &git::refname!("master")); + assert_eq!(project.default_branch(), &git::fmt::refname!("master")); assert_eq!(doc.delegates().first(), &Did::from(public_key)); } @@ -586,7 +587,7 @@ mod tests { &original, "acme".try_into().unwrap(), "Acme's repo", - git::refname!("master"), + git::fmt::refname!("master"), Visibility::default(), &alice, &storage, @@ -622,7 +623,7 @@ mod tests { &original, "acme".try_into().unwrap(), "Acme's repo", - git::refname!("master"), + git::fmt::refname!("master"), Visibility::default(), &signer, &storage, diff --git a/crates/radicle/src/schemars_ext.rs b/crates/radicle/src/schemars_ext.rs index 07b125f2..bac10a98 100644 --- a/crates/radicle/src/schemars_ext.rs +++ b/crates/radicle/src/schemars_ext.rs @@ -90,23 +90,9 @@ pub(crate) mod localtime { } pub(crate) mod git { - use super::*; - - /// See [`crate::git::Oid`] - /// See [`::git_ext::Oid`] - /// See [`::git::raw::Oid`] - /// - /// A Git Object Identifier in hexadecimal encoding. - #[derive(JsonSchema)] - #[schemars( - remote = "git::raw::Oid", - description = "A Git Object Identifier (SHA-1 or SHA-256 hash) in hexadecimal encoding." - )] - pub(crate) struct Oid( - #[schemars(regex(pattern = r"^([0-9a-fA-F]{64}|[0-9a-fA-F]{40})$"))] String, - ); - - /// See [`crate::git::RefString`] - #[derive(JsonSchema)] - pub(crate) struct RefString(String); + pub(crate) mod fmt { + /// See [`crate::git::fmt::RefString`] + #[derive(schemars::JsonSchema)] + pub(crate) struct RefString(String); + } } diff --git a/crates/radicle/src/storage.rs b/crates/radicle/src/storage.rs index 3e9a830f..37acaf17 100644 --- a/crates/radicle/src/storage.rs +++ b/crates/radicle/src/storage.rs @@ -10,15 +10,16 @@ use nonempty::NonEmpty; use serde::{Deserialize, Serialize}; use thiserror::Error; +pub use crate::git::Oid; use crypto::{PublicKey, Unverified, Verified}; pub use git::{Validation, Validations}; -pub use radicle_git_ext::Oid; use crate::cob; use crate::collections::RandomMap; +use crate::git::canonical; +use crate::git::fmt::{refspec::PatternString, refspec::Refspec, Qualified, RefStr, RefString}; use crate::git::raw::ErrorExt as _; -use crate::git::{canonical, ext as git_ext}; -use crate::git::{refspec::Refspec, PatternString, Qualified, RefError, RefStr, RefString}; +use crate::git::RefError; use crate::identity::{doc, Did, PayloadError}; use crate::identity::{Doc, DocAt, DocError}; use crate::identity::{Identity, RepoId}; @@ -27,10 +28,8 @@ use crate::node::SyncedAt; use crate::storage::git::NAMESPACES_GLOB; use crate::storage::refs::Refs; -use self::git::UserInfo; use self::refs::{RefsAt, SignedRefs}; - -pub type BranchName = git::RefString; +use crate::git::UserInfo; /// Basic repository information. #[derive(Debug, Clone, PartialEq, Eq)] @@ -69,7 +68,9 @@ impl Namespaces { Namespaces::Followed(pks) => pks .iter() .map(|pk| { - let ns = pk.to_namespace().with_pattern(git::refspec::STAR); + let ns = pk + .to_namespace() + .with_pattern(crate::git::fmt::refspec::STAR); Refspec { src: ns.clone(), dst: ns, @@ -114,9 +115,7 @@ pub enum RepositoryError { #[error(transparent)] Payload(#[from] PayloadError), #[error(transparent)] - Git(#[from] git::raw::Error), - #[error(transparent)] - GitExt(#[from] git_ext::Error), + Git(#[from] crate::git::raw::Error), #[error(transparent)] Quorum(#[from] canonical::error::QuorumError), #[error(transparent)] @@ -135,7 +134,6 @@ impl RepositoryError { pub fn is_not_found(&self) -> bool { match self { Self::Storage(e) => e.is_not_found(), - Self::GitExt(e) => e.is_not_found(), Self::Git(e) => e.is_not_found(), _ => false, } @@ -154,9 +152,7 @@ pub enum Error { #[error(transparent)] Refs(#[from] refs::Error), #[error("git: {0}")] - Git(#[from] git::raw::Error), - #[error("git: {0}")] - Ext(#[from] git::ext::Error), + Git(#[from] crate::git::raw::Error), #[error("invalid repository identifier {0:?}")] InvalidId(std::ffi::OsString), #[error("i/o: {0}")] @@ -180,7 +176,7 @@ impl Error { #[allow(clippy::large_enum_variant)] pub enum FetchError { #[error("git: {0}")] - Git(#[from] git::raw::Error), + Git(#[from] crate::git::raw::Error), #[error("i/o: {0}")] Io(#[from] io::Error), #[error(transparent)] @@ -205,36 +201,31 @@ pub enum RefUpdate { Updated { #[cfg_attr( feature = "schemars", - schemars(with = "crate::schemars_ext::git::RefString") + schemars(with = "crate::schemars_ext::git::fmt::RefString") )] name: RefString, - #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))] old: Oid, - #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))] new: Oid, }, Created { #[cfg_attr( feature = "schemars", - schemars(with = "crate::schemars_ext::git::RefString") + schemars(with = "crate::schemars_ext::git::fmt::RefString") )] name: RefString, - #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))] oid: Oid, }, Deleted { #[cfg_attr( feature = "schemars", - schemars(with = "crate::schemars_ext::git::RefString") + schemars(with = "crate::schemars_ext::git::fmt::RefString") )] name: RefString, - #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))] oid: Oid, }, Skipped { #[cfg_attr(feature = "schemars", schemars(with = "String"))] name: RefString, - #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))] oid: Oid, }, } @@ -504,7 +495,7 @@ pub trait ReadRepository: Sized + ValidateRepository { fn id(&self) -> RepoId; /// Returns `true` if there are no references in the repository. - fn is_empty(&self) -> Result; + fn is_empty(&self) -> Result; /// The [`Path`] to the git repository. fn path(&self) -> &Path; @@ -514,10 +505,10 @@ pub trait ReadRepository: Sized + ValidateRepository { &self, commit: Oid, path: P, - ) -> Result; + ) -> Result; /// Get a blob in this repository, given its id. - fn blob(&self, oid: Oid) -> Result; + fn blob(&self, oid: Oid) -> Result; /// Get the head of this repository. /// @@ -541,7 +532,7 @@ pub trait ReadRepository: Sized + ValidateRepository { fn identity_head(&self) -> Result; /// Get the identity head of a specific remote. - fn identity_head_of(&self, remote: &RemoteId) -> Result; + fn identity_head_of(&self, remote: &RemoteId) -> Result; /// Get the root commit of the canonical identity branch. fn identity_root(&self) -> Result; @@ -577,28 +568,28 @@ pub trait ReadRepository: Sized + ValidateRepository { &self, remote: &RemoteId, reference: &Qualified, - ) -> Result; + ) -> Result; - /// Get the [`git::raw::Commit`] found using its `oid`. + /// Get the [`crate::git::raw::Commit`] found using its `oid`. /// /// Returns `Err` if the commit did not exist. - fn commit(&self, oid: Oid) -> Result; + fn commit(&self, oid: Oid) -> Result; /// Perform a revision walk of a commit history starting from the given head. - fn revwalk(&self, head: Oid) -> Result; + fn revwalk(&self, head: Oid) -> Result; /// Check if the underlying ODB contains the given `oid`. - fn contains(&self, oid: Oid) -> Result; + fn contains(&self, oid: Oid) -> Result; /// Check whether the given commit is an ancestor of another commit. - fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result; + fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result; /// Get the object id of a reference under the given remote. fn reference_oid( &self, remote: &RemoteId, reference: &Qualified, - ) -> Result; + ) -> Result; /// Get all references of the given remote. fn references_of(&self, remote: &RemoteId) -> Result; @@ -610,8 +601,8 @@ pub trait ReadRepository: Sized + ValidateRepository { /// commit pointed to by the tag is returned, and not the [`Oid`] of the tag itsself. fn references_glob( &self, - pattern: &git::PatternStr, - ) -> Result, git::ext::Error>; + pattern: &crate::git::fmt::refspec::PatternStr, + ) -> Result, crate::git::raw::Error>; /// Get repository delegates. fn delegates(&self) -> Result, RepositoryError> { @@ -632,7 +623,7 @@ pub trait ReadRepository: Sized + ValidateRepository { fn identity_doc_at(&self, head: Oid) -> Result; /// Get the merge base of two commits. - fn merge_base(&self, left: &Oid, right: &Oid) -> Result; + fn merge_base(&self, left: &Oid, right: &Oid) -> Result; } /// Access the remotes of a repository. @@ -697,7 +688,7 @@ pub trait WriteRepository: ReadRepository + SignRepository { /// Set the user info of the Git repository. fn set_user(&self, info: &UserInfo) -> Result<(), Error>; /// Get the underlying git repository. - fn raw(&self) -> &git::raw::Repository; + fn raw(&self) -> &crate::git::raw::Repository; } /// Allows signing refs. diff --git a/crates/radicle/src/storage/git.rs b/crates/radicle/src/storage/git.rs index bdfea41e..faf5f29e 100644 --- a/crates/radicle/src/storage/git.rs +++ b/crates/radicle/src/storage/git.rs @@ -27,24 +27,26 @@ use crate::storage::{ ReadRepository, ReadStorage, Remote, Remotes, RepositoryInfo, SetHead, SignRepository, WriteRepository, WriteStorage, }; -use crate::{git, node}; +use crate::{git, git::Oid, node}; -pub use crate::git::{ - ext, raw, refname, refspec, Oid, PatternStr, Qualified, RefError, RefString, UserInfo, +use crate::git::fmt::{ + refname, refspec, refspec::PatternStr, refspec::PatternString, Qualified, RefString, }; +use crate::git::RefError; +use crate::git::UserInfo; pub use crate::storage::{Error, RepositoryError}; use super::refs::RefsAt; use super::{RemoteId, RemoteRepository, ValidateRepository}; -pub static NAMESPACES_GLOB: LazyLock = - LazyLock::new(|| git::refspec::pattern!("refs/namespaces/*")); +pub static NAMESPACES_GLOB: LazyLock = + LazyLock::new(|| git::fmt::pattern!("refs/namespaces/*")); pub static SIGREFS_GLOB: LazyLock = - LazyLock::new(|| git::refspec::pattern!("refs/namespaces/*/rad/sigrefs")); -pub static CANONICAL_IDENTITY: LazyLock = LazyLock::new(|| { - git::Qualified::from_components( - git::name::component!("rad"), - git::name::component!("id"), + LazyLock::new(|| git::fmt::pattern!("refs/namespaces/*/rad/sigrefs")); +pub static CANONICAL_IDENTITY: LazyLock = LazyLock::new(|| { + git::fmt::Qualified::from_components( + git::fmt::component!("rad"), + git::fmt::component!("id"), None, ) }); @@ -52,7 +54,7 @@ pub static CANONICAL_IDENTITY: LazyLock = LazyLock::new(|| { /// A parsed Git reference. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Ref { - pub oid: git::Oid, + pub oid: crate::git::Oid, pub name: RefString, pub namespace: Option, } @@ -446,9 +448,9 @@ impl Repository { continue; } - let glob = git::refname!("refs/namespaces") - .join(git::Component::from(&id)) - .with_pattern(git::refspec::STAR); + let glob = git::fmt::refname!("refs/namespaces") + .join(git::fmt::Component::from(&id)) + .with_pattern(git::fmt::refspec::STAR); let refs = match self.references_glob(&glob) { Ok(refs) => refs, Err(e) => { @@ -476,7 +478,7 @@ impl Repository { doc: &Doc, storage: &S, signer: &Device, - ) -> Result<(Self, git::Oid), RepositoryError> + ) -> Result<(Self, crate::git::Oid), RepositoryError> where G: crypto::signature::Signer, S: WriteStorage, @@ -486,7 +488,7 @@ impl Repository { let repo = Self::create(paths::repository(storage, &id), id, storage.info())?; let oid = repo.backend.blob(&doc_bytes)?; // Store document blob in repository. - debug_assert_eq!(oid, *doc_oid); + debug_assert_eq!(doc_oid, oid); let commit = doc.init(&repo, signer)?; @@ -661,48 +663,52 @@ impl ReadRepository for Repository { self.backend.path() } - fn blob_at>(&self, commit: Oid, path: P) -> Result { - let commit = self.backend.find_commit(*commit)?; + fn blob_at>( + &self, + commit_id: Oid, + path: P, + ) -> Result { + let commit = self.backend.find_commit(git::raw::Oid::from(commit_id))?; let tree = commit.tree()?; let entry = tree.get_path(path.as_ref())?; let obj = entry.to_object(&self.backend)?; - let blob = obj.into_blob().map_err(|_| { - git::Error::NotFound(git::NotFound::NoSuchBlob( - path.as_ref().display().to_string(), - )) - })?; + let blob = obj.into_blob().map_err(|_| + crate::git::raw::Error::new( + crate::git::raw::ErrorCode::NotFound, + crate::git::raw::ErrorClass::None, + format!("Path '{}' in tree of commit {commit_id} was expected to be a blob, but is not.", path.as_ref().display()), + ) + )?; Ok(blob) } - fn blob(&self, oid: Oid) -> Result { - self.backend.find_blob(oid.into()).map_err(git::Error::from) + fn blob(&self, oid: Oid) -> Result { + self.backend.find_blob(oid.into()) } fn reference( &self, remote: &RemoteId, - name: &git::Qualified, - ) -> Result { + name: &git::fmt::Qualified, + ) -> Result { let name = name.with_namespace(remote.into()); - self.backend.find_reference(&name).map_err(git::Error::from) + self.backend.find_reference(&name) } fn reference_oid( &self, remote: &RemoteId, - reference: &git::Qualified, - ) -> Result { + reference: &git::fmt::Qualified, + ) -> Result { let name = reference.with_namespace(remote.into()); let oid = self.backend.refname_to_id(&name)?; Ok(oid.into()) } - fn commit(&self, oid: Oid) -> Result { - self.backend - .find_commit(oid.into()) - .map_err(git::Error::from) + fn commit(&self, oid: Oid) -> Result { + self.backend.find_commit(oid.into()) } fn revwalk(&self, head: Oid) -> Result { @@ -712,14 +718,13 @@ impl ReadRepository for Repository { Ok(revwalk) } - fn contains(&self, oid: Oid) -> Result { + fn contains(&self, oid: Oid) -> Result { self.backend.odb().map(|odb| odb.exists(oid.into())) } - fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result { + fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result { self.backend .graph_descendant_of(head.into(), ancestor.into()) - .map_err(git::Error::from) } fn references_of(&self, remote: &RemoteId) -> Result { @@ -735,12 +740,14 @@ impl ReadRepository for Repository { let oid = e.resolve()?.target().ok_or(Error::InvalidRef)?; let (_, category, _, _) = refname.non_empty_components(); + use git::fmt::{component, name}; + if [ - git::name::HEADS, - git::name::TAGS, - git::name::NOTES, - &git::name::component!("rad"), - &git::name::component!("cobs"), + name::HEADS, + name::TAGS, + name::NOTES, + &component!("rad"), + &component!("cobs"), ] .contains(&category.as_ref()) { @@ -753,7 +760,7 @@ impl ReadRepository for Repository { fn references_glob( &self, pattern: &PatternStr, - ) -> Result, git::ext::Error> { + ) -> Result, crate::git::raw::Error> { let mut refs = Vec::new(); for r in self.backend.references_glob(pattern)? { @@ -765,8 +772,8 @@ impl ReadRepository for Repository { if let Some(name) = r .name() - .and_then(|n| git::RefStr::try_from_str(n).ok()) - .and_then(git::Qualified::from_refstr) + .and_then(|n| git::fmt::RefStr::try_from_str(n).ok()) + .and_then(git::fmt::Qualified::from_refstr) { refs.push((name.to_owned(), oid.into())); } @@ -823,9 +830,8 @@ impl ReadRepository for Repository { } } - fn identity_head_of(&self, remote: &RemoteId) -> Result { + fn identity_head_of(&self, remote: &RemoteId) -> Result { self.reference_oid(remote, &git::refs::storage::IDENTITY_BRANCH) - .map_err(git::ext::Error::from) } fn identity_root(&self) -> Result { @@ -864,7 +870,7 @@ impl ReadRepository for Repository { let blob = Doc::blob_at(root, self)?; // We've got an identity that goes back to the correct root. - if blob.id() == **self.id { + if *self.id == blob.id() { let identity = Identity::get(&root.into(), self)?; return Ok(identity.head()); @@ -873,11 +879,10 @@ impl ReadRepository for Repository { Err(DocError::Missing.into()) } - fn merge_base(&self, left: &Oid, right: &Oid) -> Result { + fn merge_base(&self, left: &Oid, right: &Oid) -> Result { self.backend - .merge_base(**left, **right) + .merge_base(left.into(), right.into()) .map(Oid::from) - .map_err(git::ext::Error::from) } } @@ -897,7 +902,7 @@ impl WriteRepository for Repository { } log::debug!(target: "storage", "Setting ref: {} -> {}", &branch_ref, new); self.raw() - .reference(&branch_ref, *new, true, "set-local-branch (radicle)")?; + .reference(&branch_ref, new.into(), true, "set-local-branch (radicle)")?; log::debug!(target: "storage", "Setting ref: {head_ref} -> {branch_ref}"); self.raw() @@ -910,7 +915,7 @@ impl WriteRepository for Repository { log::debug!(target: "storage", "Setting ref: {} -> {}", *CANONICAL_IDENTITY, commit); self.raw().reference( CANONICAL_IDENTITY.as_str(), - *commit, + commit.into(), true, "set-local-branch (radicle)", )?; @@ -925,7 +930,7 @@ impl WriteRepository for Repository { let refname = git::refs::storage::id_root(remote); self.raw() - .reference(refname.as_str(), *root, true, "set-id-root (radicle)")?; + .reference(refname.as_str(), root.into(), true, "set-id-root (radicle)")?; Ok(()) } @@ -1104,7 +1109,7 @@ mod tests { git::commit( &working, &head, - &git::RefString::try_from(format!("refs/remotes/{alice}/heads/master")).unwrap(), + &git::fmt::RefString::try_from(format!("refs/remotes/{alice}/heads/master")).unwrap(), "Second commit", &sig, &head.tree().unwrap(), diff --git a/crates/radicle/src/storage/git/cob.rs b/crates/radicle/src/storage/git/cob.rs index f35a4509..1c612bd2 100644 --- a/crates/radicle/src/storage/git/cob.rs +++ b/crates/radicle/src/storage/git/cob.rs @@ -12,6 +12,7 @@ use storage::SignRepository; use storage::ValidateRepository; use crate::git; +use crate::git::fmt::*; use crate::git::*; use crate::identity; use crate::identity::doc::DocError; @@ -34,8 +35,6 @@ pub enum ObjectsError { Convert(#[from] cob::object::storage::convert::Error), #[error(transparent)] Git(#[from] git::raw::Error), - #[error(transparent)] - GitExt(#[from] git_ext::Error), } #[derive(Error, Debug)] @@ -290,7 +289,7 @@ impl ReadRepository for DraftStore<'_, R> { self.repo.path() } - fn commit(&self, oid: Oid) -> Result { + fn commit(&self, oid: Oid) -> Result { self.repo.commit(oid) } @@ -298,39 +297,39 @@ impl ReadRepository for DraftStore<'_, R> { self.repo.revwalk(head) } - fn contains(&self, oid: Oid) -> Result { + fn contains(&self, oid: Oid) -> Result { self.repo.contains(oid) } - fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result { + fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result { self.repo.is_ancestor_of(ancestor, head) } fn blob_at>( &self, - oid: git_ext::Oid, + oid: Oid, path: P, - ) -> Result { + ) -> Result { self.repo.blob_at(oid, path) } - fn blob(&self, oid: git_ext::Oid) -> Result { + fn blob(&self, oid: Oid) -> Result { self.repo.blob(oid) } fn reference( &self, remote: &RemoteId, - reference: &git::Qualified, - ) -> Result { + reference: &git::fmt::Qualified, + ) -> Result { self.repo.reference(remote, reference) } fn reference_oid( &self, remote: &RemoteId, - reference: &git::Qualified, - ) -> Result { + reference: &git::fmt::Qualified, + ) -> Result { self.repo.reference_oid(remote, reference) } @@ -340,8 +339,8 @@ impl ReadRepository for DraftStore<'_, R> { fn references_glob( &self, - pattern: &git::PatternStr, - ) -> Result, git::ext::Error> { + pattern: &git::fmt::refspec::PatternStr, + ) -> Result, crate::git::raw::Error> { self.repo.references_glob(pattern) } @@ -357,7 +356,7 @@ impl ReadRepository for DraftStore<'_, R> { self.repo.identity_head() } - fn identity_head_of(&self, remote: &RemoteId) -> Result { + fn identity_head_of(&self, remote: &RemoteId) -> Result { self.repo.identity_head_of(remote) } @@ -373,14 +372,14 @@ impl ReadRepository for DraftStore<'_, R> { self.repo.canonical_identity_head() } - fn merge_base(&self, left: &Oid, right: &Oid) -> Result { + fn merge_base(&self, left: &Oid, right: &Oid) -> Result { self.repo.merge_base(left, right) } } impl cob::object::Storage for DraftStore<'_, R> { type ObjectsError = ObjectsError; - type TypesError = git::ext::Error; + type TypesError = git::raw::Error; type UpdateError = git::raw::Error; type RemoveError = git::raw::Error; diff --git a/crates/radicle/src/storage/refs.rs b/crates/radicle/src/storage/refs.rs index 9a109d71..585e36be 100644 --- a/crates/radicle/src/storage/refs.rs +++ b/crates/radicle/src/storage/refs.rs @@ -13,7 +13,6 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::git; -use crate::git::ext as git_ext; use crate::git::raw::ErrorExt as _; use crate::git::Oid; use crate::node::device::Device; @@ -48,7 +47,7 @@ pub enum Error { #[error("invalid reference")] InvalidRef, #[error("missing identity root reference '{0}'")] - MissingIdentityRoot(git::RefString), + MissingIdentityRoot(git::fmt::RefString), #[error("missing identity object '{0}'")] MissingIdentity(Oid), #[error("mismatched identity: local {local}, remote {remote}")] @@ -57,26 +56,23 @@ pub enum Error { Ref(#[from] git::RefError), #[error(transparent)] Git(#[from] git::raw::Error), - #[error(transparent)] - GitExt(#[from] git_ext::Error), } impl Error { /// Whether this error is caused by a reference not being found. pub fn is_not_found(&self) -> bool { match self { - Self::GitExt(e) => e.is_not_found(), Self::Git(e) => e.is_not_found(), _ => false, } } } -// TODO(finto): we should turn `git::RefString` to `git::Qualified`, +// TODO(finto): we should turn `git::fmt::RefString` to `git::fmt::Qualified`, // since all these refs SHOULD be `Qualified`. /// The published state of a local repository. #[derive(Default, Clone, Debug, PartialEq, Eq, Serialize)] -pub struct Refs(BTreeMap); +pub struct Refs(BTreeMap); impl Refs { /// Verify the given signature on these refs, and return [`SignedRefs`] on success. @@ -102,13 +98,13 @@ impl Refs { } /// Get a particular ref. - pub fn get(&self, name: &git::Qualified) -> Option { + pub fn get(&self, name: &git::fmt::Qualified) -> Option { self.0.get(name.to_ref_string().as_refstr()).copied() } /// Get a particular head ref. - pub fn head(&self, name: impl AsRef) -> Option { - let branch = git::refname!("refs/heads").join(name); + pub fn head(&self, name: impl AsRef) -> Option { + let branch = git::fmt::refname!("refs/heads").join(name); self.0.get(&branch).copied() } @@ -123,8 +119,8 @@ impl Refs { .split_once(' ') .ok_or(canonical::Error::InvalidFormat)?; - let name = git::RefString::try_from(name)?; - let oid = Oid::from_str(oid)?; + let name = git::fmt::RefString::try_from(name)?; + let oid = Oid::from_str(oid).map_err(|_| canonical::Error::InvalidFormat)?; if oid.is_zero() { continue; @@ -148,15 +144,15 @@ impl Refs { } impl IntoIterator for Refs { - type Item = (git::RefString, Oid); - type IntoIter = std::collections::btree_map::IntoIter; + type Item = (git::fmt::RefString, Oid); + type IntoIter = std::collections::btree_map::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } -impl From for BTreeMap { +impl From for BTreeMap { fn from(refs: Refs) -> Self { refs.0 } @@ -168,14 +164,14 @@ impl From> for Refs { } } -impl From> for Refs { - fn from(refs: BTreeMap) -> Self { +impl From> for Refs { + fn from(refs: BTreeMap) -> Self { Self(refs) } } impl Deref for Refs { - type Target = BTreeMap; + type Target = BTreeMap; fn deref(&self) -> &Self::Target { &self.0 @@ -297,7 +293,7 @@ impl SignedRefs { Some(SignedRefsAt { sigrefs, at }) if sigrefs.signature == self.signature => { return Ok(Updated::Unchanged { oid: at }); } - Some(SignedRefsAt { at, .. }) => Some(raw.find_commit(*at)?), + Some(SignedRefsAt { at, .. }) => Some(raw.find_commit(at.into())?), None => None, }; @@ -386,12 +382,14 @@ pub struct RefsAt { )] pub remote: RemoteId, /// The commit SHA that `rad/sigrefs` points to. - #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))] pub at: Oid, } impl RefsAt { - pub fn new(repo: &S, remote: RemoteId) -> Result { + pub fn new( + repo: &S, + remote: RemoteId, + ) -> Result { let at = repo.reference_oid(&remote, &storage::refs::SIGREFS_BRANCH)?; Ok(RefsAt { remote, at }) } @@ -400,7 +398,7 @@ impl RefsAt { SignedRefsAt::load_at(self.at, self.remote, repo) } - pub fn path(&self) -> &git::Qualified { + pub fn path(&self) -> &git::fmt::Qualified { &SIGREFS_BRANCH } } @@ -446,7 +444,7 @@ impl SignedRefsAt { }) } - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.sigrefs.refs.iter() } } @@ -515,7 +513,7 @@ mod tests { &paris_repo, "paris".try_into().unwrap(), "Paris repository", - git::refname!("master"), + git::fmt::refname!("master"), Default::default(), &alice, storage, @@ -528,7 +526,7 @@ mod tests { &london_repo, "london".try_into().unwrap(), "London repository", - git::refname!("master"), + git::fmt::refname!("master"), Default::default(), &alice, storage, @@ -585,7 +583,7 @@ mod tests { let bob_head = git::empty_commit( &bob_working, &paris_head, - git::refname!("refs/heads/master").as_refstr(), + git::fmt::refname!("refs/heads/master").as_refstr(), "Bob's commit", &bob_sig, ) @@ -602,9 +600,9 @@ mod tests { assert_eq!( sigrefs - .get(&git_ext::ref_format::qualified!("refs/heads/master")) + .get(&crate::git::fmt::qualified!("refs/heads/master")) .unwrap(), - bob_head.id().into() + bob_head.id() ); (sigrefs, bob_head.id()) }; @@ -616,10 +614,10 @@ mod tests { .unwrap(); assert_ne!( alice_paris_sigrefs - .get(&git_ext::ref_format::qualified!("refs/heads/master")) + .get(&crate::git::fmt::qualified!("refs/heads/master")) .unwrap(), bob_paris_sigrefs - .get(&git_ext::ref_format::qualified!("refs/heads/master")) + .get(&crate::git::fmt::qualified!("refs/heads/master")) .unwrap() ); } @@ -651,7 +649,8 @@ mod tests { london .raw() .reference( - git::refs::storage::branch_of(bob.public_key(), &git::refname!("master")).as_str(), + git::refs::storage::branch_of(bob.public_key(), &git::fmt::refname!("master")) + .as_str(), bob_head, false, "", diff --git a/crates/radicle/src/test.rs b/crates/radicle/src/test.rs index b21ee03e..e6b40ed2 100644 --- a/crates/radicle/src/test.rs +++ b/crates/radicle/src/test.rs @@ -34,7 +34,7 @@ pub fn fetch( }; callbacks.update_tips(|name, old, new| { - if let Ok(name) = crate::git::RefString::try_from(name) { + if let Ok(name) = git::fmt::RefString::try_from(name) { if name.to_namespaced().is_some() { updates.push(RefUpdate::from(name, old, new)); // Returning `true` ensures the process is not aborted. @@ -77,13 +77,8 @@ pub mod setup { use crate::crypto::test::signer::MockSigner; use crate::node::device::Device; use crate::storage::git::transport::remote; - use crate::{ - git, - profile::Home, - rad::REMOTE_NAME, - test::{fixtures, storage::git::Repository}, - Storage, - }; + use crate::storage::git::Repository; + use crate::{git, profile::Home, rad::REMOTE_NAME, test::fixtures, Storage}; use crate::{prelude::*, rad}; /// A node. @@ -188,7 +183,8 @@ pub mod setup { &self, blobs: impl IntoIterator, ) -> BranchWith { - let refname = git::Qualified::from(git::lit::refs_heads(git::refname!("master"))); + let refname = + git::fmt::Qualified::from(git::fmt::lit::refs_heads(git::fmt::refname!("master"))); let base = self.checkout.refname_to_id(refname.as_str()).unwrap(); let parent = self.checkout.find_commit(base).unwrap(); let oid = commit(&self.checkout, &refname, blobs, &[&parent]); @@ -297,10 +293,10 @@ pub mod setup { pub fn commit, T: AsRef<[u8]>>( repo: &git::raw::Repository, - refname: &git::Qualified, + refname: &git::fmt::Qualified, blobs: impl IntoIterator, parents: &[&git::raw::Commit<'_>], - ) -> git::Oid { + ) -> crate::git::Oid { let tree = { let mut tb = repo.treebuilder(None).unwrap(); for (name, blob) in blobs.into_iter() { diff --git a/crates/radicle/src/test/arbitrary.rs b/crates/radicle/src/test/arbitrary.rs index 88cd2b2d..1df2cd9d 100644 --- a/crates/radicle/src/test/arbitrary.rs +++ b/crates/radicle/src/test/arbitrary.rs @@ -27,14 +27,14 @@ use crate::{cob, git}; pub fn oid() -> storage::Oid { let oid_bytes: [u8; 20] = gen(1); - storage::Oid::try_from(oid_bytes.as_slice()).unwrap() + storage::Oid::from_sha1(oid_bytes) } pub fn entry_id() -> cob::EntryId { self::oid() } -pub fn refstring(len: usize) -> git::RefString { +pub fn refstring(len: usize) -> git::fmt::RefString { let mut buf = Vec::::new(); for _ in 0..len { buf.push(fastrand::u8(0x61..0x7a)); @@ -135,7 +135,7 @@ impl Arbitrary for Project { let description = iter::repeat_with(|| rng.alphanumeric()) .take(length * 2) .collect(); - let default_branch: git::RefString = iter::repeat_with(|| rng.alphanumeric()) + let default_branch: git::fmt::RefString = iter::repeat_with(|| rng.alphanumeric()) .take(length) .collect::() .try_into() @@ -207,7 +207,7 @@ impl Arbitrary for SignedRefs { impl Arbitrary for Refs { fn arbitrary(g: &mut qcheck::Gen) -> Self { - let mut refs: BTreeMap = BTreeMap::new(); + let mut refs: BTreeMap = BTreeMap::new(); let mut bytes: [u8; 20] = [0; 20]; let names = &[ "heads/master", @@ -225,8 +225,8 @@ impl Arbitrary for Refs { for byte in &mut bytes { *byte = u8::arbitrary(g); } - let oid = storage::Oid::try_from(&bytes[..]).unwrap(); - let name = git::RefString::try_from(*name).unwrap(); + let oid = storage::Oid::from_sha1(bytes); + let name = git::fmt::RefString::try_from(*name).unwrap(); refs.insert(name, oid); } @@ -273,7 +273,7 @@ impl Arbitrary for storage::Remote { impl Arbitrary for RepoId { fn arbitrary(g: &mut qcheck::Gen) -> Self { let bytes = <[u8; 20]>::arbitrary(g); - let oid = git::Oid::try_from(bytes.as_slice()).unwrap(); + let oid = crate::git::Oid::from_sha1(bytes); RepoId::from(oid) } diff --git a/crates/radicle/src/test/fixtures.rs b/crates/radicle/src/test/fixtures.rs index f30d92e7..66fc00ae 100644 --- a/crates/radicle/src/test/fixtures.rs +++ b/crates/radicle/src/test/fixtures.rs @@ -57,7 +57,7 @@ where &repo, name.try_into().unwrap(), desc, - git::refname!("master"), + git::fmt::refname!("master"), Visibility::default(), signer, &storage, @@ -92,7 +92,7 @@ where &working, "acme".try_into().unwrap(), "Acme's repository", - git::refname!("master"), + git::fmt::refname!("master"), Visibility::default(), signer, storage, @@ -131,19 +131,21 @@ fn repository_with>( config.set_str("user.name", USER_NAME).unwrap(); config.set_str("user.email", USER_EMAIL).unwrap(); } + let sig = git::raw::Signature::new( USER_NAME, USER_EMAIL, &git::raw::Time::new(RADICLE_EPOCH, 0), ) .unwrap(); + let head = git::initial_commit(&repo, &sig).unwrap(); let tree = git::write_tree(Path::new("README"), "Hello World!\n".as_bytes(), &repo).unwrap(); let oid = { let commit = git::commit( &repo, &head, - git::refname!("refs/heads/master").as_refstr(), + git::fmt::refname!("refs/heads/master").as_refstr(), "Second commit", &sig, &tree, @@ -203,7 +205,7 @@ pub fn tag( } /// Populate a repository with commits, branches and blobs. -pub fn populate(repo: &git::raw::Repository, scale: usize) -> Vec { +pub fn populate(repo: &git::raw::Repository, scale: usize) -> Vec { assert!( scale <= 8, "Scale parameter must be less than or equal to 8" @@ -221,8 +223,8 @@ pub fn populate(repo: &git::raw::Repository, scale: usize) -> Vec() .to_lowercase(); - let name = - git::refname!("feature").join(git::RefString::try_from(random.as_str()).unwrap()); + let name = git::fmt::refname!("feature") + .join(git::fmt::RefString::try_from(random.as_str()).unwrap()); let signature = git::raw::Signature::now("Radicle", "radicle@radicle.xyz").unwrap(); rng.fill(&mut buffer); @@ -244,7 +246,7 @@ pub fn populate(repo: &git::raw::Repository, scale: usize) -> Vec &git::UserInfo { + fn info(&self) -> &crate::git::UserInfo { &self.info } @@ -215,10 +216,12 @@ impl ReadRepository for MockRepository { todo!() } - fn commit(&self, oid: Oid) -> Result { - Err(git_ext::Error::NotFound(git_ext::NotFound::NoSuchObject( - *oid, - ))) + fn commit(&self, oid: Oid) -> Result { + Err(git::raw::Error::new( + git::raw::ErrorCode::NotFound, + git::raw::ErrorClass::None, + format!("commit {oid} not found"), + )) } fn revwalk(&self, _head: Oid) -> Result { @@ -232,39 +235,39 @@ impl ReadRepository for MockRepository { .any(|sigrefs| sigrefs.at == oid || sigrefs.refs.values().any(|oid_| *oid_ == oid))) } - fn is_ancestor_of(&self, _ancestor: Oid, _head: Oid) -> Result { + fn is_ancestor_of(&self, _ancestor: Oid, _head: Oid) -> Result { Ok(true) } - fn blob(&self, _oid: Oid) -> Result { + fn blob(&self, _oid: Oid) -> Result { todo!() } fn blob_at>( &self, - _oid: git_ext::Oid, + _oid: Oid, _path: P, - ) -> Result { + ) -> Result { todo!() } fn reference( &self, _remote: &RemoteId, - _reference: &git::Qualified, - ) -> Result { + _reference: &git::fmt::Qualified, + ) -> Result { todo!() } fn reference_oid( &self, remote: &RemoteId, - reference: &git::Qualified, - ) -> Result { + reference: &crate::git::fmt::Qualified, + ) -> Result { let not_found = || { - git::raw::Error::new( - git::raw::ErrorCode::NotFound, - git::raw::ErrorClass::Reference, + crate::git::raw::Error::new( + crate::git::raw::ErrorCode::NotFound, + crate::git::raw::ErrorClass::Reference, format!("could not find {reference} for {remote}"), ) }; @@ -283,8 +286,8 @@ impl ReadRepository for MockRepository { fn references_glob( &self, - _pattern: &git::PatternStr, - ) -> Result, git::ext::Error> { + _pattern: &crate::git::fmt::refspec::PatternStr, + ) -> Result, crate::git::raw::Error> { todo!() } @@ -300,7 +303,7 @@ impl ReadRepository for MockRepository { self.canonical_identity_head() } - fn identity_head_of(&self, _remote: &RemoteId) -> Result { + fn identity_head_of(&self, _remote: &RemoteId) -> Result { Ok(self.doc.commit) } @@ -316,7 +319,7 @@ impl ReadRepository for MockRepository { Ok(self.doc.commit) } - fn merge_base(&self, _left: &Oid, _right: &Oid) -> Result { + fn merge_base(&self, _left: &Oid, _right: &Oid) -> Result { todo!() } } @@ -342,7 +345,7 @@ impl WriteRepository for MockRepository { todo!() } - fn set_user(&self, _info: &git::UserInfo) -> Result<(), Error> { + fn set_user(&self, _info: &crate::git::UserInfo) -> Result<(), Error> { todo!() } }