workspace: A Little Less `git2`

Make `git2` an *optional* dependency of `radicle-cob`, and refactor
`radicle` to depend on crates that in turn do not depend on `git2`
*non-optionally*

The main offending dependency of `radicle-cob` is `radicle-git-ext`
from the `radicle-git` workspace in repository
(rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) which *non-optionally* depends on
`git2`.

So, to achieve removal of this dependency:

 1. The crate is refactored to depend on the new crates
    `radicle-git-ref-format` `radicle-git-metadata`, and
    `radicle-oid` introduced in the previous commits, instead of
    `radicle-git-ext`.
 2. Some code from the `radicle-git-ext` crate in the `radicle-git`
    workspace in repository (rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) is
    copied. See `crates/radicle-cob/src/backend/git/commit.rs`.

This cascades to `radicle` and its dependents.

  Firstly, the there is an
`impl Deref<Target=git2::Oid> for radicle_git_ext::Oid`. This made
it very convenient to just deref to obtain the wrapped `git2::Oid`,
so there are many expressions of the shape `*oid` in `radicle` and
its dependents. However `radicle-oid` does not provide
`impl Deref<Target=git2::Oid> for radicle_oid::Oid`, as notably,
`Target` is an associated type, and not a type parameter, so an
implementation of `Deref` would tie the new `Oid` too tightly to a
particular implementation.
Instead, work with `impl From<radicle_oid::Oid> for git2::Oid`
(which can be enabled using the feature flag `radicle-oid/git2`).
This explains the changes from `*oid` to `oid.into()` at every
transition to "`git2` land".

  Secondly, `radicle` and its dependents are refactored to also depend
on `radicle-git-ref-format` and (much less prominently)
`radicle-git-metadata` instead of `radicle-git-ext`
This is to avoid pulling in `git2` via these dependencies.

  Thirdly, as the re-exports in `crates/radicle/src/git.rs` change,
they are at the same time also cleaned up. Notably, the types from
`radicle-git-ref-format` are re-exported under `fmt` only, not "twice".

While overall this obviously is very much a breaking change, these
changes should mostly amount to changing from `Deref` to `Into`, i.e.,
`*oid` → `oid.into()` and rewriting imports for `radicle::git::fmt`.
This is indicated by the mostly mechanical nature of the changes to
`crates/radicle-{cli,node,remote-helper}`.
This commit is contained in:
Lorenz Leutgeb 2025-10-04 11:59:26 +02:00
parent f4a8908727
commit b7cfcfff75
No known key found for this signature in database
124 changed files with 1540 additions and 1196 deletions

90
Cargo.lock generated
View File

@ -239,12 +239,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.7" version = "0.21.7"
@ -1119,9 +1113,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]] [[package]]
name = "git-ref-format" name = "git-ref-format"
version = "0.3.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7428e0d6e549a9a613d6f019b839a0f5142c331295b79e119ca8f4faac145da1" checksum = "76314f6eb43910ebc5eb89a1f0728724a6b9144dabd18bca4e7cc7c01c3804e3"
dependencies = [ dependencies = [
"git-ref-format-core", "git-ref-format-core",
"git-ref-format-macro", "git-ref-format-macro",
@ -1129,9 +1123,9 @@ dependencies = [
[[package]] [[package]]
name = "git-ref-format-core" name = "git-ref-format-core"
version = "0.3.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaeb9672a55e9e32cb6d3ef781e7526b25ab97d499fae71615649340b143424" checksum = "ae6d9ee666ca7d4ad49cbf7174f785f299a18b37565694f665e8c7df24999cdd"
dependencies = [ dependencies = [
"bstr", "bstr",
"serde", "serde",
@ -1140,12 +1134,12 @@ dependencies = [
[[package]] [[package]]
name = "git-ref-format-macro" name = "git-ref-format-macro"
version = "0.3.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b6ca5353accc201f6324dff744ba4660099546d4daf187ba868f07562e36ca4" checksum = "2dc2ded12a6ea2b6e63afaf09415e4c15bf4baa74e530acd9daed9ff47e7dd41"
dependencies = [ dependencies = [
"git-ref-format-core", "git-ref-format-core",
"proc-macro-error", "proc-macro-error2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.89",
] ]
@ -2238,12 +2232,6 @@ dependencies = [
"cyphergraphy", "cyphergraphy",
] ]
[[package]]
name = "nonempty"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff7ac1e5ea23db6d61ad103e91864675049644bf47c35912336352fa4e9c109"
[[package]] [[package]]
name = "nonempty" name = "nonempty"
version = "0.9.0" version = "0.9.0"
@ -2643,27 +2631,25 @@ dependencies = [
] ]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error-attr2"
version = "1.0.4" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [ dependencies = [
"proc-macro-error-attr",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.109",
"version_check",
] ]
[[package]] [[package]]
name = "proc-macro-error-attr" name = "proc-macro-error2"
version = "1.0.4" version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [ dependencies = [
"proc-macro-error-attr2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"version_check", "syn 2.0.89",
] ]
[[package]] [[package]]
@ -2751,13 +2737,15 @@ dependencies = [
"localtime", "localtime",
"log", "log",
"multibase", "multibase",
"nonempty 0.9.0", "nonempty",
"pretty_assertions", "pretty_assertions",
"qcheck", "qcheck",
"qcheck-macros", "qcheck-macros",
"radicle-cob", "radicle-cob",
"radicle-crypto", "radicle-crypto",
"radicle-git-ext", "radicle-git-metadata",
"radicle-git-ref-format",
"radicle-oid",
"radicle-ssh", "radicle-ssh",
"schemars", "schemars",
"serde", "serde",
@ -2779,19 +2767,18 @@ dependencies = [
"chrono", "chrono",
"clap", "clap",
"dunce", "dunce",
"git-ref-format",
"human-panic", "human-panic",
"itertools", "itertools",
"lexopt", "lexopt",
"localtime", "localtime",
"log", "log",
"nonempty 0.9.0", "nonempty",
"pretty_assertions", "pretty_assertions",
"radicle", "radicle",
"radicle-cli-test", "radicle-cli-test",
"radicle-cob", "radicle-cob",
"radicle-crypto", "radicle-crypto",
"radicle-git-ext", "radicle-git-ref-format",
"radicle-node", "radicle-node",
"radicle-surf", "radicle-surf",
"radicle-term", "radicle-term",
@ -2837,14 +2824,17 @@ name = "radicle-cob"
version = "0.17.0" version = "0.17.0"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"git-ref-format-core",
"git2", "git2",
"log", "log",
"nonempty 0.9.0", "nonempty",
"qcheck", "qcheck",
"qcheck-macros", "qcheck-macros",
"radicle-crypto", "radicle-crypto",
"radicle-dag", "radicle-dag",
"radicle-git-ext", "radicle-git-metadata",
"radicle-git-ref-format",
"radicle-oid",
"serde", "serde",
"serde_json", "serde_json",
"signature 2.2.0", "signature 2.2.0",
@ -2894,17 +2884,18 @@ dependencies = [
"gix-protocol", "gix-protocol",
"gix-transport", "gix-transport",
"log", "log",
"nonempty 0.9.0", "nonempty",
"radicle", "radicle",
"radicle-git-ext", "radicle-git-ref-format",
"radicle-oid",
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
[[package]] [[package]]
name = "radicle-git-ext" name = "radicle-git-ext"
version = "0.8.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b78c26e67d1712ad5a0c602ae3b236609461372ac04e200bda359fe4a1c6650" checksum = "fb3de6999a8ff570e0dd92a04ba3132853dd08a3bcfc2f7faf56de7b1bd36053"
dependencies = [ dependencies = [
"git-ref-format", "git-ref-format",
"git2", "git2",
@ -2944,13 +2935,12 @@ dependencies = [
"localtime", "localtime",
"log", "log",
"mio 1.0.4", "mio 1.0.4",
"nonempty 0.9.0", "nonempty",
"qcheck", "qcheck",
"qcheck-macros", "qcheck-macros",
"radicle", "radicle",
"radicle-crypto", "radicle-crypto",
"radicle-fetch", "radicle-fetch",
"radicle-git-ext",
"radicle-protocol", "radicle-protocol",
"radicle-signals", "radicle-signals",
"radicle-systemd", "radicle-systemd",
@ -2991,14 +2981,13 @@ dependencies = [
"fastrand", "fastrand",
"localtime", "localtime",
"log", "log",
"nonempty 0.9.0", "nonempty",
"paste", "paste",
"qcheck", "qcheck",
"qcheck-macros", "qcheck-macros",
"radicle", "radicle",
"radicle-crypto", "radicle-crypto",
"radicle-fetch", "radicle-fetch",
"radicle-git-ext",
"scrypt", "scrypt",
"serde", "serde",
"serde_json", "serde_json",
@ -3015,7 +3004,6 @@ dependencies = [
"radicle", "radicle",
"radicle-cli", "radicle-cli",
"radicle-crypto", "radicle-crypto",
"radicle-git-ext",
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
@ -3049,22 +3037,22 @@ dependencies = [
[[package]] [[package]]
name = "radicle-std-ext" name = "radicle-std-ext"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db20136bbc9ae63f3fec8e5a6c369f4902fac2244501b5dfc6d668e43475aaa4" checksum = "5310e7a04506b6ce92dc9c47b26bd24c1c680937a3dcd13cd20847f89dbda32a"
[[package]] [[package]]
name = "radicle-surf" name = "radicle-surf"
version = "0.22.0" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb308c3989087f71e43d8c7a2737273fdc7fbcd3e6628af81a42f601ae64f314" checksum = "2f08adc954d4a49287d86bd1ce14ca7e34a11a3acd904044db30551ca8f4e46a"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.13.1", "base64 0.21.7",
"flate2", "flate2",
"git2", "git2",
"log", "log",
"nonempty 0.5.0", "nonempty",
"radicle-git-ext", "radicle-git-ext",
"radicle-std-ext", "radicle-std-ext",
"tar", "tar",

View File

@ -28,8 +28,6 @@ crossbeam-channel = "0.5.6"
cyphernet = "0.5.2" cyphernet = "0.5.2"
dunce = "1.0.5" dunce = "1.0.5"
fastrand = { version = "2.0.0", default-features = false } 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 } git2 = { version = "0.19.0", default-features = false }
human-panic = "2" human-panic = "2"
itertools = "0.14" 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-crypto = { version = "0.14", path = "crates/radicle-crypto" }
radicle-dag = { version = "0.10", path = "crates/radicle-dag" } radicle-dag = { version = "0.10", path = "crates/radicle-dag" }
radicle-fetch = { version = "0.16", path = "crates/radicle-fetch" } 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-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-node = { version = "0.16", path = "crates/radicle-node" }
radicle-oid = { version = "0.1.0", path = "crates/radicle-oid", default-features = false } radicle-oid = { version = "0.1.0", path = "crates/radicle-oid", default-features = false }
@ -70,6 +68,13 @@ thiserror = "1.0"
winpipe = "0.1.1" winpipe = "0.1.1"
zeroize = "1.5.7" 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] [workspace.lints]
clippy.type_complexity = "allow" clippy.type_complexity = "allow"
clippy.enum_variant_names = "allow" clippy.enum_variant_names = "allow"

View File

@ -18,7 +18,6 @@ anyhow = "1"
chrono = { workspace = true, features = ["clock", "std"] } chrono = { workspace = true, features = ["clock", "std"] }
clap = { version = "4.5.44", features = ["derive"] } clap = { version = "4.5.44", features = ["derive"] }
dunce = { workspace = true } dunce = { workspace = true }
git-ref-format = { version = "0.3.0", features = ["macro"] }
human-panic.workspace = true human-panic.workspace = true
itertools.workspace = true itertools.workspace = true
lexopt = { workspace = true } lexopt = { workspace = true }
@ -28,10 +27,8 @@ nonempty = { workspace = true }
radicle = { workspace = true, features = ["logger", "schemars"] } radicle = { workspace = true, features = ["logger", "schemars"] }
radicle-cob = { workspace = true } radicle-cob = { workspace = true }
radicle-crypto = { workspace = true } radicle-crypto = { workspace = true }
# N.b. this is required to use macros, even though it's re-exported radicle-git-ref-format = { workspace = true, features = ["macro"] }
# through radicle radicle-surf = { workspace = true }
radicle-git-ext = { workspace = true, features = ["serde"] }
radicle-surf = "0.22.0"
radicle-term = { workspace = true } radicle-term = { workspace = true }
schemars = { workspace = true } schemars = { workspace = true }
serde = { workspace = true } serde = { workspace = true }

View File

@ -156,9 +156,9 @@ pub fn setup_remotes(
pub fn setup_remote( pub fn setup_remote(
setup: &project::SetupRemote, setup: &project::SetupRemote,
remote_id: &NodeId, remote_id: &NodeId,
remote_name: Option<git::RefString>, remote_name: Option<git::fmt::RefString>,
aliases: &impl AliasStore, aliases: &impl AliasStore,
) -> anyhow::Result<git::RefString> { ) -> anyhow::Result<git::fmt::RefString> {
let remote_name = if let Some(name) = remote_name { let remote_name = if let Some(name) = remote_name {
name name
} else { } else {
@ -167,7 +167,7 @@ pub fn setup_remote(
} else { } else {
remote_id.to_human() 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}'"))? .map_err(|_| anyhow!("invalid remote name: '{name}'"))?
}; };
let (remote, branch) = setup.run(&remote_name, *remote_id)?; let (remote, branch) = setup.run(&remote_name, *remote_id)?;

View File

@ -4,16 +4,17 @@ use std::process;
use anyhow::anyhow; use anyhow::anyhow;
use git_ref_format::Qualified;
use localtime::LocalTime; use localtime::LocalTime;
use radicle::cob::TypedId; use radicle::cob::TypedId;
use radicle::git::fmt::Qualified;
use radicle::git::BranchName;
use radicle::identity::Identity; use radicle::identity::Identity;
use radicle::issue::cache::Issues as _; use radicle::issue::cache::Issues as _;
use radicle::node::notifications; use radicle::node::notifications;
use radicle::node::notifications::*; use radicle::node::notifications::*;
use radicle::patch::cache::Patches as _; use radicle::patch::cache::Patches as _;
use radicle::prelude::{NodeId, Profile, RepoId}; use radicle::prelude::{NodeId, Profile, RepoId};
use radicle::storage::{BranchName, ReadRepository, ReadStorage}; use radicle::storage::{ReadRepository, ReadStorage};
use radicle::{cob, git, Storage}; use radicle::{cob, git, Storage};
use term::Element as _; use term::Element as _;

View File

@ -16,9 +16,9 @@ use serde_json as json;
use radicle::crypto::ssh; use radicle::crypto::ssh;
use radicle::explorer::ExplorerUrl; use radicle::explorer::ExplorerUrl;
use radicle::git::fmt::RefString;
use radicle::git::raw; use radicle::git::raw;
use radicle::git::raw::ErrorExt as _; use radicle::git::raw::ErrorExt as _;
use radicle::git::RefString;
use radicle::identity::project::ProjectName; use radicle::identity::project::ProjectName;
use radicle::identity::{Doc, RepoId, Visibility}; use radicle::identity::{Doc, RepoId, Visibility};
use radicle::node::events::UploadPack; use radicle::node::events::UploadPack;

View File

@ -24,7 +24,7 @@ use anyhow::anyhow;
use radicle::cob::patch::PatchId; use radicle::cob::patch::PatchId;
use radicle::cob::{patch, Label, Reaction}; use radicle::cob::{patch, Label, Reaction};
use radicle::git::RefString; use radicle::git::fmt::RefString;
use radicle::patch::cache::Patches as _; use radicle::patch::cache::Patches as _;
use radicle::storage::git::transport; use radicle::storage::git::transport;
use radicle::{prelude::*, Node}; use radicle::{prelude::*, Node};

View File

@ -1,10 +1,10 @@
use anyhow::anyhow; use anyhow::anyhow;
use git_ref_format::Qualified;
use radicle::cob::patch; use radicle::cob::patch;
use radicle::cob::patch::RevisionId; use radicle::cob::patch::RevisionId;
use radicle::git::fmt::Qualified;
use radicle::git::fmt::RefString;
use radicle::git::raw::ErrorExt as _; use radicle::git::raw::ErrorExt as _;
use radicle::git::RefString;
use radicle::patch::cache::Patches as _; use radicle::patch::cache::Patches as _;
use radicle::patch::PatchId; use radicle::patch::PatchId;
use radicle::storage::git::Repository; use radicle::storage::git::Repository;
@ -25,7 +25,7 @@ impl Options {
Some(refname) => Ok(Qualified::from_refstr(refname) Some(refname) => Ok(Qualified::from_refstr(refname)
.map_or_else(|| refname.clone(), |q| q.to_ref_string())), .map_or_else(|| refname.clone(), |q| q.to_ref_string())),
// SAFETY: Patch IDs are valid refstrings. // 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())), .join(RefString::try_from(term::format::cob(id).item).unwrap())),
} }
} }
@ -57,8 +57,7 @@ pub fn run(
let mut spinner = term::spinner("Performing checkout..."); let mut spinner = term::spinner("Performing checkout...");
let patch_branch = opts.branch(patch_id)?; let patch_branch = opts.branch(patch_id)?;
let commit = let commit = match working.find_branch(patch_branch.as_str(), git::raw::BranchType::Local) {
match working.find_branch(patch_branch.as_str(), radicle::git::raw::BranchType::Local) {
Ok(branch) if opts.force => { Ok(branch) if opts.force => {
let commit = find_patch_commit(revision, stored, working)?; let commit = find_patch_commit(revision, stored, working)?;
let mut r = branch.into_reference(); let mut r = branch.into_reference();
@ -67,7 +66,7 @@ pub fn run(
} }
Ok(branch) => { Ok(branch) => {
let head = branch.get().peel_to_commit()?; let head = branch.get().peel_to_commit()?;
if head.id() != *revision.head() { if revision.head() != head.id() {
anyhow::bail!( anyhow::bail!(
"branch '{patch_branch}' already exists (use `--force` to overwrite)" "branch '{patch_branch}' already exists (use `--force` to overwrite)"
); );
@ -84,7 +83,7 @@ pub fn run(
}; };
if opts.force { if opts.force {
let mut builder = radicle::git::raw::build::CheckoutBuilder::new(); let mut builder = git::raw::build::CheckoutBuilder::new();
builder.force(); builder.force();
working.checkout_tree(commit.as_object(), Some(&mut builder))?; working.checkout_tree(commit.as_object(), Some(&mut builder))?;
} else { } else {
@ -125,7 +124,7 @@ fn find_patch_commit<'a>(
stored: &Repository, stored: &Repository,
working: &'a git::raw::Repository, working: &'a git::raw::Repository,
) -> anyhow::Result<git::raw::Commit<'a>> { ) -> anyhow::Result<git::raw::Commit<'a>> {
let head = *revision.head(); let head = revision.head().into();
match working.find_commit(head) { match working.find_commit(head) {
Ok(commit) => Ok(commit), Ok(commit) => Ok(commit),

View File

@ -23,10 +23,10 @@ use radicle::cob::patch::{PatchId, Revision, Verdict};
use radicle::cob::{CodeLocation, CodeRange}; use radicle::cob::{CodeLocation, CodeRange};
use radicle::crypto; use radicle::crypto;
use radicle::git; use radicle::git;
use radicle::git::Oid;
use radicle::node::device::Device; use radicle::node::device::Device;
use radicle::prelude::*; use radicle::prelude::*;
use radicle::storage::git::{cob::DraftStore, Repository}; use radicle::storage::git::{cob::DraftStore, Repository};
use radicle_git_ext::Oid;
use radicle_surf::diff::*; use radicle_surf::diff::*;
use radicle_term::{Element, VStack}; use radicle_term::{Element, VStack};
@ -196,25 +196,28 @@ impl ReviewItem {
fn paths(&self) -> (Option<(&Path, Oid)>, Option<(&Path, Oid)>) { fn paths(&self) -> (Option<(&Path, Oid)>, Option<(&Path, Oid)>) {
match self { match self {
Self::FileAdded { path, new, .. } => (None, Some((path, new.oid))), Self::FileAdded { path, new, .. } => (None, Some((path, Oid::from(*new.oid)))),
Self::FileDeleted { path, old, .. } => (Some((path, old.oid)), None), Self::FileDeleted { path, old, .. } => (Some((path, Oid::from(*old.oid))), None),
Self::FileMoved { moved } => ( Self::FileMoved { moved } => (
Some((&moved.old_path, moved.old.oid)), Some((&moved.old_path, Oid::from(*moved.old.oid))),
Some((&moved.new_path, moved.new.oid)), Some((&moved.new_path, Oid::from(*moved.new.oid))),
), ),
Self::FileCopied { copied } => ( Self::FileCopied { copied } => (
Some((&copied.old_path, copied.old.oid)), Some((&copied.old_path, Oid::from(*copied.old.oid))),
Some((&copied.new_path, copied.new.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. /// of changes introduced by a patch.
pub struct Brain<'a> { pub struct Brain<'a> {
/// Where the review draft is being stored. /// Where the review draft is being stored.
refname: git::Namespaced<'a>, refname: git::fmt::Namespaced<'a>,
/// The commit pointed to by the ref. /// The commit pointed to by the ref.
head: git::raw::Commit<'a>, head: git::raw::Commit<'a>,
/// The tree of accepted changes pointed to by the head commit. /// 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. /// 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) git::refs::storage::draft::review(remote, patch)
} }
} }

View File

@ -1,5 +1,6 @@
use radicle::cob::patch; use radicle::cob::patch;
use radicle::git; use radicle::git;
use radicle::git::Oid;
use radicle::prelude::*; use radicle::prelude::*;
use radicle::storage::git::Repository; use radicle::storage::git::Repository;
@ -9,7 +10,7 @@ use crate::terminal::patch::*;
/// Run patch update. /// Run patch update.
pub fn run( pub fn run(
patch_id: patch::PatchId, patch_id: patch::PatchId,
base_id: Option<git::raw::Oid>, base_id: Option<Oid>,
message: term::patch::Message, message: term::patch::Message,
profile: &Profile, profile: &Profile,
repository: &Repository, repository: &Repository,
@ -27,22 +28,25 @@ pub fn run(
let head_oid = branch_oid(&head_branch)?; let head_oid = branch_oid(&head_branch)?;
let base_oid = match base_id { let base_oid = match base_id {
Some(oid) => oid, 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 // N.b. we don't update if both the head and base are the same as
// any previous revision // any previous revision
if patch if patch
.revisions() .revisions()
.any(|(_, revision)| revision.head() == head_oid && **revision.base() == base_oid) .any(|(_, revision)| revision.head() == head_oid && *revision.base() == base_oid)
{ {
return Ok(()); return Ok(());
} }
let (_, revision) = patch.latest(); 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 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); term::print(revision);

View File

@ -8,7 +8,7 @@ use std::ffi::OsString;
use anyhow::anyhow; use anyhow::anyhow;
use radicle::git::RefString; use radicle::git::fmt::RefString;
use radicle::prelude::NodeId; use radicle::prelude::NodeId;
use radicle::storage::ReadStorage; use radicle::storage::ReadStorage;

View File

@ -1,7 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use radicle::git; use radicle::git;
use radicle::git::RefString; use radicle::git::fmt::RefString;
use radicle::prelude::*; use radicle::prelude::*;
use radicle::Profile; use radicle::Profile;
use radicle_crypto::PublicKey; use radicle_crypto::PublicKey;

View File

@ -71,7 +71,7 @@ pub fn run(_args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let remote = remote?; let remote = remote?;
let sigrefs = repo.reference_oid(&remote, &git::refs::storage::SIGREFS_BRANCH)?; let sigrefs = repo.reference_oid(&remote, &git::refs::storage::SIGREFS_BRANCH)?;
let mut walk = repo.raw().revwalk()?; let mut walk = repo.raw().revwalk()?;
walk.push(*sigrefs)?; walk.push(sigrefs.into())?;
stats.local.pushes += walk.count(); stats.local.pushes += walk.count();
stats.local.forks += 1; stats.local.forks += 1;

View File

@ -41,7 +41,7 @@ Options
pub struct Options { pub struct Options {
rid: Option<RepoId>, rid: Option<RepoId>,
refstr: git::RefString, refstr: git::fmt::RefString,
target: Option<git::Oid>, target: Option<git::Oid>,
nid: Option<NodeId>, nid: Option<NodeId>,
interval: time::Duration, interval: time::Duration,
@ -56,7 +56,7 @@ impl Args for Options {
let mut rid = None; let mut rid = None;
let mut nid: Option<NodeId> = None; let mut nid: Option<NodeId> = None;
let mut target: Option<git::Oid> = None; let mut target: Option<git::Oid> = None;
let mut refstr: Option<git::RefString> = None; let mut refstr: Option<git::fmt::RefString> = None;
let mut interval: Option<time::Duration> = None; let mut interval: Option<time::Duration> = None;
let mut timeout: time::Duration = time::Duration::MAX; 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<R: ReadRepository>( fn reference<R: ReadRepository>(
repo: &R, repo: &R,
nid: &NodeId, nid: &NodeId,
qual: &git::Qualified, qual: &git::fmt::Qualified,
) -> Result<Option<git::Oid>, git::raw::Error> { ) -> Result<Option<git::Oid>, git::raw::Error> {
match repo.reference_oid(nid, qual) { match repo.reference_oid(nid, qual) {
Ok(oid) => Ok(Some(oid)), Ok(oid) => Ok(Some(oid)),

View File

@ -20,14 +20,15 @@ use thiserror::Error;
use radicle::crypto::ssh; use radicle::crypto::ssh;
use radicle::git; use radicle::git;
use radicle::git::raw as git2;
use radicle::git::{Version, VERSION_REQUIRED}; use radicle::git::{Version, VERSION_REQUIRED};
use radicle::prelude::{NodeId, RepoId}; use radicle::prelude::{NodeId, RepoId};
use radicle::storage::git::transport; use radicle::storage::git::transport;
pub use radicle::git::Oid;
pub use radicle::git::raw::{ pub use radicle::git::raw::{
build::CheckoutBuilder, AnnotatedCommit, Commit, Direction, ErrorCode, MergeAnalysis, build::CheckoutBuilder, AnnotatedCommit, Commit, Direction, ErrorCode, ErrorExt as _,
MergeOptions, Oid, Reference, Repository, Signature, MergeAnalysis, MergeOptions, Reference, Repository, Signature,
}; };
pub const CONFIG_COMMIT_GPG_SIGN: &str = "commit.gpgsign"; pub const CONFIG_COMMIT_GPG_SIGN: &str = "commit.gpgsign";
@ -46,10 +47,10 @@ impl Rev {
&self.0 &self.0
} }
/// Resolve the revision to an [`From<git2::Oid>`]. /// Resolve the revision to an [`From<git::raw::Oid>`].
pub fn resolve<T>(&self, repo: &git2::Repository) -> Result<T, git2::Error> pub fn resolve<T>(&self, repo: &Repository) -> Result<T, git::raw::Error>
where where
T: From<git2::Oid>, T: From<git::raw::Oid>,
{ {
let object = repo.revparse_single(self.as_str())?; let object = repo.revparse_single(self.as_str())?;
Ok(object.id().into()) Ok(object.id().into())
@ -84,13 +85,13 @@ pub struct Remote<'a> {
pub url: radicle::git::Url, pub url: radicle::git::Url,
pub pushurl: Option<radicle::git::Url>, pub pushurl: Option<radicle::git::Url>,
inner: git2::Remote<'a>, inner: git::raw::Remote<'a>,
} }
impl<'a> TryFrom<git2::Remote<'a>> for Remote<'a> { impl<'a> TryFrom<git::raw::Remote<'a>> for Remote<'a> {
type Error = RemoteError; type Error = RemoteError;
fn try_from(value: git2::Remote<'a>) -> Result<Self, Self::Error> { fn try_from(value: git::raw::Remote<'a>) -> Result<Self, Self::Error> {
let url = value.url().map_or(Err(RemoteError::MissingUrl), |url| { let url = value.url().map_or(Err(RemoteError::MissingUrl), |url| {
Ok(radicle::git::Url::from_str(url)?) Ok(radicle::git::Url::from_str(url)?)
})?; })?;
@ -110,7 +111,7 @@ impl<'a> TryFrom<git2::Remote<'a>> for Remote<'a> {
} }
impl<'a> Deref 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 { fn deref(&self) -> &Self::Target {
&self.inner &self.inner
@ -250,7 +251,7 @@ pub fn is_signing_configured(repo: &Path) -> Result<bool, anyhow::Error> {
} }
/// Return the list of radicle remotes for the given repository. /// Return the list of radicle remotes for the given repository.
pub fn rad_remotes(repo: &git2::Repository) -> anyhow::Result<Vec<Remote>> { pub fn rad_remotes(repo: &Repository) -> anyhow::Result<Vec<Remote>> {
let remotes: Vec<_> = repo let remotes: Vec<_> = repo
.remotes()? .remotes()?
.iter() .iter()
@ -263,16 +264,16 @@ pub fn rad_remotes(repo: &git2::Repository) -> anyhow::Result<Vec<Remote>> {
} }
/// Check if the git remote is configured for the `Repository`. /// Check if the git remote is configured for the `Repository`.
pub fn is_remote(repo: &git2::Repository, alias: &str) -> anyhow::Result<bool> { pub fn is_remote(repo: &Repository, alias: &str) -> anyhow::Result<bool> {
match repo.find_remote(alias) { match repo.find_remote(alias) {
Ok(_) => Ok(true), 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()), Err(err) => Err(err.into()),
} }
} }
/// Get the repository's "rad" remote. /// 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) { match radicle::rad::remote(repo) {
Ok((remote, id)) => Ok((remote, id)), Ok((remote, id)) => Ok((remote, id)),
Err(radicle::rad::RemoteError::NotFound(_)) => Err(anyhow!( Err(radicle::rad::RemoteError::NotFound(_)) => Err(anyhow!(
@ -352,12 +353,12 @@ pub fn parse_remote(refspec: &str) -> Option<(NodeId, &str)> {
} }
pub fn add_tag( pub fn add_tag(
repo: &git2::Repository, repo: &Repository,
message: &str, message: &str,
patch_tag_name: &str, patch_tag_name: &str,
) -> anyhow::Result<git2::Oid> { ) -> anyhow::Result<git::raw::Oid> {
let head = repo.head()?; 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)?; let oid = repo.tag(patch_tag_name, &commit, &repo.signature()?, message, false)?;
Ok(oid) Ok(oid)

View File

@ -2,7 +2,7 @@ use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use radicle::git; use radicle::git;
use radicle_git_ext::Oid; use radicle::git::Oid;
use radicle_surf::diff; use radicle_surf::diff;
use radicle_surf::diff::{Added, Copied, Deleted, FileStats, Hunks, Modified, Moved}; use radicle_surf::diff::{Added, Copied, Deleted, FileStats, Hunks, Modified, Moved};
use radicle_surf::diff::{Diff, DiffContent, FileDiff, Hunk, Modification}; use radicle_surf::diff::{Diff, DiffContent, FileDiff, Hunk, Modification};
@ -33,7 +33,7 @@ pub trait Repo {
impl Repo for git::raw::Repository { impl Repo for git::raw::Repository {
fn blob(&self, oid: git::Oid) -> Result<Blob, git::raw::Error> { fn blob(&self, oid: git::Oid) -> Result<Blob, git::raw::Error> {
let blob = self.find_blob(*oid)?; let blob = self.find_blob(oid.into())?;
if blob.is_binary() { if blob.is_binary() {
Ok(Blob::Binary) Ok(Blob::Binary)
@ -338,7 +338,7 @@ impl ToPretty for Added {
repo: &R, repo: &R,
) -> Self::Output { ) -> Self::Output {
let old = None; 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) pretty_modification(header, &self.diff, old, new, repo, hi)
} }
@ -354,7 +354,7 @@ impl ToPretty for Deleted {
header: &Self::Context, header: &Self::Context,
repo: &R, repo: &R,
) -> Self::Output { ) -> 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; let new = None;
pretty_modification(header, &self.diff, old, new, repo, hi) pretty_modification(header, &self.diff, old, new, repo, hi)
@ -371,8 +371,8 @@ impl ToPretty for Modified {
header: &Self::Context, header: &Self::Context,
repo: &R, repo: &R,
) -> Self::Output { ) -> 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 = 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) pretty_modification(header, &self.diff, old, new, repo, hi)
} }
@ -595,8 +595,8 @@ mod test {
use term::Element; use term::Element;
use super::*; use super::*;
use radicle::git::raw::RepositoryOpenFlags; use git::raw::RepositoryOpenFlags;
use radicle::git::raw::{Oid, Repository}; use git::raw::{Oid, Repository};
#[test] #[test]
#[ignore] #[ignore]

View File

@ -7,7 +7,6 @@ use radicle_surf::diff::FileStats;
use thiserror::Error; use thiserror::Error;
use radicle::git; use radicle::git;
use radicle::git::raw::Oid;
use radicle_surf::diff; use radicle_surf::diff;
use radicle_surf::diff::{Diff, DiffContent, DiffFile, FileDiff, Hunk, Hunks, Line, Modification}; 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 { if old.mode == new.mode {
w.meta(format!( w.meta(format!(
"index {}..{} {:o}", "index {}..{} {:o}",
term::format::oid(old.oid), term::format::oid(*old.oid),
term::format::oid(new.oid), term::format::oid(*new.oid),
u32::from(old.mode.clone()), u32::from(old.mode.clone()),
))?; ))?;
} else { } else {
@ -316,8 +315,8 @@ impl Encode for FileHeader {
w.meta(format!("new mode {:o}", u32::from(new.mode.clone())))?; w.meta(format!("new mode {:o}", u32::from(new.mode.clone())))?;
w.meta(format!( w.meta(format!(
"index {}..{}", "index {}..{}",
term::format::oid(old.oid), term::format::oid(*old.oid),
term::format::oid(new.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!("new file mode {:o}", u32::from(new.mode.clone())))?;
w.meta(format!( w.meta(format!(
"index {}..{}", "index {}..{}",
term::format::oid(Oid::zero()), term::format::oid(git::Oid::sha1_zero()),
term::format::oid(new.oid), term::format::oid(*new.oid),
))?; ))?;
w.meta("--- /dev/null")?; w.meta("--- /dev/null")?;
@ -355,8 +354,8 @@ impl Encode for FileHeader {
))?; ))?;
w.meta(format!( w.meta(format!(
"index {}..{}", "index {}..{}",
term::format::oid(old.oid), term::format::oid(*old.oid),
term::format::oid(Oid::zero()) term::format::oid(git::Oid::sha1_zero())
))?; ))?;
w.meta(format!("--- a/{}", path.display()))?; w.meta(format!("--- a/{}", path.display()))?;

View File

@ -1,7 +1,7 @@
use radicle::prelude::*; use radicle::prelude::*;
use crate::git; use crate::git;
use radicle::git::RefStr; use radicle::git::fmt::RefStr;
use radicle::node::NodeId; use radicle::node::NodeId;
/// Setup a repository remote and tracking branch. /// Setup a repository remote and tracking branch.

View File

@ -7,7 +7,7 @@ use anyhow::anyhow;
use radicle::cob::{self, issue, patch}; use radicle::cob::{self, issue, patch};
use radicle::crypto; use radicle::crypto;
use radicle::git::{Oid, RefString}; use radicle::git::{fmt::RefString, Oid};
use radicle::node::{Address, Alias}; use radicle::node::{Address, Alias};
use radicle::prelude::{Did, NodeId, RepoId}; use radicle::prelude::{Did, NodeId, RepoId};

View File

@ -188,8 +188,8 @@ fn message_from_commits(name: &str, commits: Vec<git::raw::Commit>) -> Result<St
/// Return commits between the merge base and a head. /// Return commits between the merge base and a head.
pub fn patch_commits<'a>( pub fn patch_commits<'a>(
repo: &'a git::raw::Repository, repo: &'a git::raw::Repository,
base: &git::Oid, base: &git::raw::Oid,
head: &git::Oid, head: &git::raw::Oid,
) -> Result<Vec<git::raw::Commit<'a>>, git::raw::Error> { ) -> Result<Vec<git::raw::Commit<'a>>, git::raw::Error> {
let mut commits = Vec::new(); let mut commits = Vec::new();
let mut revwalk = repo.revwalk()?; let mut revwalk = repo.revwalk()?;
@ -205,8 +205,8 @@ pub fn patch_commits<'a>(
/// The message shown in the editor when creating a `Patch`. /// The message shown in the editor when creating a `Patch`.
fn create_display_message( fn create_display_message(
repo: &git::raw::Repository, repo: &git::raw::Repository,
base: &git::Oid, base: &git::raw::Oid,
head: &git::Oid, head: &git::raw::Oid,
) -> Result<String, Error> { ) -> Result<String, Error> {
let commits = patch_commits(repo, base, head)?; let commits = patch_commits(repo, base, head)?;
if commits.is_empty() { if commits.is_empty() {
@ -226,8 +226,8 @@ fn create_display_message(
pub fn get_create_message( pub fn get_create_message(
message: term::patch::Message, message: term::patch::Message,
repo: &git::raw::Repository, repo: &git::raw::Repository,
base: &git::Oid, base: &git::raw::Oid,
head: &git::Oid, head: &git::raw::Oid,
) -> Result<(Title, String), Error> { ) -> Result<(Title, String), Error> {
let display_msg = create_display_message(repo, base, head)?; let display_msg = create_display_message(repo, base, head)?;
let message = message.get(&display_msg)?; 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`. /// The message shown in the editor when updating a `Patch`.
fn update_display_message( fn update_display_message(
repo: &git::raw::Repository, repo: &git::raw::Repository,
last_rev_head: &git::Oid, last_rev_head: &git::raw::Oid,
head: &git::Oid, head: &git::raw::Oid,
) -> Result<String, Error> { ) -> Result<String, Error> {
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()); return Ok(REVISION_MSG.trim_start().to_string());
} }
@ -302,9 +302,9 @@ pub fn get_update_message(
message: term::patch::Message, message: term::patch::Message,
repo: &git::raw::Repository, repo: &git::raw::Repository,
latest: &patch::Revision, latest: &patch::Revision,
head: &git::Oid, head: &git::raw::Oid,
) -> Result<String, Error> { ) -> Result<String, Error> {
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.get(&display_msg)?;
let message = message.trim(); let message = message.trim();
@ -366,11 +366,8 @@ pub fn show(
} else { } else {
vec![] vec![]
}; };
let ahead_behind = common::ahead_behind( let ahead_behind =
stored.raw(), common::ahead_behind(stored.raw(), revision.head(), patch.target().head(stored)?)?;
*revision.head(),
*patch.target().head(stored)?,
)?;
let author = patch.author(); let author = patch.author();
let author = term::format::Author::new(author.id(), profile, verbose); let author = term::format::Author::new(author.id(), profile, verbose);
let labels = patch.labels().map(|l| l.to_string()).collect::<Vec<_>>(); let labels = patch.labels().map(|l| l.to_string()).collect::<Vec<_>>();
@ -468,7 +465,7 @@ fn patch_commit_lines(
let (from, to) = patch.range()?; let (from, to) = patch.range()?;
let mut lines = Vec::new(); 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([ lines.push(term::Line::spaced([
term::label(term::format::secondary::<String>( term::label(term::format::secondary::<String>(
term::format::oid(commit.id()).into(), term::format::oid(commit.id()).into(),
@ -484,37 +481,36 @@ fn patch_commit_lines(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use radicle::git::refname; use radicle::git::fmt::refname;
use radicle::test::fixtures; use radicle::test::fixtures;
use std::path; use std::path;
fn commit( fn commit(
repo: &git::raw::Repository, repo: &git::raw::Repository,
branch: &git::RefStr, branch: &git::fmt::RefStr,
parent: &git::Oid, parent: &git::raw::Oid,
msg: &str, msg: &str,
) -> git::Oid { ) -> git::raw::Oid {
let sig = git::raw::Signature::new( let sig = git::raw::Signature::new(
"anonymous", "anonymous",
"anonymous@radicle.example.com", "anonymous@radicle.example.com",
&git::raw::Time::new(0, 0), &git::raw::Time::new(0, 0),
) )
.unwrap(); .unwrap();
let head = repo.find_commit(**parent).unwrap(); let head = repo.find_commit(*parent).unwrap();
let tree = let tree =
git::write_tree(path::Path::new("README"), "Hello World!\n".as_bytes(), repo).unwrap(); git::write_tree(path::Path::new("README"), "Hello World!\n".as_bytes(), repo).unwrap();
let branch = git::refs::branch(branch); let branch = git::refs::branch(branch);
let commit = git::commit(repo, &head, &branch, msg, &sig, &tree).unwrap(); let commit = git::commit(repo, &head, &branch, msg, &sig, &tree).unwrap();
commit.id().into() commit.id()
} }
#[test] #[test]
fn test_create_display_message() { fn test_create_display_message() {
let tmpdir = tempfile::tempdir().unwrap(); let tmpdir = tempfile::tempdir().unwrap();
let (repo, commit_0) = fixtures::repository(&tmpdir); let (repo, commit_0) = fixtures::repository(&tmpdir);
let commit_0 = commit_0.into();
let commit_1 = commit( let commit_1 = commit(
&repo, &repo,
&refname!("feature"), &refname!("feature"),
@ -625,7 +621,6 @@ mod test {
fn test_update_display_message() { fn test_update_display_message() {
let tmpdir = tempfile::tempdir().unwrap(); let tmpdir = tempfile::tempdir().unwrap();
let (repo, commit_0) = fixtures::repository(&tmpdir); 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_1 = commit(&repo, &refname!("feature"), &commit_0, "commit 1\n");
let commit_2 = commit(&repo, &refname!("feature"), &commit_1, "commit 2\n"); let commit_2 = commit(&repo, &refname!("feature"), &commit_1, "commit 2\n");

View File

@ -1,7 +1,7 @@
use anyhow::anyhow; use anyhow::anyhow;
use radicle::git; use radicle::git;
use radicle::git::raw::Oid; use radicle::git::Oid;
use radicle::prelude::*; use radicle::prelude::*;
use radicle::storage::git::Repository; use radicle::storage::git::Repository;
@ -9,7 +9,7 @@ use crate::terminal as term;
/// Give the oid of the branch or an appropriate error. /// Give the oid of the branch or an appropriate error.
#[inline] #[inline]
pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result<git::Oid> { pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result<Oid> {
let oid = branch let oid = branch
.get() .get()
.target() .target()
@ -18,7 +18,7 @@ pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result<git::Oid> {
} }
#[inline] #[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(); let (_, _, head, tail) = git_ref.non_empty_components();
std::iter::once(head).chain(tail).collect() 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( pub fn get_merge_target(
storage: &Repository, storage: &Repository,
head_branch: &git::raw::Branch, 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 (qualified_ref, target_oid) = storage.canonical_head()?;
let head_oid = branch_oid(head_branch)?; 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"); 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. /// Get the diff stats between two commits.
@ -47,8 +49,8 @@ pub fn diff_stats(
old: &Oid, old: &Oid,
new: &Oid, new: &Oid,
) -> Result<git::raw::DiffStats, git::raw::Error> { ) -> Result<git::raw::DiffStats, git::raw::Error> {
let old = repo.find_commit(*old)?; let old = repo.find_commit(old.into())?;
let new = repo.find_commit(*new)?; let new = repo.find_commit(new.into())?;
let old_tree = old.tree()?; let old_tree = old.tree()?;
let new_tree = new.tree()?; let new_tree = new.tree()?;
let mut diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None)?; 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, revision_oid: Oid,
head_oid: Oid, head_oid: Oid,
) -> anyhow::Result<term::Line> { ) -> anyhow::Result<term::Line> {
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 { if a == 0 && b == 0 {
return Ok(term::Line::new(term::format::dim("up to date"))); 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<Vec
continue; continue;
} }
if let (Some(oid), Some(name)) = (&r.target(), &r.shorthand()) { if let (Some(oid), Some(name)) = (&r.target(), &r.shorthand()) {
if oid == target { if target == oid {
branches.push(name.to_string()); branches.push(name.to_string());
}; };
}; };

View File

@ -5,6 +5,7 @@ use localtime::LocalTime;
use radicle::cob::cache::COBS_DB_FILE; use radicle::cob::cache::COBS_DB_FILE;
use radicle::crypto::ssh::{keystore::MemorySigner, Keystore}; use radicle::crypto::ssh::{keystore::MemorySigner, Keystore};
use radicle::crypto::{KeyPair, Seed}; use radicle::crypto::{KeyPair, Seed};
use radicle::git;
use radicle::node::policy::store as policy; use radicle::node::policy::store as policy;
use radicle::node::{self, UserAgent}; use radicle::node::{self, UserAgent};
use radicle::node::{Alias, Config, POLICIES_DB_FILE}; use radicle::node::{Alias, Config, POLICIES_DB_FILE};
@ -233,7 +234,7 @@ impl Environment {
pub fn repository( pub fn repository(
&self, &self,
has_alias: &impl HasAlias, has_alias: &impl HasAlias,
) -> (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()) radicle::test::fixtures::repository(self.work(has_alias).as_path())
} }

View File

@ -17,15 +17,18 @@ rust-version.workspace = true
default = [] default = []
# Only used for testing. Ensures that commit ids are stable. # Only used for testing. Ensures that commit ids are stable.
stable-commit-ids = [] stable-commit-ids = []
test = []
[dependencies] [dependencies]
fastrand = { workspace = true } 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 } log = { workspace = true }
nonempty = { workspace = true, features = ["serialize"] } nonempty = { workspace = true, features = ["serialize"] }
radicle-crypto = { workspace = true, features = ["ssh"] } radicle-crypto = { workspace = true, features = ["ssh"] }
radicle-dag = { workspace = true } 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 = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
signature = { workspace = true } signature = { workspace = true }
@ -36,4 +39,6 @@ fastrand = { workspace = true }
qcheck = { workspace = true } qcheck = { workspace = true }
qcheck-macros = { workspace = true } qcheck-macros = { workspace = true }
radicle-crypto = { workspace = true, features = ["test", "git-ref-format-core"] } 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 } tempfile = { workspace = true }

View File

@ -1,3 +1,7 @@
// Copyright © 2022 The Radicle Link Contributors // Copyright © 2022 The Radicle Link Contributors
#[cfg(feature = "git2")]
pub mod git; pub mod git;
#[cfg(feature = "stable-commit-ids")]
pub mod stable;

View File

@ -1,9 +1,8 @@
// Copyright © 2022 The Radicle Team // Copyright © 2022 The Radicle Team
pub mod change; mod commit;
#[cfg(feature = "stable-commit-ids")] pub mod change;
pub mod stable;
/// Environment variable to set to overwrite the commit date for both the author and the committer. /// Environment variable to set to overwrite the commit date for both the author and the committer.
/// ///

View File

@ -5,11 +5,11 @@ use std::convert::TryFrom;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::LazyLock; use std::sync::LazyLock;
use git_ext::author::Author; use metadata::author::Author;
use git_ext::commit::{headers::Headers, Commit}; use metadata::commit::headers::Headers;
use git_ext::Oid; use metadata::commit::trailers::OwnedTrailer;
use nonempty::NonEmpty; use nonempty::NonEmpty;
use radicle_git_ext::commit::trailers::OwnedTrailer; use oid::Oid;
use crate::change::store::Version; use crate::change::store::Version;
use crate::signatures; use crate::signatures;
@ -21,6 +21,8 @@ use crate::{
trailers, Embed, trailers, Embed,
}; };
use super::commit::Commit;
/// Name of the COB manifest file. /// Name of the COB manifest file.
pub const MANIFEST_BLOB_NAME: &str = "manifest"; pub const MANIFEST_BLOB_NAME: &str = "manifest";
/// Path under which COB embeds are kept. /// Path under which COB embeds are kept.
@ -30,8 +32,7 @@ pub mod error {
use std::str::Utf8Error; use std::str::Utf8Error;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use git_ext::commit; use oid::Oid;
use git_ext::Oid;
use thiserror::Error; use thiserror::Error;
use crate::signatures::error::Signatures; use crate::signatures::error::Signatures;
@ -39,7 +40,7 @@ pub mod error {
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Create { pub enum Create {
#[error(transparent)] #[error(transparent)]
WriteCommit(#[from] commit::error::Write), WriteCommit(#[from] super::super::commit::error::Write),
#[error(transparent)] #[error(transparent)]
FromUtf8(#[from] FromUtf8Error), FromUtf8(#[from] FromUtf8Error),
#[error(transparent)] #[error(transparent)]
@ -55,7 +56,7 @@ pub mod error {
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Load { pub enum Load {
#[error(transparent)] #[error(transparent)]
Read(#[from] commit::error::Read), Read(#[from] super::super::commit::error::Read),
#[error(transparent)] #[error(transparent)]
Signatures(#[from] Signatures), Signatures(#[from] Signatures),
#[error(transparent)] #[error(transparent)]
@ -123,7 +124,7 @@ impl change::Storage for git2::Repository {
let (id, timestamp) = write_commit( let (id, timestamp) = write_commit(
self, self,
resource.map(|o| *o), resource.map(|o| o.into()),
// Commit to tips, extra parents and resource. // Commit to tips, extra parents and resource.
tips.iter() tips.iter()
.cloned() .cloned()
@ -134,7 +135,7 @@ impl change::Storage for git2::Repository {
signature.clone(), signature.clone(),
related related
.iter() .iter()
.map(|p| trailers::CommitTrailer::Related(**p).into()), .map(|p| trailers::CommitTrailer::Related(*p).into()),
tree, tree,
)?; )?;
@ -153,40 +154,34 @@ impl change::Storage for git2::Repository {
fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError> { fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError> {
Ok(self Ok(self
.find_commit(**id)? .find_commit(id.into())?
.parent_ids() .parent_ids()
.map(Oid::from) .map(Oid::from)
.collect::<Vec<_>>()) .collect::<Vec<_>>())
} }
fn manifest_of(&self, id: &Oid) -> Result<crate::Manifest, Self::LoadError> { fn manifest_of(&self, id: &Oid) -> Result<crate::Manifest, Self::LoadError> {
let commit = self.find_commit(**id)?; let commit = self.find_commit(id.into())?;
let tree = commit.tree()?; let tree = commit.tree()?;
load_manifest(self, &tree) load_manifest(self, &tree)
} }
fn load(&self, id: Self::ObjectId) -> Result<Entry, Self::LoadError> { fn load(&self, id: Self::ObjectId) -> Result<Entry, Self::LoadError> {
let commit = Commit::read(self, id.into())?; let commit = super::commit::Commit::read(self, id.into())?;
let timestamp = git2::Time::from(commit.committer().time).seconds() as u64; let timestamp = commit.committer().time.seconds() as u64;
let trailers = parse_trailers(commit.trailers())?; let trailers = parse_trailers(commit.trailers())?;
let (resources, related): (Vec<_>, Vec<_>) = trailers.iter().partition(|t| match t { let (resources, related): (Vec<_>, Vec<_>) = trailers.iter().partition(|t| match t {
CommitTrailer::Resource(_) => true, CommitTrailer::Resource(_) => true,
CommitTrailer::Related(_) => false, CommitTrailer::Related(_) => false,
}); });
let mut resources = resources let mut resources = resources.into_iter().map(|r| r.oid()).collect::<Vec<_>>();
.into_iter() let related = related.into_iter().map(|r| r.oid()).collect::<Vec<_>>();
.map(|r| r.oid().into())
.collect::<Vec<_>>();
let related = related
.into_iter()
.map(|r| r.oid().into())
.collect::<Vec<_>>();
let parents = commit let parents = commit
.parents() .parents()
.map(Oid::from) .map(Oid::from)
.filter(|p| !resources.contains(p) && !related.contains(p)) .filter(|p| !resources.contains(p) && !related.contains(p))
.collect(); .collect();
let mut signatures = Signatures::try_from(&commit)? let mut signatures = Signatures::try_from(&*commit)?
.into_iter() .into_iter()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let Some((key, sig)) = signatures.pop() else { let Some((key, sig)) = signatures.pop() else {
@ -285,7 +280,7 @@ fn write_commit(
) -> Result<(Oid, Timestamp), error::Create> { ) -> Result<(Oid, Timestamp), error::Create> {
let trailers: Vec<OwnedTrailer> = trailers let trailers: Vec<OwnedTrailer> = trailers
.into_iter() .into_iter()
.chain(resource.map(|r| trailers::CommitTrailer::Resource(r).into())) .chain(resource.map(|r| trailers::CommitTrailer::Resource(r.into()).into()))
.collect(); .collect();
let author = repo.signature()?; let author = repo.signature()?;
#[allow(unused_variables)] #[allow(unused_variables)]
@ -299,29 +294,37 @@ fn write_commit(
.map_err(signatures::error::Signatures::from)? .map_err(signatures::error::Signatures::from)?
.as_str(), .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")] #[cfg(feature = "stable-commit-ids")]
// Ensures the commit id doesn't change on every run. // Ensures the commit id doesn't change on every run.
let (author, timestamp) = { let (author, timestamp) = {
let stable = crate::git::stable::read_timestamp(); let stable = crate::stable::read_timestamp();
( (
Author { Author {
time: git_ext::author::Time::new(stable, 0), time: metadata::author::Time::new(stable, 0),
..author ..author
}, },
stable, 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::<i64>() else { let Ok(timestamp) = s.trim().parse::<i64>() else {
panic!( panic!(
"Invalid timestamp value {s:?} for `{}`", "Invalid timestamp value {s:?} for `{}`",
crate::git::GIT_COMMITTER_DATE super::GIT_COMMITTER_DATE
); );
}; };
let author = Author { let author = Author {
time: git_ext::author::Time::new(timestamp, 0), time: metadata::author::Time::new(timestamp, 0),
..author ..author
}; };
(author, timestamp) (author, timestamp)
@ -380,7 +383,7 @@ fn write_manifest(
let oid = embed.content; let oid = embed.content;
let path = PathBuf::from(embed.name); 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()?; let oid = embeds_tree.write()?;

View File

@ -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<Oid, Oid>);
impl Commit {
pub fn new<P, I, T>(
tree: git2::Oid,
parents: P,
author: Author,
committer: Author,
headers: Headers,
message: String,
trailers: I,
) -> Self
where
P: IntoIterator<Item = Oid>,
I: IntoIterator<Item = T>,
OwnedTrailer: From<T>,
{
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<Self, error::Read> {
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<Oid, error::Write> {
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<Self, Self::Error> {
Commit::from_str(str::from_utf8(data)?)
}
}
impl FromStr for Commit {
type Err = error::Parse;
fn from_str(buffer: &str) -> Result<Self, Self::Err> {
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<git2::Oid, git2::Oid>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View File

@ -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<Self, git2::Error> {
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, Self::Err> {
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<Self::Item> {
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),
})
}
}

View File

@ -1,6 +1,6 @@
// Copyright © 2021 The Radicle Link Contributors // Copyright © 2021 The Radicle Link Contributors
use git_ext::Oid; use oid::Oid;
pub mod store; pub mod store;
pub use store::{Contents, EntryId, Storage, Template, Timestamp}; pub use store::{Contents, EntryId, Storage, Template, Timestamp};

View File

@ -3,14 +3,15 @@
use std::{error::Error, fmt, num::NonZeroUsize}; use std::{error::Error, fmt, num::NonZeroUsize};
use nonempty::NonEmpty; use nonempty::NonEmpty;
use radicle_git_ext::Oid; use oid::Oid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::object::collaboration::error::{Create, Update};
use crate::{signatures, TypeName}; use crate::{signatures, TypeName};
/// Change entry storage. /// Change entry storage.
pub trait Storage { pub trait Storage {
type StoreError: Error + Send + Sync + 'static; type StoreError: Error + Send + Sync + 'static + Into<Create> + Into<Update>;
type LoadError: Error + Send + Sync + 'static; type LoadError: Error + Send + Sync + 'static;
type ObjectId; type ObjectId;
@ -194,6 +195,7 @@ pub struct Embed<T = Vec<u8>> {
pub content: T, pub content: T,
} }
#[cfg(feature = "git2")]
impl<T: From<Oid>> Embed<T> { impl<T: From<Oid>> Embed<T> {
/// Create a new embed. /// Create a new embed.
pub fn store( pub fn store(
@ -210,6 +212,7 @@ impl<T: From<Oid>> Embed<T> {
} }
} }
#[cfg(feature = "git2")]
impl Embed<Vec<u8>> { impl Embed<Vec<u8>> {
/// Get the object id of the embedded content. /// Get the object id of the embedded content.
pub fn oid(&self) -> Oid { pub fn oid(&self) -> Oid {

View File

@ -3,8 +3,8 @@
use std::ops::ControlFlow; use std::ops::ControlFlow;
use std::{cmp::Ordering, collections::BTreeSet}; use std::{cmp::Ordering, collections::BTreeSet};
use git_ext::Oid; use dag::Dag;
use radicle_dag::Dag; use oid::Oid;
use crate::{ use crate::{
change, object, object::collaboration::Evaluate, signatures::ExtendedSignature, change, object, object::collaboration::Evaluate, signatures::ExtendedSignature,

View File

@ -2,8 +2,8 @@
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
use std::{cmp::Ordering, collections::BTreeSet, ops::ControlFlow}; use std::{cmp::Ordering, collections::BTreeSet, ops::ControlFlow};
use git_ext::Oid; use dag::Dag;
use radicle_dag::Dag; use oid::Oid;
pub use crate::change::{Contents, Entry, EntryId, Timestamp}; pub use crate::change::{Contents, Entry, EntryId, Timestamp};
@ -77,7 +77,7 @@ impl History {
self.graph.node(id, change); self.graph.node(id, change);
for tip in tips { for tip in tips {
self.graph.dependency(id, (*tip).into()); self.graph.dependency(id, tip);
} }
} }

View File

@ -59,12 +59,20 @@ extern crate qcheck;
#[macro_use(quickcheck)] #[macro_use(quickcheck)]
extern crate qcheck_macros; extern crate qcheck_macros;
extern crate git_ref_format_core as fmt;
extern crate radicle_crypto as crypto; 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; mod backend;
#[cfg(all(any(test, feature = "test"), feature = "git2"))]
pub use backend::git; pub use backend::git;
#[cfg(feature = "stable-commit-ids")]
pub use backend::stable;
mod change_graph; mod change_graph;
mod trailers; mod trailers;
@ -105,19 +113,9 @@ mod tests;
/// ///
/// * [`object::Storage`] /// * [`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 pub trait Store
where where
Self: object::Storage Self: object::Storage
+ change::Storage< + change::Storage<ObjectId = oid::Oid, Parent = oid::Oid, Signatures = ExtendedSignature>,
StoreError = git::change::error::Create,
LoadError = git::change::error::Load,
ObjectId = git_ext::Oid,
Parent = git_ext::Oid,
Signatures = ExtendedSignature,
>,
{ {
} }

View File

@ -1,9 +1,9 @@
// Copyright © 2022 The Radicle Link Contributors // 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 fmt::{Component, RefString};
use git_ext::Oid; use oid::Oid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
@ -19,7 +19,7 @@ pub use storage::{Commit, Objects, Reference, Storage};
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ParseObjectId { pub enum ParseObjectId {
#[error(transparent)] #[error(transparent)]
Git(#[from] git2::Error), Git(#[from] oid::str::ParseOidError),
} }
/// The id of an object /// The id of an object
@ -48,12 +48,14 @@ impl From<&Oid> for ObjectId {
} }
} }
#[cfg(feature = "git2")]
impl From<git2::Oid> for ObjectId { impl From<git2::Oid> for ObjectId {
fn from(oid: git2::Oid) -> Self { fn from(oid: git2::Oid) -> Self {
Oid::from(oid).into() Oid::from(oid).into()
} }
} }
#[cfg(feature = "git2")]
impl From<&git2::Oid> for ObjectId { impl From<&git2::Oid> for ObjectId {
fn from(oid: &git2::Oid) -> Self { fn from(oid: &git2::Oid) -> Self {
ObjectId(Oid::from(*oid)) ObjectId(Oid::from(*oid))
@ -68,8 +70,8 @@ impl Deref for ObjectId {
} }
} }
impl fmt::Display for ObjectId { impl std::fmt::Display for ObjectId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0) write!(f, "{}", self.0)
} }
} }

View File

@ -2,8 +2,8 @@
use std::convert::Infallible; use std::convert::Infallible;
use std::fmt::Debug; use std::fmt::Debug;
use git_ext::Oid;
use nonempty::NonEmpty; use nonempty::NonEmpty;
use oid::Oid;
use crate::change::store::{Manifest, Version}; use crate::change::store::{Manifest, Version};
use crate::{change, Entry, History, ObjectId, TypeName}; use crate::{change, Entry, History, ObjectId, TypeName};
@ -101,11 +101,11 @@ impl<R> Evaluate<R> for NonEmpty<Entry> {
/// [`TypeName`] and [`ObjectId`] from it. /// [`TypeName`] and [`ObjectId`] from it.
/// ///
/// This assumes that the `refname` is in a /// 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 /// `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: /// reference name is of the form:
/// ///
/// `refs/<category>/<typename>/<object_id>[/<rest>*]` /// `refs/<category>/<typename>/<object_id>[/<rest>*]`
@ -115,14 +115,14 @@ impl<R> Evaluate<R> for NonEmpty<Entry> {
/// ///
/// Also note that this will return `None` if: /// 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 [`ObjectId`] fails
/// * The parsing of the [`TypeName`] fails /// * The parsing of the [`TypeName`] fails
pub fn parse_refstr<R>(name: &R) -> Option<(TypeName, ObjectId)> pub fn parse_refstr<R>(name: &R) -> Option<(TypeName, ObjectId)>
where where
R: AsRef<git_ext::ref_format::RefStr>, R: AsRef<fmt::RefStr>,
{ {
use git_ext::ref_format::Qualified; use fmt::Qualified;
let name = name.as_ref(); let name = name.as_ref();
let refs_cobs = match name.to_namespaced() { let refs_cobs = match name.to_namespaced() {
None => Qualified::from_refstr(name)?, None => Qualified::from_refstr(name)?,

View File

@ -4,7 +4,6 @@ use nonempty::NonEmpty;
use crate::Embed; use crate::Embed;
use crate::Evaluate; use crate::Evaluate;
use crate::Store;
use super::*; use super::*;
@ -23,7 +22,7 @@ pub struct Create {
} }
impl Create { impl Create {
fn template(self) -> change::Template<git_ext::Oid> { fn template(self) -> change::Template<oid::Oid> {
change::Template { change::Template {
type_name: self.type_name, type_name: self.type_name,
tips: Vec::new(), tips: Vec::new(),
@ -38,7 +37,7 @@ impl Create {
/// ///
/// The `storage` is the backing storage for storing /// The `storage` is the backing storage for storing
/// [`crate::Entry`]s at content-addressable locations. Please see /// [`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 /// The `signer` is expected to be a cryptographic signing key. This
/// ensures that the objects origin is cryptographically verifiable. /// ensures that the objects origin is cryptographically verifiable.
@ -57,19 +56,24 @@ pub fn create<T, S, G>(
signer: &G, signer: &G,
resource: Option<Oid>, resource: Option<Oid>,
related: Vec<Oid>, related: Vec<Oid>,
identifier: &S::Namespace, identifier: &<S as crate::object::Storage>::Namespace,
args: Create, args: Create,
) -> Result<CollaborativeObject<T>, error::Create> ) -> Result<CollaborativeObject<T>, error::Create>
where where
T: Evaluate<S>, T: Evaluate<S>,
S: Store, S: crate::object::Storage
+ crate::change::Storage<
ObjectId = crate::object::Oid,
Parent = crate::object::Oid,
Signatures = crate::ExtendedSignature,
>,
G: signature::Signer<crate::ExtendedSignature>, G: signature::Signer<crate::ExtendedSignature>,
{ {
let type_name = args.type_name.clone(); let type_name = args.type_name.clone();
let version = args.version; let version = args.version;
let init_change = storage let init_change = storage
.store(resource, related, signer, args.template()) .store(resource, related, signer, args.template())
.map_err(error::Create::from)?; .map_err(Into::<error::Create>::into)?;
let object_id = init_change.id().into(); let object_id = init_change.id().into();
let object = T::init(&init_change, storage).map_err(error::Create::evaluate)?; let object = T::init(&init_change, storage).map_err(error::Create::evaluate)?;

View File

@ -2,14 +2,13 @@
use thiserror::Error; use thiserror::Error;
use crate::git;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Create { pub enum Create {
#[error(transparent)] #[error(transparent)]
Evaluate(Box<dyn std::error::Error + Send + Sync + 'static>), Evaluate(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error(transparent)] #[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}")] #[error("failed to updated references for during object creation: {err}")]
Refs { Refs {
#[source] #[source]
@ -39,6 +38,7 @@ pub enum Retrieve {
#[error(transparent)] #[error(transparent)]
Evaluate(Box<dyn std::error::Error + Send + Sync + 'static>), Evaluate(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error(transparent)] #[error(transparent)]
#[cfg(feature = "git2")]
Git(#[from] git2::Error), Git(#[from] git2::Error),
#[error("failed to get references during object retrieval: {err}")] #[error("failed to get references during object retrieval: {err}")]
Refs { Refs {
@ -62,13 +62,15 @@ pub enum Update {
#[error("no object found")] #[error("no object found")]
NoSuchObject, NoSuchObject,
#[error(transparent)] #[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}")] #[error("failed to get references during object update: {err}")]
Refs { Refs {
#[source] #[source]
err: Box<dyn std::error::Error + Send + Sync + 'static>, err: Box<dyn std::error::Error + Send + Sync + 'static>,
}, },
#[error(transparent)] #[error(transparent)]
#[cfg(feature = "git2")]
Git(#[from] git2::Error), Git(#[from] git2::Error),
#[error(transparent)] #[error(transparent)]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),

View File

@ -1,6 +1,8 @@
// Copyright © 2022 The Radicle Link Contributors // 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; use super::error;
@ -8,7 +10,7 @@ use super::error;
/// ///
/// The `storage` is the backing storage for storing /// The `storage` is the backing storage for storing
/// [`crate::Entry`]s at content-addressable locations. Please see /// [`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 /// The `typename` is the type of object to be found, while the
/// `object_id` is the identifier for the particular object under that /// `object_id` is the identifier for the particular object under that
@ -20,7 +22,12 @@ pub fn get<T, S>(
) -> Result<Option<CollaborativeObject<T>>, error::Retrieve> ) -> Result<Option<CollaborativeObject<T>>, error::Retrieve>
where where
T: Evaluate<S>, T: Evaluate<S>,
S: Store, S: crate::object::Storage,
S: crate::change::Storage<
ObjectId = crate::object::Oid,
Parent = crate::object::Oid,
Signatures = ExtendedSignature,
>,
{ {
let tip_refs = storage let tip_refs = storage
.objects(typename, oid) .objects(typename, oid)

View File

@ -6,9 +6,10 @@
use std::collections::BTreeSet; 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; use super::error;
@ -28,7 +29,7 @@ pub struct ChangeGraphInfo {
/// ///
/// The `storage` is the backing storage for storing /// The `storage` is the backing storage for storing
/// [`crate::Entry`]s at content-addressable locations. Please see /// [`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` /// The `typename` is the type of object to be found, while the `oid`
/// is the identifier for the particular object under that type. /// is the identifier for the particular object under that type.
@ -38,7 +39,12 @@ pub fn changegraph<S>(
oid: &ObjectId, oid: &ObjectId,
) -> Result<Option<ChangeGraphInfo>, error::Retrieve> ) -> Result<Option<ChangeGraphInfo>, error::Retrieve>
where 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 let tip_refs = storage
.objects(typename, oid) .objects(typename, oid)

View File

@ -1,6 +1,6 @@
// Copyright © 2022 The Radicle Link Contributors // 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; use super::error;
@ -8,7 +8,7 @@ use super::error;
/// ///
/// The `storage` is the backing storage for storing /// The `storage` is the backing storage for storing
/// [`crate::Entry`]s at content-addressable locations. Please see /// [`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. /// The `typename` is the type of objects to be listed.
pub fn list<T, S>( pub fn list<T, S>(
@ -17,7 +17,12 @@ pub fn list<T, S>(
) -> Result<Vec<CollaborativeObject<T>>, error::Retrieve> ) -> Result<Vec<CollaborativeObject<T>>, error::Retrieve>
where where
T: Evaluate<S>, T: Evaluate<S>,
S: Store, S: crate::object::Storage,
S: crate::change::Storage<
ObjectId = crate::object::Oid,
Parent = crate::object::Oid,
Signatures = crate::ExtendedSignature,
>,
{ {
let references = storage let references = storage
.types(typename) .types(typename)

View File

@ -1,6 +1,6 @@
// Copyright © 2022 The Radicle Link Contributors // Copyright © 2022 The Radicle Link Contributors
use crate::{ObjectId, Store, TypeName}; use crate::{ObjectId, TypeName};
use super::error; use super::error;
@ -8,7 +8,7 @@ use super::error;
/// ///
/// The `storage` is the backing storage for storing /// The `storage` is the backing storage for storing
/// [`crate::Entry`]s at content-addressable locations. Please see /// [`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 /// The `typename` is the type of object to be found, while the
/// `object_id` is the identifier for the particular object under that /// `object_id` is the identifier for the particular object under that
@ -20,7 +20,7 @@ pub fn remove<S>(
oid: &ObjectId, oid: &ObjectId,
) -> Result<(), error::Remove> ) -> Result<(), error::Remove>
where where
S: Store, S: crate::object::Storage,
{ {
storage storage
.remove(identifier, typename, oid) .remove(identifier, typename, oid)

View File

@ -1,12 +1,12 @@
// Copyright © 2022 The Radicle Link Contributors // Copyright © 2022 The Radicle Link Contributors
use std::iter; use std::iter;
use git_ext::Oid;
use nonempty::NonEmpty; use nonempty::NonEmpty;
use oid::Oid;
use crate::{ use crate::{
change, change_graph::ChangeGraph, history::EntryId, CollaborativeObject, Embed, Evaluate, change, change_graph::ChangeGraph, history::EntryId, CollaborativeObject, Embed, Evaluate,
ExtendedSignature, ObjectId, Store, TypeName, ExtendedSignature, ObjectId, TypeName,
}; };
use super::error; use super::error;
@ -40,7 +40,7 @@ pub struct Update {
/// ///
/// The `storage` is the backing storage for storing /// The `storage` is the backing storage for storing
/// [`crate::Entry`]s at content-addressable locations. Please see /// [`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 /// The `signer` is expected to be a cryptographic signing key. This
/// ensures that the objects origin is cryptographically verifiable. /// ensures that the objects origin is cryptographically verifiable.
@ -65,7 +65,8 @@ pub fn update<T, S, G>(
) -> Result<Updated<T>, error::Update> ) -> Result<Updated<T>, error::Update>
where where
T: Evaluate<S>, T: Evaluate<S>,
S: Store, S: crate::object::Storage,
S: change::Storage<ObjectId = Oid, Parent = Oid, Signatures = ExtendedSignature>,
G: signature::Signer<ExtendedSignature>, G: signature::Signer<ExtendedSignature>,
{ {
let Update { let Update {
@ -86,7 +87,8 @@ where
graph.evaluate(storage).map_err(error::Update::evaluate)?; graph.evaluate(storage).map_err(error::Update::evaluate)?;
// Create a commit for this change, but don't update any references yet. // Create a commit for this change, but don't update any references yet.
let entry = storage.store( let entry = storage
.store(
resource, resource,
related, related,
signer, signer,
@ -97,7 +99,8 @@ where
type_name: typename.clone(), type_name: typename.clone(),
message, message,
}, },
)?; )
.map_err(Into::<error::Update>::into)?;
let head = entry.id; let head = entry.id;
let parents = entry.parents.to_vec(); let parents = entry.parents.to_vec();

View File

@ -2,8 +2,8 @@
use std::{collections::BTreeMap, error::Error}; use std::{collections::BTreeMap, error::Error};
use git_ext::ref_format::RefString; use fmt::RefString;
use git_ext::Oid; use oid::Oid;
use crate::change::EntryId; use crate::change::EntryId;
use crate::{ObjectId, TypeName}; use crate::{ObjectId, TypeName};
@ -93,31 +93,32 @@ pub trait Storage {
pub mod convert { pub mod convert {
use std::str; use std::str;
use git_ext::ref_format::RefString; use fmt::RefString;
use thiserror::Error; use thiserror::Error;
use super::{Commit, Reference};
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[error("the reference '{name}' does not point to a commit object")] #[error("the reference '{name}' does not point to a commit object")]
NotCommit { NotCommit {
name: RefString, name: RefString,
#[cfg(feature = "git2")]
#[source] #[source]
err: git2::Error, err: git2::Error,
}, },
#[error(transparent)] #[error(transparent)]
Ref(#[from] git_ext::ref_format::Error), Ref(#[from] fmt::Error),
#[error(transparent)] #[error(transparent)]
Utf8(#[from] str::Utf8Error), Utf8(#[from] str::Utf8Error),
} }
impl<'a> TryFrom<git2::Reference<'a>> for Reference { #[cfg(feature = "git2")]
impl<'a> TryFrom<git2::Reference<'a>> for super::Reference {
type Error = Error; type Error = Error;
fn try_from(value: git2::Reference<'a>) -> Result<Self, Self::Error> { fn try_from(value: git2::Reference<'a>) -> Result<Self, Self::Error> {
let name = RefString::try_from(str::from_utf8(value.name_bytes())?)?; let name = RefString::try_from(str::from_utf8(value.name_bytes())?)?;
let target = Commit::from(value.peel_to_commit().map_err(|err| Error::NotCommit { let target =
super::Commit::from(value.peel_to_commit().map_err(|err| Error::NotCommit {
name: name.clone(), name: name.clone(),
err, err,
})?); })?);
@ -125,9 +126,10 @@ pub mod convert {
} }
} }
impl<'a> From<git2::Commit<'a>> for Commit { #[cfg(feature = "git2")]
impl<'a> From<git2::Commit<'a>> for super::Commit {
fn from(commit: git2::Commit<'a>) -> Self { fn from(commit: git2::Commit<'a>) -> Self {
Commit { Self {
id: commit.id().into(), id: commit.id().into(),
} }
} }

View File

@ -8,9 +8,9 @@ use std::{
}; };
use crypto::{ssh, PublicKey}; use crypto::{ssh, PublicKey};
use git_ext::commit::{ use metadata::commit::{
headers::Signature::{Pgp, Ssh}, headers::Signature::{Pgp, Ssh},
Commit, CommitData,
}; };
pub use ssh::ExtendedSignature; pub use ssh::ExtendedSignature;
@ -55,10 +55,10 @@ impl From<Signatures> for BTreeMap<PublicKey, crypto::Signature> {
} }
} }
impl TryFrom<&Commit> for Signatures { impl<Tree, Parent> TryFrom<&CommitData<Tree, Parent>> for Signatures {
type Error = error::Signatures; type Error = error::Signatures;
fn try_from(value: &Commit) -> Result<Self, Self::Error> { fn try_from(value: &CommitData<Tree, Parent>) -> Result<Self, Self::Error> {
value value
.signatures() .signatures()
.filter_map(|signature| { .filter_map(|signature| {

View File

@ -1,6 +1,6 @@
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation> // Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
use radicle_crypto::ssh::ExtendedSignatureError; use crypto::ssh::ExtendedSignatureError;
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]

View File

@ -1,7 +1,11 @@
#[cfg(feature = "git2")]
pub mod identity; pub mod identity;
#[cfg(feature = "git2")]
pub use identity::{Person, Project, RemoteProject}; pub use identity::{Person, Project, RemoteProject};
#[cfg(feature = "git2")]
pub mod storage; pub mod storage;
#[cfg(feature = "git2")]
pub use storage::Storage; pub use storage::Storage;
pub mod arbitrary; pub mod arbitrary;

View File

@ -26,11 +26,7 @@ impl Arbitrary for TypeName {
impl Arbitrary for ObjectId { impl Arbitrary for ObjectId {
fn arbitrary(g: &mut qcheck::Gen) -> Self { fn arbitrary(g: &mut qcheck::Gen) -> Self {
let mut rng = fastrand::Rng::with_seed(u64::arbitrary(g)); Self::from(oid::Oid::arbitrary(g))
let bytes = iter::repeat_with(|| rng.u8(..))
.take(20)
.collect::<Vec<_>>();
Self::from(git_ext::Oid::try_from(bytes.as_slice()).unwrap())
} }
} }

View File

@ -1,7 +1,9 @@
pub mod project; pub mod project;
pub use project::{Project, RemoteProject}; pub use project::{Project, RemoteProject};
#[cfg(feature = "git2")]
pub mod person; pub mod person;
#[cfg(feature = "git2")]
pub use person::Person; pub use person::Person;
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]

View File

@ -1,4 +1,4 @@
use git_ext::Oid; use oid::Oid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::test::storage::{self, Storage}; use crate::test::storage::{self, Storage};

View File

@ -1,6 +1,6 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use git_ext::Oid; use oid::Oid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::test; use crate::test;

View File

@ -1,6 +1,8 @@
use std::{collections::BTreeMap, convert::TryFrom as _}; 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 tempfile::TempDir;
use crate::{ use crate::{
@ -29,7 +31,7 @@ pub mod error {
#[error(transparent)] #[error(transparent)]
Git(#[from] git2::Error), Git(#[from] git2::Error),
#[error(transparent)] #[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) self.as_raw().load(id)
} }
fn parents_of(&self, id: &git_ext::Oid) -> Result<Vec<git_ext::Oid>, Self::LoadError> { fn parents_of(&self, id: &oid::Oid) -> Result<Vec<radicle_oid::Oid>, Self::LoadError> {
Ok(self Ok(self
.as_raw() .as_raw()
.find_commit(**id)? .find_commit(id.into())?
.parent_ids() .parent_ids()
.map(git_ext::Oid::from) .map(oid::Oid::from)
.collect::<Vec<_>>()) .collect::<Vec<_>>())
} }
fn manifest_of(&self, id: &git_ext::Oid) -> Result<crate::Manifest, Self::LoadError> { fn manifest_of(&self, id: &oid::Oid) -> Result<crate::Manifest, Self::LoadError> {
self.as_raw().manifest_of(id) self.as_raw().manifest_of(id)
} }
} }

View File

@ -1,20 +1,26 @@
use std::ops::ControlFlow; use fmt::{Component, RefString};
use crypto::test::signer::MockSigner; use radicle_git_ref_format::refname;
use crypto::{PublicKey, Signer};
use git_ext::ref_format::{refname, Component, RefString};
use nonempty::{nonempty, NonEmpty};
use qcheck::Arbitrary;
use crate::{ use crate::{object, test::arbitrary::Invalid, ObjectId, TypeName};
create, get, list, object, test::arbitrary::Invalid, update, Create, Entry, ObjectId, TypeName,
Update, Updated, Version,
};
use super::test; #[cfg(feature = "git2")]
mod git {
use std::ops::ControlFlow;
#[test] use crypto::test::signer::MockSigner;
fn roundtrip() { use crypto::{PublicKey, Signer};
use nonempty::{nonempty, NonEmpty};
use qcheck::Arbitrary;
use crate::{
create, get, list, update, Create, Entry, ObjectId, TypeName, Update, Updated, Version,
};
use crate::test;
#[test]
fn roundtrip() {
let storage = test::Storage::new(); let storage = test::Storage::new();
let signer = gen::<MockSigner>(1); let signer = gen::<MockSigner>(1);
let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap(); let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
@ -45,10 +51,10 @@ fn roundtrip() {
.expect("BUG: cob was missing"); .expect("BUG: cob was missing");
assert_eq!(cob, expected); assert_eq!(cob, expected);
} }
#[test] #[test]
fn list_cobs() { fn list_cobs() {
let storage = test::Storage::new(); let storage = test::Storage::new();
let signer = gen::<MockSigner>(1); let signer = gen::<MockSigner>(1);
let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap(); let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
@ -97,10 +103,10 @@ fn list_cobs() {
actual.sort_by(|x, y| x.id().cmp(y.id())); actual.sort_by(|x, y| x.id().cmp(y.id()));
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
#[test] #[test]
fn update_cob() { fn update_cob() {
let storage = test::Storage::new(); let storage = test::Storage::new();
let signer = gen::<MockSigner>(1); let signer = gen::<MockSigner>(1);
let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap(); let terry = test::Person::new(&storage, "terry", *signer.public_key()).unwrap();
@ -152,10 +158,10 @@ fn update_cob() {
assert_ne!(object, not_expected); assert_ne!(object, not_expected);
assert_eq!(object, expected, "{object:#?} {expected:#?}"); assert_eq!(object, expected, "{object:#?} {expected:#?}");
} }
#[test] #[test]
fn traverse_cobs() { fn traverse_cobs() {
let storage = test::Storage::new(); let storage = test::Storage::new();
let neil_signer = gen::<MockSigner>(2); let neil_signer = gen::<MockSigner>(2);
let neil = test::Person::new(&storage, "gaiman", *neil_signer.public_key()).unwrap(); let neil = test::Person::new(&storage, "gaiman", *neil_signer.public_key()).unwrap();
@ -233,6 +239,36 @@ fn traverse_cobs() {
}); });
assert_eq!(contents, vec![b"issue 1".to_vec(), b"issue 2".to_vec()]); assert_eq!(contents, vec![b"issue 1".to_vec(), b"issue 2".to_vec()]);
}
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(())
}
fn gen<T: Arbitrary>(size: usize) -> T {
let mut gen = qcheck::Gen::new(size);
T::arbitrary(&mut gen)
}
} }
#[quickcheck] #[quickcheck]
@ -297,32 +333,3 @@ fn invalid_parse_refstr(oid: Invalid<ObjectId>, typename: TypeName) {
None None
); );
} }
fn gen<T: Arbitrary>(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(())
}

View File

@ -1,6 +1,6 @@
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation> // Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
use git_ext::commit::trailers::{OwnedTrailer, Token, Trailer}; use metadata::commit::trailers::{OwnedTrailer, Token, Trailer};
use std::ops::Deref as _; use std::ops::Deref as _;
pub mod error { pub mod error {
@ -12,21 +12,22 @@ pub mod error {
WrongToken, WrongToken,
#[error("no value for Rad-Resource")] #[error("no value for Rad-Resource")]
NoValue, NoValue,
#[error("invalid git OID")] /// Invalid object ID.
InvalidOid, #[error("invalid oid: {0}")]
InvalidOid(#[from] radicle_oid::str::ParseOidError),
} }
} }
/// Commit trailer for COB commits. /// Commit trailer for COB commits.
pub enum CommitTrailer { pub enum CommitTrailer {
/// Points to the owning resource. /// Points to the owning resource.
Resource(git2::Oid), Resource(oid::Oid),
/// Points to a related change. /// Points to a related change.
Related(git2::Oid), Related(oid::Oid),
} }
impl CommitTrailer { impl CommitTrailer {
pub fn oid(&self) -> git2::Oid { pub fn oid(&self) -> oid::Oid {
match self { match self {
Self::Resource(oid) => *oid, Self::Resource(oid) => *oid,
Self::Related(oid) => *oid, Self::Related(oid) => *oid,
@ -38,12 +39,11 @@ impl TryFrom<&Trailer<'_>> for CommitTrailer {
type Error = error::InvalidResourceTrailer; type Error = error::InvalidResourceTrailer;
fn try_from(Trailer { value, token }: &Trailer<'_>) -> Result<Self, Self::Error> { fn try_from(Trailer { value, token }: &Trailer<'_>) -> Result<Self, Self::Error> {
let ext_oid = let oid = value.as_ref().parse::<oid::Oid>()?;
git_ext::Oid::try_from(value.as_ref()).map_err(|_| Self::Error::InvalidOid)?;
if token.deref() == "Rad-Resource" { if token.deref() == "Rad-Resource" {
Ok(CommitTrailer::Resource(ext_oid.into())) Ok(CommitTrailer::Resource(oid))
} else if token.deref() == "Rad-Related" { } else if token.deref() == "Rad-Related" {
Ok(CommitTrailer::Related(ext_oid.into())) Ok(CommitTrailer::Related(oid))
} else { } else {
Err(Self::Error::WrongToken) Err(Self::Error::WrongToken)
} }

View File

@ -1,8 +1,8 @@
// Copyright © 2022 The Radicle Link Contributors // 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 serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
@ -24,8 +24,8 @@ impl TypeName {
} }
} }
impl fmt::Display for TypeName { impl std::fmt::Display for TypeName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0.as_str()) f.write_str(self.0.as_str())
} }
} }

View File

@ -20,5 +20,6 @@ gix-transport = { version = "0.44.0", features = ["blocking-client"] }
log = { workspace = true, features = ["std"] } log = { workspace = true, features = ["std"] }
nonempty = { workspace = true } nonempty = { workspace = true }
radicle = { 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 } thiserror = { workspace = true }

View File

@ -3,21 +3,3 @@ pub(crate) mod packfile;
pub(crate) mod repository; pub(crate) mod repository;
pub mod refs; 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()))
}
}

View File

@ -1,6 +1,7 @@
use std::collections::HashMap; 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 radicle::prelude::PublicKey;
use super::refs::{Applied, RefUpdate, Update}; use super::refs::{Applied, RefUpdate, Update};

View File

@ -18,7 +18,8 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use either::Either; use either::Either;
use radicle::git::{Namespaced, Oid, Qualified}; use radicle::git::fmt::{Namespaced, Qualified};
use radicle::git::Oid;
use radicle::prelude::PublicKey; use radicle::prelude::PublicKey;
pub use radicle::storage::RefUpdate; pub use radicle::storage::RefUpdate;

View File

@ -2,7 +2,11 @@ pub mod error;
use either::Either; use either::Either;
use radicle::git::raw::ErrorExt as _; 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 radicle::storage::git::Repository;
use super::refs::{Applied, Policy, RefUpdate, Update}; use super::refs::{Applied, Policy, RefUpdate, Update};
@ -48,7 +52,7 @@ pub fn contains(repo: &Repository, oid: Oid) -> Result<bool, error::Contains> {
/// - The object does not peel to a commit /// - The object does not peel to a commit
/// - Attempting to find the object fails /// - Attempting to find the object fails
fn find_and_peel(repo: &Repository, oid: Oid) -> Result<Oid, error::Ancestry> { fn find_and_peel(repo: &Repository, oid: Oid) -> Result<Oid, error::Ancestry> {
match repo.backend.find_object(*oid, None) { match repo.backend.find_object(oid.into(), None) {
Ok(object) => Ok(object Ok(object) => Ok(object
.peel(git::raw::ObjectType::Commit) .peel(git::raw::ObjectType::Commit)
.map_err(|err| error::Ancestry::Peel { oid, err })? .map_err(|err| error::Ancestry::Peel { oid, err })?
@ -80,7 +84,7 @@ pub fn ahead_behind(
let (ahead, behind) = repo let (ahead, behind) = repo
.backend .backend
.graph_ahead_behind(*new_commit, *old_commit) .graph_ahead_behind(new_commit.into(), old_commit.into())
.map_err(|err| error::Ancestry::Check { .map_err(|err| error::Ancestry::Check {
old: old_commit, old: old_commit,
new: new_commit, new: new_commit,
@ -100,7 +104,7 @@ pub fn refname_to_id<'a, N>(repo: &Repository, refname: N) -> Result<Option<Oid>
where where
N: Into<Qualified<'a>>, N: Into<Qualified<'a>>,
{ {
use radicle::git::raw::ErrorCode::NotFound; use git::raw::ErrorCode::NotFound;
let refname = refname.into(); let refname = refname.into();
match repo.backend.refname_to_id(refname.as_ref()) { 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, // If the two objects are identical, their ancestry does not matter,
// we can always skip the update. // we can always skip the update.
return Ok(RefUpdate::Skipped { return Ok(RefUpdate::Skipped {
@ -193,7 +197,7 @@ fn direct<'a>(
let target = repo let target = repo
.backend .backend
.find_object(*target, ANY_KIND) .find_object(target.into(), ANY_KIND)
.map_err(|err| error::Update::Ancestry(error::Ancestry::Object { oid: target, err }))?; .map_err(|err| error::Update::Ancestry(error::Ancestry::Object { oid: target, err }))?;
match (prev.kind(), target.kind()) { match (prev.kind(), target.kind()) {
@ -276,7 +280,7 @@ fn prune<'a>(
name: Namespaced<'a>, name: Namespaced<'a>,
prev: Either<Oid, Qualified<'a>>, prev: Either<Oid, Qualified<'a>>,
) -> Result<Updated<'a>, error::Update> { ) -> Result<Updated<'a>, error::Update> {
use radicle::git::raw::ObjectType; use git::raw::ObjectType;
match find(repo, &name)? { match find(repo, &name)? {
Some(mut r) => { Some(mut r) => {

View File

@ -1,9 +1,13 @@
use radicle::git::{ext, raw, Namespaced, Oid, Qualified}; use radicle::git::{
self,
fmt::{Namespaced, Qualified},
Oid,
};
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[error("could not open Git ODB")] #[error("could not open Git ODB")]
pub struct Contains(#[source] pub raw::Error); pub struct Contains(#[source] pub git::raw::Error);
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Ancestry { pub enum Ancestry {
@ -14,19 +18,19 @@ pub enum Ancestry {
old: Oid, old: Oid,
new: Oid, new: Oid,
#[source] #[source]
err: raw::Error, err: git::raw::Error,
}, },
#[error("failed to peel object to commit {oid}: {err}")] #[error("failed to peel object to commit {oid}: {err}")]
Peel { Peel {
oid: Oid, oid: Oid,
#[source] #[source]
err: raw::Error, err: git::raw::Error,
}, },
#[error("failed to find object {oid}: {err}")] #[error("failed to find object {oid}: {err}")]
Object { Object {
oid: Oid, oid: Oid,
#[source] #[source]
err: raw::Error, err: git::raw::Error,
}, },
} }
@ -35,15 +39,15 @@ pub enum Ancestry {
pub struct Resolve { pub struct Resolve {
pub name: Qualified<'static>, pub name: Qualified<'static>,
#[source] #[source]
pub err: raw::Error, pub err: git::raw::Error,
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[error("failed to scan for refs matching {pattern}")] #[error("failed to scan for refs matching {pattern}")]
pub struct Scan { pub struct Scan {
pub pattern: radicle::git::PatternString, pub pattern: radicle::git::fmt::refspec::PatternString,
#[source] #[source]
pub err: ext::Error, pub err: git::raw::Error,
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -55,19 +59,19 @@ pub enum Update {
name: Namespaced<'static>, name: Namespaced<'static>,
target: Oid, target: Oid,
#[source] #[source]
err: raw::Error, err: git::raw::Error,
}, },
#[error("failed to delete reference {name}")] #[error("failed to delete reference {name}")]
Delete { Delete {
name: Namespaced<'static>, name: Namespaced<'static>,
#[source] #[source]
err: raw::Error, err: git::raw::Error,
}, },
#[error("failed to find ref {name}")] #[error("failed to find ref {name}")]
Find { Find {
name: Namespaced<'static>, name: Namespaced<'static>,
#[source] #[source]
err: raw::Error, err: git::raw::Error,
}, },
#[error("non-fast-forward update of {name} (current: {cur}, new: {new})")] #[error("non-fast-forward update of {name} (current: {cur}, new: {new})")]
NonFF { NonFF {
@ -76,7 +80,7 @@ pub enum Update {
cur: Oid, cur: Oid,
}, },
#[error("failed to peel ref to object")] #[error("failed to peel ref to object")]
Peel(#[source] raw::Error), Peel(#[source] git::raw::Error),
#[error(transparent)] #[error(transparent)]
Resolve(#[from] Resolve), Resolve(#[from] Resolve),

View File

@ -1,7 +1,11 @@
use bstr::{BString, ByteSlice}; use bstr::{BString, ByteSlice};
use either::Either; use either::Either;
use radicle::crypto::PublicKey; use radicle::crypto::PublicKey;
use radicle::git::{self, Component, Namespaced, Oid, Qualified}; use radicle::git::{
self,
fmt::{Component, Namespaced, Qualified},
Oid,
};
use thiserror::Error; use thiserror::Error;
pub use radicle::git::refs::storage::Special; pub use radicle::git::refs::storage::Special;
@ -26,7 +30,6 @@ pub enum Error {
pub(crate) fn unpack_ref<'a>( pub(crate) fn unpack_ref<'a>(
r: gix_protocol::handshake::Ref, r: gix_protocol::handshake::Ref,
) -> Result<(ReceivedRefname<'a>, Oid), Error> { ) -> Result<(ReceivedRefname<'a>, Oid), Error> {
use crate::git::oid;
use gix_protocol::handshake::Ref; use gix_protocol::handshake::Ref;
match r { match r {
@ -43,7 +46,7 @@ pub(crate) fn unpack_ref<'a>(
full_ref_name, full_ref_name,
object, 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, .. } => { Ref::Unborn { full_ref_name, .. } => {
unreachable!("BUG: unborn ref {}", full_ref_name) unreachable!("BUG: unborn ref {}", full_ref_name)
} }

View File

@ -37,7 +37,7 @@ use either::Either;
use gix_protocol::handshake::Ref; use gix_protocol::handshake::Ref;
use nonempty::NonEmpty; use nonempty::NonEmpty;
use radicle::crypto::PublicKey; 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::git::Repository;
use radicle::storage::refs::{RefsAt, Special}; use radicle::storage::refs::{RefsAt, Special};
use radicle::storage::ReadRepository; use radicle::storage::ReadRepository;
@ -52,7 +52,7 @@ use crate::{policy, refs};
pub mod error { pub mod error {
use radicle::crypto::PublicKey; use radicle::crypto::PublicKey;
use radicle::git::RefString; use radicle::git::fmt::RefString;
use thiserror::Error; use thiserror::Error;
use crate::transport::WantsHavesError; use crate::transport::WantsHavesError;

View File

@ -3,7 +3,7 @@ use std::time::Instant;
use gix_protocol::handshake; use gix_protocol::handshake;
use radicle::crypto::PublicKey; use radicle::crypto::PublicKey;
use radicle::git::{Oid, Qualified}; use radicle::git::{fmt::Qualified, Oid};
use radicle::identity::{Did, Doc, DocError}; use radicle::identity::{Did, Doc, DocError};
use radicle::prelude::Verified; use radicle::prelude::Verified;

View File

@ -13,12 +13,11 @@ use gix_protocol::handshake;
use gix_transport::client; use gix_transport::client;
use gix_transport::Protocol; use gix_transport::Protocol;
use gix_transport::Service; use gix_transport::Service;
use radicle::git::fmt::Qualified;
use radicle::git::Oid; use radicle::git::Oid;
use radicle::git::Qualified;
use radicle::storage::git::Repository; use radicle::storage::git::Repository;
use thiserror::Error; use thiserror::Error;
use crate::git::oid;
use crate::git::packfile::Keepfile; use crate::git::packfile::Keepfile;
use crate::git::repository; use crate::git::repository;
@ -163,7 +162,7 @@ where
let idx = File::at(pack_path, gix_hash::Kind::Sha1)?; let idx = File::at(pack_path, gix_hash::Kind::Sha1)?;
for oid in wants_haves.wants { 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)); return Err(Error::NotFound(oid));
} }
} }

View File

@ -9,7 +9,7 @@ use gix_protocol::fetch::negotiate::one_round::State;
use gix_protocol::handshake; use gix_protocol::handshake;
use gix_protocol::handshake::Ref; use gix_protocol::handshake::Ref;
use crate::git::{oid, packfile}; use crate::git::packfile;
use super::{agent_name, Connection, WantsHaves}; use super::{agent_name, Connection, WantsHaves};
@ -109,7 +109,7 @@ impl fetch::Negotiate for Negotiate {
) -> bool { ) -> bool {
let mut has_want = false; let mut has_want = false;
for oid in &self.wants_haves.wants { for oid in &self.wants_haves.wants {
arguments.want(oid::to_object_id(*oid)); arguments.want(oid);
has_want = true; has_want = true;
} }
has_want has_want
@ -126,7 +126,7 @@ impl fetch::Negotiate for Negotiate {
_previous_response: Option<&fetch::Response>, _previous_response: Option<&fetch::Response>,
) -> Result<(fetch::negotiate::Round, bool), fetch::negotiate::Error> { ) -> Result<(fetch::negotiate::Round, bool), fetch::negotiate::Error> {
for oid in &self.wants_haves.haves { for oid in &self.wants_haves.haves {
arguments.have(oid::to_object_id(*oid)); arguments.have(oid);
} }
let round = fetch::negotiate::Round { let round = fetch::negotiate::Round {

View File

@ -31,9 +31,6 @@ nonempty = { workspace = true, features = ["serialize"] }
qcheck = { workspace = true, optional = true } qcheck = { workspace = true, optional = true }
radicle = { workspace = true, features = ["logger"] } radicle = { workspace = true, features = ["logger"] }
radicle-fetch = { workspace = true } 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-protocol = { workspace = true }
radicle-signals = { workspace = true } radicle-signals = { workspace = true }
sqlite = { workspace = true, features = ["bundled"] } sqlite = { workspace = true, features = ["bundled"] }

View File

@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex};
use std::time; use std::time;
use radicle::crypto::PublicKey; use radicle::crypto::PublicKey;
use radicle::git; use radicle::git::Oid;
use radicle::storage::refs::RefsAt; use radicle::storage::refs::RefsAt;
use crate::identity::RepoId; use crate::identity::RepoId;
@ -108,7 +108,7 @@ impl radicle::node::Handle for Handle {
Ok(RefsAt { Ok(RefsAt {
remote: self.nid()?, remote: self.nid()?,
at: git::raw::Oid::zero().into(), at: Oid::sha1_zero(),
}) })
} }

View File

@ -16,7 +16,7 @@ use radicle::crypto::ssh::keystore::MemorySigner;
use radicle::crypto::test::signer::MockSigner; use radicle::crypto::test::signer::MockSigner;
use radicle::crypto::Signature; use radicle::crypto::Signature;
use radicle::git; use radicle::git;
use radicle::git::refname; use radicle::git::fmt::refname;
use radicle::identity::{RepoId, Visibility}; use radicle::identity::{RepoId, Visibility};
use radicle::node::config::ConnectAddress; use radicle::node::config::ConnectAddress;
use radicle::node::policy::store as policy; use radicle::node::policy::store as policy;
@ -363,7 +363,7 @@ impl<G: Signer<Signature> + cyphernet::Ecdh> NodeHandle<G> {
/// of the new commit, and the reference will be updated. /// of the new commit, and the reference will be updated.
/// ///
/// The `rad/sigrefs` are then updated to reflect the new change. /// The `rad/sigrefs` are then updated to reflect the new change.
pub fn commit_to(&self, rid: RepoId, refname: impl AsRef<git::RefStr>) { pub fn commit_to(&self, rid: RepoId, refname: impl AsRef<git::fmt::RefStr>) {
use radicle::test::arbitrary; use radicle::test::arbitrary;
let refname = refname.as_ref(); let refname = refname.as_ref();
@ -516,14 +516,14 @@ impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer<Signature> + Clone> Node<G> {
); );
// Push local branches to storage. // 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() { for branch in repo.branches(Some(git::raw::BranchType::Local)).unwrap() {
let (branch, _) = branch.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(( refs.push((
git::lit::refs_heads(&name).into(), git::fmt::lit::refs_heads(&name).into(),
git::lit::refs_heads(&name).into(), git::fmt::lit::refs_heads(&name).into(),
)); ));
} }
git::push(repo, "rad", refs.iter().map(|(a, b)| (a, b))).unwrap(); git::push(repo, "rad", refs.iter().map(|(a, b)| (a, b))).unwrap();

View File

@ -138,7 +138,7 @@ impl<G: crypto::signature::Signer<crypto::Signature>> Peer<Storage, G> {
&repo, &repo,
name.try_into().unwrap(), name.try_into().unwrap(),
description, description,
radicle::git::refname!("master"), radicle::git::fmt::refname!("master"),
Visibility::default(), Visibility::default(),
self.signer(), self.signer(),
self.storage(), self.storage(),

View File

@ -1567,7 +1567,7 @@ fn test_queued_fetch_from_ann_same_rid() {
let refname = carol let refname = carol
.id() .id()
.to_namespace() .to_namespace()
.join(git::refname!("refs/sigrefs")); .join(git::fmt::refname!("refs/sigrefs"));
// Finish the 1st fetch. // Finish the 1st fetch.
// Ensure the ref is in the storage and cache. // Ensure the ref is in the storage and cache.
@ -1749,7 +1749,7 @@ fn test_init_and_seed() {
&repo, &repo,
"alice".try_into().unwrap(), "alice".try_into().unwrap(),
"alice's repo", "alice's repo",
git::refname!("master"), git::fmt::refname!("master"),
Visibility::default(), Visibility::default(),
alice.signer(), alice.signer(),
alice.storage(), alice.storage(),

View File

@ -3,6 +3,7 @@ use std::{collections::HashSet, thread, time};
use radicle::cob::Title; use radicle::cob::Title;
use test_log::test; use test_log::test;
use radicle::git::raw::ErrorExt as _;
use radicle::node::device::Device; use radicle::node::device::Device;
use radicle::node::policy::Scope; use radicle::node::policy::Scope;
use radicle::node::Event; use radicle::node::Event;
@ -248,7 +249,7 @@ fn test_replication_ref_in_sigrefs() {
bob.storage bob.storage
.repository_mut(acme) .repository_mut(acme)
.unwrap() .unwrap()
.reference(&bob.id, &git::qualified!("refs/heads/master")) .reference(&bob.id, &git::fmt::qualified!("refs/heads/master"))
.unwrap() .unwrap()
.delete() .delete()
.unwrap(); .unwrap();
@ -271,7 +272,7 @@ fn test_replication_ref_in_sigrefs() {
.storage .storage
.repository(acme) .repository(acme)
.unwrap() .unwrap()
.reference(&bob.id, &git::qualified!("refs/heads/master")) .reference(&bob.id, &git::fmt::qualified!("refs/heads/master"))
.is_ok(), .is_ok(),
"refs/namespaces/{}/refs/heads/master does not exist", "refs/namespaces/{}/refs/heads/master does not exist",
bob.id bob.id
@ -292,8 +293,8 @@ fn test_replication_invalid() {
// Create some unsigned refs for Carol in Bob's storage. // Create some unsigned refs for Carol in Bob's storage.
repo.raw() repo.raw()
.reference( .reference(
&git::qualified!("refs/heads/carol").with_namespace(carol.public_key().into()), &git::fmt::qualified!("refs/heads/carol").with_namespace(carol.public_key().into()),
*head, head.into(),
true, true,
&String::default(), &String::default(),
) )
@ -579,7 +580,7 @@ fn test_clone() {
.canonical_head() .canonical_head()
.unwrap(); .unwrap();
assert_eq!(oid, *canonical); assert_eq!(canonical, oid);
// Make sure that bob has refs/rad/id set // Make sure that bob has refs/rad/id set
assert!(bob assert!(bob
@ -1193,7 +1194,6 @@ fn missing_default_branch() {
#[test] #[test]
fn missing_delegate_default_branch() { fn missing_delegate_default_branch() {
use radicle::git::raw;
use radicle::identity::Identity; use radicle::identity::Identity;
use radicle::storage::git::Repository; use radicle::storage::git::Repository;
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
@ -1238,7 +1238,7 @@ fn missing_delegate_default_branch() {
); );
assert!(matches!( assert!(matches!(
default_branch, 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(); let result = bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
assert!(result.is_success()); assert!(result.is_success());
let default_branch: git::Qualified = { let default_branch: git::fmt::Qualified = {
let repo = alice.storage.repository(rid).unwrap(); let repo = alice.storage.repository(rid).unwrap();
let proj = repo.project().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); alice.commit_to(rid, &default_branch);

View File

@ -196,7 +196,7 @@ fn notify(
// for sigref verification. // for sigref verification.
continue; 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() { if radicle::cob::ObjectId::from_str(rest.as_str()).is_ok() {
// Don't notify about patch branches, since we already get // Don't notify about patch branches, since we already get
// notifications about patch updates. // notifications about patch updates.
@ -400,7 +400,7 @@ fn set_canonical_refs(
let oid = object.id(); let oid = object.id();
if let Err(e) = repo.backend.reference( if let Err(e) = repo.backend.reference(
refname.clone().as_str(), refname.clone().as_str(),
*oid, oid.into(),
true, true,
"set-canonical-reference from fetch (radicle)", "set-canonical-reference from fetch (radicle)",
) { ) {

View File

@ -23,7 +23,6 @@ nonempty = { workspace = true, features = ["serialize"] }
qcheck = { workspace = true, optional = true } qcheck = { workspace = true, optional = true }
radicle = { workspace = true, features = ["logger"] } radicle = { workspace = true, features = ["logger"] }
radicle-fetch = { workspace = true } radicle-fetch = { workspace = true }
radicle-git-ext = { workspace = true, features = ["serde"] }
sqlite = { workspace = true, features = ["bundled"] } sqlite = { workspace = true, features = ["bundled"] }
scrypt = { version = "0.11.0", default-features = false } scrypt = { version = "0.11.0", default-features = false }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }

View File

@ -186,8 +186,6 @@ pub enum Error {
#[error(transparent)] #[error(transparent)]
Git(#[from] radicle::git::raw::Error), Git(#[from] radicle::git::raw::Error),
#[error(transparent)] #[error(transparent)]
GitExt(#[from] radicle::git::ext::Error),
#[error(transparent)]
Storage(#[from] storage::Error), Storage(#[from] storage::Error),
#[error(transparent)] #[error(transparent)]
Gossip(#[from] gossip::Error), Gossip(#[from] gossip::Error),

View File

@ -20,6 +20,7 @@ use cyphernet::addr::tor;
use radicle::crypto::{PublicKey, Signature, Unverified}; use radicle::crypto::{PublicKey, Signature, Unverified};
use radicle::git; use radicle::git;
use radicle::git::fmt; use radicle::git::fmt;
use radicle::git::raw;
use radicle::identity::RepoId; use radicle::identity::RepoId;
use radicle::node; use radicle::node;
use radicle::node::Alias; 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) { fn encode(&self, buf: &mut impl BufMut) {
self.as_str().encode(buf) self.as_str().encode(buf)
} }
@ -300,7 +301,8 @@ impl Encode for Signature {
impl Encode for git::Oid { impl Encode for git::Oid {
fn encode(&self, buf: &mut impl BufMut) { fn encode(&self, buf: &mut impl BufMut) {
// Nb. We use length-encoding here to support future SHA-2 object ids. // 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 { for _ in 0..len {
let name = String::decode(buf)?; 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)?; let oid = git::Oid::decode(buf)?;
refs.insert(name, oid); 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<Self, Error> { fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
let ref_str = String::decode(buf)?; 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 { impl Decode for git::Oid {
fn decode(buf: &mut impl Buf) -> Result<Self, Error> { fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
const LEN_EXPECTED: usize = mem::size_of::<git::raw::Oid>(); const LEN_EXPECTED: usize = mem::size_of::<raw::Oid>();
let len = Size::decode(buf)? as usize; 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 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); let oid = git::Oid::from(oid);
Ok(oid) Ok(oid)
@ -627,7 +629,7 @@ mod tests {
#[quickcheck] #[quickcheck]
fn prop_oid(input: [u8; 20]) { fn prop_oid(input: [u8; 20]) {
roundtrip(git::Oid::try_from(input.as_slice()).unwrap()); roundtrip(git::Oid::from_sha1(input));
} }
#[test] #[test]

View File

@ -36,12 +36,12 @@ impl FetchResult {
/// corresponding targets. /// corresponding targets.
#[derive(Clone, Default, Debug)] #[derive(Clone, Default, Debug)]
pub struct UpdatedCanonicalRefs { pub struct UpdatedCanonicalRefs {
inner: BTreeMap<git::Qualified<'static>, git::Oid>, inner: BTreeMap<git::fmt::Qualified<'static>, git::Oid>,
} }
impl IntoIterator for UpdatedCanonicalRefs { impl IntoIterator for UpdatedCanonicalRefs {
type Item = (git::Qualified<'static>, git::Oid); type Item = (git::fmt::Qualified<'static>, git::Oid);
type IntoIter = std::collections::btree_map::IntoIter<git::Qualified<'static>, git::Oid>; type IntoIter = std::collections::btree_map::IntoIter<git::fmt::Qualified<'static>, git::Oid>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter() self.inner.into_iter()
@ -51,12 +51,12 @@ impl IntoIterator for UpdatedCanonicalRefs {
impl UpdatedCanonicalRefs { impl UpdatedCanonicalRefs {
/// Insert a new updated entry for the canonical reference identified by /// Insert a new updated entry for the canonical reference identified by
/// `refname` and its new `target.` /// `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); self.inner.insert(refname, target);
} }
/// Return an iterator of all the updates. /// Return an iterator of all the updates.
pub fn iter(&self) -> impl Iterator<Item = (&git::Qualified<'static>, &git::Oid)> { pub fn iter(&self) -> impl Iterator<Item = (&git::fmt::Qualified<'static>, &git::Oid)> {
self.inner.iter() self.inner.iter()
} }
} }

View File

@ -2,7 +2,7 @@ use std::io;
use thiserror::Error; use thiserror::Error;
use radicle::{cob, git, identity, storage}; use radicle::{cob, git::raw, identity, storage};
use radicle_fetch as fetch; use radicle_fetch as fetch;
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -10,7 +10,7 @@ pub enum Fetch {
#[error(transparent)] #[error(transparent)]
Run(#[from] fetch::Error), Run(#[from] fetch::Error),
#[error(transparent)] #[error(transparent)]
Git(#[from] git::raw::Error), Git(#[from] raw::Error),
#[error(transparent)] #[error(transparent)]
Storage(#[from] storage::Error), Storage(#[from] storage::Error),
#[error(transparent)] #[error(transparent)]

View File

@ -19,5 +19,4 @@ log = { workspace = true }
radicle = { workspace = true } radicle = { workspace = true }
radicle-cli = { workspace = true } radicle-cli = { workspace = true }
radicle-crypto = { workspace = true } radicle-crypto = { workspace = true }
radicle-git-ext = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }

View File

@ -19,9 +19,9 @@ pub enum Error {
/// Invalid reference name. /// Invalid reference name.
#[error("invalid ref: {0}")] #[error("invalid ref: {0}")]
InvalidRef(#[from] radicle::git::fmt::Error), InvalidRef(#[from] radicle::git::fmt::Error),
/// Git error. /// Invalid object ID.
#[error("git: {0}")] #[error("invalid oid: {0}")]
InvalidOid(#[source] git::raw::Error), InvalidOid(#[from] radicle::git::ParseOidError),
/// Error fetching pack from storage to working copy. /// 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}")] #[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. /// Run a git fetch command.
pub fn run<R: ReadRepository>( pub fn run<R: ReadRepository>(
mut refs: Vec<(git::Oid, git::RefString)>, mut refs: Vec<(git::Oid, git::fmt::RefString)>,
stored: R, stored: R,
stdin: &io::Stdin, stdin: &io::Stdin,
verbosity: Verbosity, verbosity: Verbosity,
@ -45,8 +45,8 @@ pub fn run<R: ReadRepository>(
let tokens = read_line(stdin, &mut line)?; let tokens = read_line(stdin, &mut line)?;
match tokens.as_slice() { match tokens.as_slice() {
["fetch", oid, refstr] => { ["fetch", oid, refstr] => {
let oid = git::Oid::from_str(oid).map_err(Error::InvalidOid)?; let oid = git::Oid::from_str(oid)?;
let refstr = git::RefString::try_from(*refstr)?; let refstr = git::fmt::RefString::try_from(*refstr)?;
refs.push((oid, refstr)); refs.push((oid, refstr));
} }

View File

@ -19,7 +19,7 @@ pub enum Error {
Identity(#[from] radicle::identity::DocError), Identity(#[from] radicle::identity::DocError),
/// Git error. /// Git error.
#[error(transparent)] #[error(transparent)]
Git(#[from] radicle::git::ext::Error), Git(#[from] radicle::git::raw::Error),
/// Profile error. /// Profile error.
#[error(transparent)] #[error(transparent)]
Profile(#[from] profile::Error), Profile(#[from] profile::Error),
@ -56,8 +56,8 @@ pub fn for_fetch<R: ReadRepository + cob::Store<Namespace = NodeId> + 'static>(
// List canonical references. // List canonical references.
// Skip over `refs/rad/*`, since those are not meant to be fetched into a working copy. // Skip over `refs/rad/*`, since those are not meant to be fetched into a working copy.
for glob in [ for glob in [
git::refspec::pattern!("refs/heads/*"), git::fmt::pattern!("refs/heads/*"),
git::refspec::pattern!("refs/tags/*"), git::fmt::pattern!("refs/tags/*"),
] { ] {
for (name, oid) in stored.references_glob(&glob)? { for (name, oid) in stored.references_glob(&glob)? {
println!("{oid} {name}"); println!("{oid} {name}");
@ -81,8 +81,8 @@ pub fn for_push<R: ReadRepository>(profile: &Profile, stored: &R) -> Result<(),
// Only our own refs can be pushed to. // Only our own refs can be pushed to.
for (name, oid) in stored.references_of(profile.id())? { for (name, oid) in stored.references_of(profile.id())? {
// Only branches and tags can be pushed to. // Only branches and tags can be pushed to.
if name.starts_with(git::refname!("refs/heads").as_str()) if name.starts_with(git::fmt::refname!("refs/heads").as_str())
|| name.starts_with(git::refname!("refs/tags").as_str()) || name.starts_with(git::fmt::refname!("refs/tags").as_str())
{ {
println!("{oid} {name}"); println!("{oid} {name}");
} }

View File

@ -117,6 +117,9 @@ pub enum Error {
/// List error. /// List error.
#[error(transparent)] #[error(transparent)]
List(#[from] list::Error), List(#[from] list::Error),
/// Invalid object ID.
#[error("invalid oid: {0}")]
InvalidOid(#[from] radicle::git::ParseOidError),
} }
/// Models values for the `verbosity` option, see /// 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/<patch id>`). /// Create a branch with the same name as the upstream branch (i.e. `patches/<patch id>`).
MirrorUpstream, MirrorUpstream,
/// Create a branch with the provided name. /// Create a branch with the provided name.
Provided(git::RefString), Provided(git::fmt::RefString),
} }
impl Branch { impl Branch {
/// Return the branch name to be used for the local branch when creating a /// Return the branch name to be used for the local branch when creating a
/// patch. /// patch.
pub fn to_branch_name(self, object: &radicle::patch::PatchId) -> Option<git::Qualified> { pub fn to_branch_name(self, object: &radicle::patch::PatchId) -> Option<git::fmt::Qualified> {
match self { match self {
Self::None => None, Self::None => None,
Self::MirrorUpstream => Some(git::refs::patch(object)), Self::MirrorUpstream => Some(git::refs::patch(object)),
@ -207,12 +210,15 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
// module is aware of that. // module is aware of that.
cli::Paint::set_terminal(cli::TerminalFile::Stderr); cli::Paint::set_terminal(cli::TerminalFile::Stderr);
let (remote, url): (Option<git::RefString>, Url) = { let (remote, url): (Option<git::fmt::RefString>, Url) = {
let args = env::args().skip(1).take(2).collect::<Vec<_>>(); let args = env::args().skip(1).take(2).collect::<Vec<_>>();
match args.as_slice() { match args.as_slice() {
[url] => (None, url.parse()?), [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)); return Err(Error::InvalidArguments(args));
@ -274,7 +280,7 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
} }
["fetch", oid, refstr] => { ["fetch", oid, refstr] => {
let oid = git::Oid::from_str(oid)?; 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( return Ok(fetch::run(
vec![(oid, refstr)], 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())); 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 => { other => {
return Err(Error::UnsupportedPushOption(other.to_owned())); return Err(Error::UnsupportedPushOption(other.to_owned()));
} }

View File

@ -43,7 +43,7 @@ pub enum Error {
NoKey, NoKey,
/// User tried to delete the canonical branch. /// User tried to delete the canonical branch.
#[error("refusing to delete default branch ref '{0}'")] #[error("refusing to delete default branch ref '{0}'")]
DeleteForbidden(git::RefString), DeleteForbidden(git::fmt::RefString),
/// Identity document error. /// Identity document error.
#[error("doc: {0}")] #[error("doc: {0}")]
Doc(#[from] radicle::identity::doc::DocError), Doc(#[from] radicle::identity::doc::DocError),
@ -65,9 +65,6 @@ pub enum Error {
/// Git error. /// Git error.
#[error("git: {0}")] #[error("git: {0}")]
Git(#[from] git::raw::Error), Git(#[from] git::raw::Error),
/// Git extension error.
#[error("git: {0}")]
GitExt(#[from] git::ext::Error),
/// Storage error. /// Storage error.
#[error(transparent)] #[error(transparent)]
Storage(#[from] radicle::storage::Error), Storage(#[from] radicle::storage::Error),
@ -136,9 +133,9 @@ pub enum Error {
/// Push command. /// Push command.
enum Command { enum Command {
/// Update ref. /// Update ref.
Push(git::Refspec<git::Oid, git::RefString>), Push(git::fmt::refspec::Refspec<git::Oid, git::fmt::RefString>),
/// Delete ref. /// Delete ref.
Delete(git::RefString), Delete(git::fmt::RefString),
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
@ -173,7 +170,7 @@ impl Command {
let Some((src, dst)) = s.split_once(':') else { let Some((src, dst)) = s.split_once(':') else {
return Err(CommandError::Empty { rev: s.to_string() }); 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(), rev: dst.to_string(),
err, err,
})?; })?;
@ -195,12 +192,12 @@ impl Command {
.id() .id()
.into(); .into();
Ok(Self::Push(git::Refspec { src, dst, force })) Ok(Self::Push(git::fmt::refspec::Refspec { src, dst, force }))
} }
} }
/// Return the destination refname. /// Return the destination refname.
fn dst(&self) -> &git::RefStr { fn dst(&self) -> &git::fmt::RefStr {
match self { match self {
Self::Push(rs) => rs.dst.as_refstr(), Self::Push(rs) => rs.dst.as_refstr(),
Self::Delete(rs) => rs, Self::Delete(rs) => rs,
@ -211,30 +208,30 @@ impl Command {
enum PushAction { enum PushAction {
OpenPatch, OpenPatch,
UpdatePatch { UpdatePatch {
dst: git::Qualified<'static>, dst: git::fmt::Qualified<'static>,
patch: patch::PatchId, patch: patch::PatchId,
}, },
PushRef { PushRef {
dst: git::Qualified<'static>, dst: git::fmt::Qualified<'static>,
}, },
} }
impl PushAction { impl PushAction {
fn new(dst: &git::RefString) -> Result<Self, error::PushAction> { fn new(dst: &git::fmt::RefString) -> Result<Self, error::PushAction> {
if dst == &*rad::PATCHES_REFNAME { if dst == &*rad::PATCHES_REFNAME {
Ok(Self::OpenPatch) Ok(Self::OpenPatch)
} else { } else {
let dst = git::Qualified::from_refstr(dst) let dst = git::fmt::Qualified::from_refstr(dst)
.ok_or_else(|| error::PushAction::InvalidRef { .ok_or_else(|| error::PushAction::InvalidRef {
refname: dst.clone(), refname: dst.clone(),
})? })?
.to_owned(); .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) let patch = git::Oid::from_str(oid)
.map_err(|err| error::PushAction::InvalidPatchId { .map_err(|source| error::PushAction::InvalidPatchId {
suffix: oid.to_string(), suffix: oid.to_string(),
source: err, source,
}) })
.map(patch::PatchId::from)?; .map(patch::PatchId::from)?;
Ok(Self::UpdatePatch { dst, patch }) Ok(Self::UpdatePatch { dst, patch })
@ -248,7 +245,7 @@ impl PushAction {
/// Run a git push command. /// Run a git push command.
pub fn run( pub fn run(
mut specs: Vec<String>, mut specs: Vec<String>,
remote: Option<git::RefString>, remote: Option<git::fmt::RefString>,
url: Url, url: Url,
stored: &storage::git::Repository, stored: &storage::git::Repository,
profile: &Profile, profile: &Profile,
@ -290,7 +287,7 @@ pub fn run(
let identity = stored.identity()?; let identity = stored.identity()?;
let project = identity.project()?; let project = identity.project()?;
let canonical_ref = git::refs::branch(project.default_branch()); 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()); Vec::with_capacity(specs.len());
// Rely on the environment variable `GIT_DIR`. // Rely on the environment variable `GIT_DIR`.
@ -317,7 +314,7 @@ pub fn run(
.map(|_| None) .map(|_| None)
.map_err(Error::from) .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 patches = crate::patches_mut(profile, stored)?;
let action = PushAction::new(dst)?; let action = PushAction::new(dst)?;
@ -373,7 +370,7 @@ pub fn run(
// canonical branch. // canonical branch.
if let Some(canonical) = rules.canonical(dst.clone(), stored) { if let Some(canonical) = rules.canonical(dst.clone(), stored) {
let object = working let object = working
.find_object(**src, None) .find_object(src.into(), None)
.map(|obj| git::canonical::Object::new(&obj))? .map(|obj| git::canonical::Object::new(&obj))?
.ok_or(Error::UnknownObjectType { oid: *src })?; .ok_or(Error::UnknownObjectType { oid: *src })?;
@ -429,10 +426,10 @@ pub fn run(
} }
match stored.backend.refname_to_id(refname.as_str()) { match stored.backend.refname_to_id(refname.as_str()) {
Ok(new) if new != *oid => { Ok(new) if oid != new => {
stored.backend.reference( stored.backend.reference(
refname.as_str(), refname.as_str(),
*oid, oid.into(),
true, true,
"set-canonical-reference from git-push (radicle)", "set-canonical-reference from git-push (radicle)",
)?; )?;
@ -441,7 +438,7 @@ pub fn run(
Err(e) if e.code() == git::raw::ErrorCode::NotFound => { Err(e) if e.code() == git::raw::ErrorCode::NotFound => {
stored.backend.reference( stored.backend.reference(
refname.as_str(), refname.as_str(),
*oid, oid.into(),
true, true,
"set-canonical-reference from git-push (radicle)", "set-canonical-reference from git-push (radicle)",
)?; )?;
@ -505,7 +502,7 @@ fn patch_base(
/// [`Drop::drop`]. /// [`Drop::drop`].
struct TempPatchRef<'a> { struct TempPatchRef<'a> {
stored: &'a storage::git::Repository, stored: &'a storage::git::Repository,
reference: git::Namespaced<'a>, reference: git::fmt::Namespaced<'a>,
} }
impl<'a> TempPatchRef<'a> { impl<'a> TempPatchRef<'a> {
@ -539,7 +536,7 @@ impl<'a> Drop for TempPatchRef<'a> {
/// Open a new patch. /// Open a new patch.
fn patch_open<G>( fn patch_open<G>(
head: &git::Oid, head: &git::Oid,
upstream: &Option<git::RefString>, upstream: &Option<git::fmt::RefString>,
nid: &NodeId, nid: &NodeId,
working: &git::raw::Repository, working: &git::raw::Repository,
stored: &storage::git::Repository, stored: &storage::git::Repository,
@ -563,7 +560,7 @@ where
} }
let (title, description) = 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 { let patch = if opts.draft {
patches.draft( patches.draft(
@ -607,24 +604,29 @@ where
let refname = git::refs::patch(&patch).with_namespace(nid.into()); let refname = git::refs::patch(&patch).with_namespace(nid.into());
let _ = stored.raw().reference( let _ = stored.raw().reference(
refname.as_str(), refname.as_str(),
**head, head.into(),
true, true,
"Create reference for patch head", "Create reference for patch head",
)?; )?;
if let Some(upstream) = upstream { if let Some(upstream) = upstream {
if let Some(local_branch) = opts.branch.to_branch_name(&patch) { 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(); let (_refs, _heads, x, xs) = qualified.non_empty_components();
std::iter::once(x).chain(xs).collect() 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 = git::refs::workdir::patch_upstream(&patch);
let remote_branch = working.reference( let remote_branch = working.reference(
&remote_branch, &remote_branch,
**head, head.into(),
true, true,
"Create remote tracking branch for patch", "Create remote tracking branch for patch",
)?; )?;
@ -667,7 +669,7 @@ where
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn patch_update<G>( fn patch_update<G>(
head: &git::Oid, head: &git::Oid,
dst: &git::Qualified, dst: &git::fmt::Qualified,
force: bool, force: bool,
patch_id: patch::PatchId, patch_id: patch::PatchId,
nid: &NodeId, nid: &NodeId,
@ -703,7 +705,8 @@ where
let (latest_id, latest) = patch.latest(); let (latest_id, latest) = patch.latest();
let latest = latest.clone(); 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()); let dst = dst.with_namespace(nid.into());
push_ref(head, &dst, force, stored.raw(), opts.verbosity)?; push_ref(head, &dst, force, stored.raw(), opts.verbosity)?;
@ -744,7 +747,7 @@ where
fn push<G>( fn push<G>(
src: &git::Oid, src: &git::Oid,
dst: &git::Qualified, dst: &git::fmt::Qualified,
force: bool, force: bool,
nid: &NodeId, nid: &NodeId,
working: &git::raw::Repository, working: &git::raw::Repository,
@ -768,7 +771,7 @@ where
if let Some(old) = old { if let Some(old) = old {
let proj = stored.project()?; 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 // 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. // 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. // Find all commits reachable from the old OID but not from the new OID.
let mut revwalk = stored.revwalk()?; let mut revwalk = stored.revwalk()?;
revwalk.push(*old)?; revwalk.push(old.into())?;
revwalk.hide(*new)?; revwalk.hide(new.into())?;
// List of commits that have been dropped. // List of commits that have been dropped.
let dropped = revwalk let dropped = revwalk
@ -943,7 +946,7 @@ where
/// Push a single reference to storage. /// Push a single reference to storage.
fn push_ref( fn push_ref(
src: &git::Oid, src: &git::Oid,
dst: &git::Namespaced, dst: &git::fmt::Namespaced,
force: bool, force: bool,
stored: &git::raw::Repository, stored: &git::raw::Repository,
verbosity: Verbosity, verbosity: Verbosity,
@ -951,7 +954,7 @@ fn push_ref(
let path = dunce::canonicalize(stored.path())?.display().to_string(); let path = dunce::canonicalize(stored.path())?.display().to_string();
// Nb. The *force* indicator (`+`) is processed by Git tooling before we even reach this code. // Nb. The *force* indicator (`+`) is processed by Git tooling before we even reach this code.
// This happens during the `list for-push` phase. // 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()]; let mut args = vec!["send-pack".to_string()];

View File

@ -40,7 +40,7 @@ where
/// copy, and that checks that any two commits are related in the graph. /// 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. /// 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 self.canonical
.quorum() .quorum()
.map(|QuorumWithConvergence { quorum, .. }| (quorum.refname, quorum.object)) .map(|QuorumWithConvergence { quorum, .. }| (quorum.refname, quorum.object))

View File

@ -37,10 +37,10 @@ pub struct HeadsDiverge {
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum PushAction { pub enum PushAction {
#[error("invalid reference {refname}, expected qualified reference starting with `refs/`")] #[error("invalid reference {refname}, expected qualified reference starting with `refs/`")]
InvalidRef { refname: git::RefString }, InvalidRef { refname: git::fmt::RefString },
#[error("found refs/heads/patches/{suffix} where {suffix} was an invalid Patch ID")] #[error("found refs/heads/patches/{suffix} where {suffix} was an invalid Patch ID: {source}")]
InvalidPatchId { InvalidPatchId {
suffix: String, suffix: String,
source: git::raw::Error, source: radicle::git::ParseOidError,
}, },
} }

View File

@ -18,6 +18,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### 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 ### Deprecated
- `radicle::node::Handle::announce_refs` is deprecated in favor of - `radicle::node::Handle::announce_refs` is deprecated in favor of

View File

@ -11,8 +11,9 @@ rust-version.workspace = true
[features] [features]
default = [] default = []
test = ["tempfile", "qcheck", "radicle-crypto/test"] test = ["tempfile", "qcheck", "radicle-crypto/test", "radicle-cob/test"]
logger = ["colored", "chrono"] logger = ["colored", "chrono"]
schemars = ["radicle-oid/schemars", "dep:schemars"]
[dependencies] [dependencies]
amplify = { workspace = true, features = ["std"] } amplify = { workspace = true, features = ["std"] }
@ -32,9 +33,10 @@ log = { workspace = true, features = ["std"] }
multibase = { workspace = true } multibase = { workspace = true }
nonempty = { workspace = true, features = ["serialize"] } nonempty = { workspace = true, features = ["serialize"] }
qcheck = { workspace = true, optional = true } 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-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 } radicle-ssh = { workspace = true }
schemars = { workspace = true, optional = true, features = ["derive", "std"] } schemars = { workspace = true, optional = true, features = ["derive", "std"] }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
@ -58,6 +60,7 @@ jsonschema = { version = "0.30", default-features = false }
pretty_assertions = { workspace = true } pretty_assertions = { workspace = true }
qcheck = { workspace = true } qcheck = { workspace = true }
qcheck-macros = { 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-crypto = { workspace = true, features = ["test"] }
radicle-git-metadata = { workspace = true }
tempfile = { workspace = true } tempfile = { workspace = true }

View File

@ -13,6 +13,9 @@ pub mod thread;
#[cfg(test)] #[cfg(test)]
pub mod test; pub mod test;
#[cfg(test)]
pub use radicle_cob::stable;
pub use cache::{migrate, MigrateCallback}; pub use cache::{migrate, MigrateCallback};
pub use common::*; pub use common::*;
pub use op::{ActorId, Op}; pub use op::{ActorId, Op};
@ -21,7 +24,7 @@ pub use radicle_cob::{
CollaborativeObject, Contents, Create, Embed, Entry, Evaluate, History, Manifest, ObjectId, CollaborativeObject, Contents, Create, Embed, Entry, Evaluate, History, Manifest, ObjectId,
Store, TypeName, Update, Updated, Version, 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. /// The exact identifier for a particular COB.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
@ -66,17 +69,17 @@ impl TypedId {
self.type_name == *identity::TYPENAME 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 /// All namespaces are stripped before parsing the suffix for the
/// [`TypedId`] (see [`TypedId::from_qualified`]). /// [`TypedId`] (see [`TypedId::from_qualified`]).
pub fn from_namespaced( pub fn from_namespaced(
n: &crate::git::Namespaced, n: &crate::git::fmt::Namespaced,
) -> Result<Option<Self>, ParseIdentifierError> { ) -> Result<Option<Self>, ParseIdentifierError> {
Self::from_qualified(&n.strip_namespace_recursive()) 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: /// The refname is expected to be of the form:
/// `refs/cobs/<type name>/<object id>` /// `refs/cobs/<type name>/<object id>`
@ -87,7 +90,9 @@ impl TypedId {
/// ///
/// This will fail if the refname is of the correct form, but the /// This will fail if the refname is of the correct form, but the
/// type name or object id fail to parse. /// type name or object id fail to parse.
pub fn from_qualified(q: &crate::git::Qualified) -> Result<Option<Self>, ParseIdentifierError> { pub fn from_qualified(
q: &crate::git::fmt::Qualified,
) -> Result<Option<Self>, ParseIdentifierError> {
match q.non_empty_iter() { match q.non_empty_iter() {
("refs", "cobs", type_name, mut id) => { ("refs", "cobs", type_name, mut id) => {
let Some(id) = id.next() else { let Some(id) = id.next() else {

View File

@ -355,7 +355,7 @@ impl From<Oid> for Uri {
} }
} }
impl TryFrom<&Uri> for Oid { impl TryFrom<&Uri> for crate::git::raw::Oid {
type Error = Uri; type Error = Uri;
fn try_from(value: &Uri) -> Result<Self, Self::Error> { fn try_from(value: &Uri) -> Result<Self, Self::Error> {
@ -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<Self, Self::Error> {
crate::git::raw::Oid::try_from(value).map(crate::git::Oid::from)
}
}
impl std::fmt::Display for Uri { impl std::fmt::Display for Uri {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0) write!(f, "{}", self.0)

View File

@ -225,7 +225,7 @@ impl<R: ReadRepository> Evaluate<R> for External {
Self::from_root(Op::try_from(entry)?, store) Self::from_root(Op::try_from(entry)?, store)
} }
fn apply<'a, I: Iterator<Item = (&'a radicle_git_ext::Oid, &'a radicle_cob::Entry)>>( fn apply<'a, I: Iterator<Item = (&'a crate::git::Oid, &'a radicle_cob::Entry)>>(
&mut self, &mut self,
entry: &radicle_cob::Entry, entry: &radicle_cob::Entry,
concurrent: I, concurrent: I,

View File

@ -4,11 +4,11 @@ use std::{fmt, ops::Deref, str::FromStr};
use crypto::{PublicKey, Signature}; use crypto::{PublicKey, Signature};
use radicle_cob::{Embed, ObjectId, TypeName}; use radicle_cob::{Embed, ObjectId, TypeName};
use radicle_git_ext as git_ext;
use radicle_git_ext::Oid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use crate::git;
use crate::git::Oid;
use crate::identity::doc::Doc; use crate::identity::doc::Doc;
use crate::node::device::Device; use crate::node::device::Device;
use crate::node::NodeId; use crate::node::NodeId;
@ -126,9 +126,7 @@ pub enum ApplyError {
#[error("document does not contain any changes to current identity")] #[error("document does not contain any changes to current identity")]
DocUnchanged, DocUnchanged,
#[error("git: {0}")] #[error("git: {0}")]
Git(#[from] crate::git::raw::Error), Git(#[from] git::raw::Error),
#[error("git: {0}")]
GitExt(#[from] git_ext::Error),
#[error("identity document error: {0}")] #[error("identity document error: {0}")]
Doc(#[from] DocError), Doc(#[from] DocError),
} }
@ -1323,16 +1321,15 @@ mod test {
.unwrap(); .unwrap();
bob.repo.fetch(alice); 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() alice_identity.redact(a2, &alice.signer).unwrap()
}); });
assert!(alice_identity.revision(&a1).is_some()); assert!(alice_identity.revision(&a1).is_some());
assert_eq!(alice_identity.timeline, vec![a0, a1, a2, a3]); assert_eq!(alice_identity.timeline, vec![a0, a1, a2, a3]);
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap(); let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
let b1 = cob::git::stable::with_advanced_timestamp(|| { let b1 =
bob_identity.accept(&a2, &bob.signer).unwrap() 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.timeline, vec![a0, a1, a2, b1]);
assert_eq!(bob_identity.revision(&a2).unwrap().state, State::Accepted); assert_eq!(bob_identity.revision(&a2).unwrap().state, State::Accepted);
@ -1381,15 +1378,14 @@ mod test {
eve.repo.fetch(bob); eve.repo.fetch(bob);
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap(); let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
let b1 = cob::git::stable::with_advanced_timestamp(|| { let b1 =
bob_identity.accept(&a2, &bob.signer).unwrap() cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap());
});
assert_eq!(bob_identity.current, a2); assert_eq!(bob_identity.current, a2);
let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap(); let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap();
let mut eve_doc = eve_identity.doc().clone().edit(); let mut eve_doc = eve_identity.doc().clone().edit();
eve_doc.visibility = Visibility::private([eve.signer.public_key().into()]); 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 eve_identity
.update( .update(
cob::Title::new("Change visibility").unwrap(), cob::Title::new("Change visibility").unwrap(),
@ -1458,15 +1454,13 @@ mod test {
// Bob accepts alice's revision. // Bob accepts alice's revision.
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap(); let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
let b1 = cob::git::stable::with_advanced_timestamp(|| { let b1 =
bob_identity.accept(&a2, &bob.signer).unwrap() cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap());
});
// Eve rejects the revision, not knowing. // Eve rejects the revision, not knowing.
let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap(); let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap();
let e1 = cob::git::stable::with_advanced_timestamp(|| { let e1 =
eve_identity.reject(a2, &eve.signer).unwrap() cob::stable::with_advanced_timestamp(|| eve_identity.reject(a2, &eve.signer).unwrap());
});
assert!(eve_identity.revision(&a2).unwrap().is_active()); assert!(eve_identity.revision(&a2).unwrap().is_active());
// Then she submits a new revision. // Then she submits a new revision.
@ -1576,7 +1570,7 @@ mod test {
assert_eq!(eve_identity.revision(&e1).unwrap().state, State::Active); assert_eq!(eve_identity.revision(&e1).unwrap().state, State::Active);
alice_identity.reload().unwrap(); 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() alice_identity.accept(&b1, &alice.signer).unwrap()
}); });

View File

@ -1765,7 +1765,7 @@ mod test {
.unwrap(); .unwrap();
// Comments require references, so adding two of them to the same transaction errors. // Comments require references, so adding two of them to the same transaction errors.
let mut tx: Transaction<Issue, test::storage::git::Repository> = let mut tx: Transaction<Issue, crate::storage::git::Repository> =
Transaction::<Issue, _>::default(); Transaction::<Issue, _>::default();
tx.comment("First reply", *issue.id, vec![]).unwrap(); tx.comment("First reply", *issue.id, vec![]).unwrap();
let err = tx.comment("Second reply", *issue.id, vec![]).unwrap_err(); let err = tx.comment("Second reply", *issue.id, vec![]).unwrap_err();

View File

@ -8,9 +8,10 @@ use radicle_crypto::PublicKey;
use crate::cob; use crate::cob;
use crate::cob::Timestamp; use crate::cob::Timestamp;
use crate::git;
use crate::identity;
use crate::identity::DocAt; use crate::identity::DocAt;
use crate::storage::ReadRepository; use crate::storage::ReadRepository;
use crate::{git, identity};
/// The author of an [`Op`]. /// The author of an [`Op`].
pub type ActorId = PublicKey; pub type ActorId = PublicKey;

View File

@ -130,7 +130,7 @@ pub enum Error {
Payload(#[from] PayloadError), Payload(#[from] PayloadError),
/// Git error. /// Git error.
#[error("git: {0}")] #[error("git: {0}")]
Git(#[from] git::ext::Error), Git(#[from] git::raw::Error),
/// Store error. /// Store error.
#[error("store: {0}")] #[error("store: {0}")]
Store(#[from] store::Error), Store(#[from] store::Error),
@ -601,12 +601,15 @@ impl Patch {
} }
/// Get the merge base of this patch. /// Get the merge base of this patch.
pub fn merge_base<R: ReadRepository>(&self, repo: &R) -> Result<git::Oid, git::ext::Error> { pub fn merge_base<R: ReadRepository>(
&self,
repo: &R,
) -> Result<crate::git::Oid, crate::git::raw::Error> {
repo.merge_base(self.base(), self.head()) repo.merge_base(self.base(), self.head())
} }
/// Get the commit range of this patch. /// 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())) Ok((*self.base(), *self.head()))
} }
@ -3334,7 +3337,7 @@ mod test {
}, },
&alice, &alice,
); );
let patch = Patch::from_history(&h0, &repo).unwrap(); let patch: Patch = Patch::from_history(&h0, &repo).unwrap();
assert_eq!(patch.revisions().count(), 2); assert_eq!(patch.revisions().count(), 2);
let mut h1 = h0.clone(); let mut h1 = h0.clone();

View File

@ -125,7 +125,7 @@ pub enum Error {
Git(git::raw::Error), Git(git::raw::Error),
#[error("failed to find reference '{name}': {err}")] #[error("failed to find reference '{name}': {err}")]
RefLookup { RefLookup {
name: git::RefString, name: git::fmt::RefString,
#[source] #[source]
err: git::raw::Error, err: git::raw::Error,
}, },

View File

@ -3,7 +3,9 @@ use std::marker::PhantomData;
use serde::Deserialize; use serde::Deserialize;
use crate::cob::{Op, TypeName}; 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::error;
use super::CobRange; use super::CobRange;
@ -83,7 +85,7 @@ impl Walk {
match self.until { match self.until {
Until::Tip(tip) => walk.push_range(&format!("{}..{}", self.from, tip))?, Until::Tip(tip) => walk.push_range(&format!("{}..{}", self.from, tip))?,
Until::Glob(glob) => { Until::Glob(glob) => {
walk.push(*self.from)?; walk.push(self.from.into())?;
walk.push_glob(glob.as_str())? 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 // N.b. ensure that we start using the `from` commit and use the revwalk
// after that. // after that.
if let Some(from) = self.from.take() { 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()?; let oid = self.inner.next()?;
Some(oid.and_then(|oid| self.repo.find_commit(oid))) Some(oid.and_then(|oid| self.repo.find_commit(oid)))
@ -132,7 +134,7 @@ where
let commit = self.walk.next()?; let commit = self.walk.next()?;
match commit { match commit {
Ok(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 // N.b. mark this commit as seen, so that it is not walked again
self.walk.inner.hide(commit.id()).ok(); self.walk.inner.hide(commit.id()).ok();
// Skip any Op that do not match the manifest // 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 /// Load the `Op` for the given `entry`, ensuring that manifest matches with
/// the expected manifest. /// the expected manifest.
fn load(&self, entry: git::Oid) -> Result<Option<Op<A>>, error::Ops> fn load(&self, entry: crate::git::Oid) -> Result<Option<Op<A>>, error::Ops>
where where
A: for<'de> Deserialize<'de>, A: for<'de> Deserialize<'de>,
{ {

View File

@ -12,10 +12,6 @@ use crate::cob::store::encoding;
use crate::cob::{patch, Title}; use crate::cob::{patch, Title};
use crate::cob::{Entry, History, Manifest, Timestamp, Version}; use crate::cob::{Entry, History, Manifest, Timestamp, Version};
use crate::crypto::Signer; 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::git::Oid;
use crate::node::device::Device; use crate::node::device::Device;
use crate::prelude::Did; use crate::prelude::Did;
@ -101,7 +97,7 @@ where
self.history.merge(other.history); self.history.merge(other.history);
} }
pub fn commit<G: Signer>(&mut self, action: &T::Action, signer: &G) -> git::ext::Oid { pub fn commit<G: Signer>(&mut self, action: &T::Action, signer: &G) -> crate::git::Oid {
let timestamp = self.time; let timestamp = self.time;
let tips = self.tips(); let tips = self.tips();
let revision = arbitrary::oid(); let revision = arbitrary::oid();
@ -182,7 +178,8 @@ impl<G: Signer> Actor<G> {
"nonce": fastrand::u64(..), "nonce": fastrand::u64(..),
})) }))
.unwrap(); .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 id = oid.into();
let author = *self.signer.public_key(); let author = *self.signer.public_key();
let actions = NonEmpty::from_vec(actions).unwrap(); let actions = NonEmpty::from_vec(actions).unwrap();
@ -226,8 +223,8 @@ impl<G: Signer> Actor<G> {
&mut self, &mut self,
title: Title, title: Title,
description: impl ToString, description: impl ToString,
base: git::Oid, base: crate::git::Oid,
oid: git::Oid, oid: crate::git::Oid,
repo: &R, repo: &R,
) -> Result<Patch, patch::Error> { ) -> Result<Patch, patch::Error> {
Patch::from_root( Patch::from_root(
@ -252,21 +249,26 @@ impl<G: Signer> Actor<G> {
/// ///
/// Doesn't encode in the same way as we do in production, but attempts to include the same data /// 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. /// that feeds into the hash entropy, so that changing any input will change the resulting oid.
pub fn encoded<T: Cob, G: Signer>( fn encoded<T: Cob, G: Signer>(
action: &T::Action, action: &T::Action,
timestamp: Timestamp, timestamp: Timestamp,
parents: impl IntoIterator<Item = Oid>, parents: impl IntoIterator<Item = Oid>,
signer: &G, signer: &G,
) -> (Vec<u8>, git::ext::Oid) { ) -> (Vec<u8>, crate::git::Oid) {
use radicle_git_metadata::{
author::{Author, Time},
commit::{headers::Headers, trailers::OwnedTrailer, CommitData},
};
let data = encoding::encode(action).unwrap(); let data = encoding::encode(action).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 parents = parents.into_iter().map(|o| *o); let parents = parents.into_iter().map(|o| o.into());
let author = Author { let author = Author {
name: "radicle".to_owned(), name: "radicle".to_owned(),
email: signer.public_key().to_human(), 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::<git2::Oid, git2::Oid>::new::<_, _, OwnedTrailer>(
oid, oid,
parents, parents,
author.clone(), author.clone(),
@ -277,7 +279,9 @@ pub fn encoded<T: Cob, G: Signer>(
) )
.to_string(); .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()) (data, hash.into())
} }

View File

@ -5,33 +5,24 @@ use std::io;
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
use std::str::FromStr; 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::collections::RandomMap;
use crate::crypto::PublicKey; use crate::crypto::PublicKey;
use crate::node::Alias; use crate::node::Alias;
use crate::rad; use crate::rad;
use crate::storage;
use crate::storage::refs::Refs; use crate::storage::refs::Refs;
use crate::storage::RemoteId; use crate::storage::RemoteId;
pub use ext::Error; pub use crate::storage::git::transport::local::Url;
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;
use raw::ErrorExt as _; use raw::ErrorExt as _;
pub type BranchName = crate::git::fmt::RefString;
/// Default port of the `git` transport protocol. /// Default port of the `git` transport protocol.
pub const PROTOCOL_PORT: u16 = 9418; pub const PROTOCOL_PORT: u16 = 9418;
/// Minimum required git version. /// Minimum required git version.
@ -160,16 +151,16 @@ pub enum RefError {
#[error("ref name is not valid UTF-8")] #[error("ref name is not valid UTF-8")]
InvalidName, InvalidName,
#[error("unexpected unqualified ref: {0}")] #[error("unexpected unqualified ref: {0}")]
Unqualified(RefString), Unqualified(fmt::RefString),
#[error("invalid ref format: {0}")] #[error("invalid ref format: {0}")]
Format(#[from] format::Error), Format(#[from] fmt::Error),
#[error("reference has no target")] #[error("reference has no target")]
NoTarget, NoTarget,
#[error("expected ref to begin with 'refs/namespaces' but found '{0}'")] #[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}'")] #[error("ref name contains invalid namespace identifier '{name}'")]
InvalidNamespace { InvalidNamespace {
name: format::RefString, name: fmt::RefString,
#[source] #[source]
err: Box<dyn std::error::Error + Send + Sync + 'static>, err: Box<dyn std::error::Error + Send + Sync + 'static>,
}, },
@ -186,9 +177,13 @@ pub enum ListRefsError {
} }
pub mod refs { pub mod refs {
use super::*; use std::sync::LazyLock;
use radicle_cob as cob; use radicle_cob as cob;
use super::fmt::*;
use super::*;
/// Try to get a qualified reference from a generic reference. /// Try to get a qualified reference from a generic reference.
pub fn qualified_from<'a>(r: &'a raw::Reference) -> Result<(Qualified<'a>, Oid), RefError> { pub fn qualified_from<'a>(r: &'a raw::Reference) -> Result<(Qualified<'a>, Oid), RefError> {
let name = r.name().ok_or(RefError::InvalidName)?; 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> { pub fn patch<'a>(object_id: &cob::ObjectId) -> Qualified<'a> {
Qualified::from_components( Qualified::from_components(
name::component!("heads"), component!("heads"),
name::component!("patches"), component!("patches"),
Some(object_id.into()), Some(object_id.into()),
) )
} }
pub mod storage { pub mod storage {
use format::{
lit,
name::component,
refspec::{self, PatternString},
};
use super::*; use super::*;
/// Where the repo's identity document is stored. /// Where the repo's identity document is stored.
/// ///
/// `refs/rad/id` /// `refs/rad/id`
/// ///
pub static IDENTITY_BRANCH: LazyLock<Qualified> = LazyLock::new(|| { pub static IDENTITY_BRANCH: LazyLock<Qualified> =
Qualified::from_components(name::component!("rad"), name::component!("id"), None) LazyLock::new(|| Qualified::from_components(component!("rad"), component!("id"), None));
});
/// Where the repo's identity root document is stored. /// Where the repo's identity root document is stored.
/// ///
/// `refs/rad/root` /// `refs/rad/root`
/// ///
pub static IDENTITY_ROOT: LazyLock<Qualified> = LazyLock::new(|| { pub static IDENTITY_ROOT: LazyLock<Qualified> = 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. /// Where the project's signed references are stored.
@ -250,7 +238,7 @@ pub mod refs {
/// `refs/rad/sigrefs` /// `refs/rad/sigrefs`
/// ///
pub static SIGREFS_BRANCH: LazyLock<Qualified> = LazyLock::new(|| { pub static SIGREFS_BRANCH: LazyLock<Qualified> = 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. /// The set of special references used in the Heartwood protocol.
@ -341,8 +329,8 @@ pub mod refs {
/// ///
/// `refs/namespaces/*/refs/cobs/<typename>/<object_id>` /// `refs/namespaces/*/refs/cobs/<typename>/<object_id>`
/// ///
pub fn cobs(typename: &cob::TypeName, object_id: &cob::ObjectId) -> PatternString { pub fn cobs(typename: &cob::TypeName, object_id: &cob::ObjectId) -> refspec::PatternString {
refspec::pattern!("refs/namespaces/*") pattern!("refs/namespaces/*")
.join(refname!("refs/cobs")) .join(refname!("refs/cobs"))
.join(Component::from(typename)) .join(Component::from(typename))
.join(Component::from(object_id)) .join(Component::from(object_id))
@ -390,8 +378,11 @@ pub mod refs {
/// ///
/// `refs/namespaces/*/refs/drafts/cobs/<typename>/<object_id>` /// `refs/namespaces/*/refs/drafts/cobs/<typename>/<object_id>`
/// ///
pub fn cobs(typename: &cob::TypeName, object_id: &cob::ObjectId) -> PatternString { pub fn cobs(
refspec::pattern!("refs/namespaces/*") typename: &cob::TypeName,
object_id: &cob::ObjectId,
) -> refspec::PatternString {
pattern!("refs/namespaces/*")
.join(refname!("refs/drafts/cobs")) .join(refname!("refs/drafts/cobs"))
.join(Component::from(typename)) .join(Component::from(typename))
.join(Component::from(object_id)) .join(Component::from(object_id))
@ -423,7 +414,6 @@ pub mod refs {
pub mod workdir { pub mod workdir {
use super::*; use super::*;
use format::name::component;
/// Create a [`RefString`] that corresponds to `refs/heads/<branch>`. /// Create a [`RefString`] that corresponds to `refs/heads/<branch>`.
pub fn branch(branch: &RefStr) -> RefString { pub fn branch(branch: &RefStr) -> RefString {
@ -483,7 +473,7 @@ pub fn remote_refs(url: &Url) -> Result<RandomMap<RemoteId, Refs>, ListRefsError
Ok(remotes) 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 /// to start with `refs/namespaces`. If the namespace is not present, then an
/// error will be returned. /// error will be returned.
/// ///
@ -498,7 +488,7 @@ pub fn remote_refs(url: &Url) -> Result<RandomMap<RemoteId, Refs>, ListRefsError
/// The `T` can be specified when calling the function. For example, if you /// 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 /// wanted to parse the namespace as a `PublicKey`, then you would the function
/// like so, `parse_ref_namespaced::<PublicKey>(s)`. /// like so, `parse_ref_namespaced::<PublicKey>(s)`.
pub fn parse_ref_namespaced<T>(s: &str) -> Result<(T, format::Qualified), RefError> pub fn parse_ref_namespaced<T>(s: &str) -> Result<(T, fmt::Qualified), RefError>
where where
T: FromStr, T: FromStr,
T::Err: std::error::Error + Send + Sync + 'static, 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 namespace, if present.
/// ///
/// The qualified form could be of the form: `refs/heads/main`, /// 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 /// 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 /// wanted to parse the namespace as a `PublicKey`, then you would the function
/// like so, `parse_ref::<PublicKey>(s)`. /// like so, `parse_ref::<PublicKey>(s)`.
pub fn parse_ref<T>(s: &str) -> Result<(Option<T>, format::Qualified), RefError> pub fn parse_ref<T>(s: &str) -> Result<(Option<T>, fmt::Qualified), RefError>
where where
T: FromStr, T: FromStr,
T::Err: std::error::Error + Send + Sync + 'static, 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() { match input.to_namespaced() {
None => { None => {
let refname = Qualified::from_refstr(input) let refname = fmt::Qualified::from_refstr(input)
.ok_or_else(|| RefError::Unqualified(input.to_owned()))?; .ok_or_else(|| RefError::Unqualified(input.to_owned()))?;
Ok((None, refname)) Ok((None, refname))
@ -573,7 +563,7 @@ pub fn initial_commit<'a>(
pub fn commit<'a>( pub fn commit<'a>(
repo: &'a raw::Repository, repo: &'a raw::Repository,
parent: &'a raw::Commit, parent: &'a raw::Commit,
target: &RefStr, target: &fmt::RefStr,
message: &str, message: &str,
sig: &raw::Signature, sig: &raw::Signature,
tree: &raw::Tree, tree: &raw::Tree,
@ -588,7 +578,7 @@ pub fn commit<'a>(
pub fn empty_commit<'a>( pub fn empty_commit<'a>(
repo: &'a raw::Repository, repo: &'a raw::Repository,
parent: &'a raw::Commit, parent: &'a raw::Commit,
target: &RefStr, target: &fmt::RefStr,
message: &str, message: &str,
sig: &raw::Signature, sig: &raw::Signature,
) -> Result<raw::Commit<'a>, raw::Error> { ) -> Result<raw::Commit<'a>, raw::Error> {
@ -611,7 +601,7 @@ pub fn write_tree<'r>(
path: &Path, path: &Path,
bytes: &[u8], bytes: &[u8],
repo: &'r raw::Repository, repo: &'r raw::Repository,
) -> Result<raw::Tree<'r>, Error> { ) -> Result<raw::Tree<'r>, raw::Error> {
let blob_id = repo.blob(bytes)?; let blob_id = repo.blob(bytes)?;
let mut builder = repo.treebuilder(None)?; let mut builder = repo.treebuilder(None)?;
builder.insert(path, blob_id, 0o100_644)?; 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>( pub fn push<'a>(
repo: &raw::Repository, repo: &raw::Repository,
remote: &str, remote: &str,
refspecs: impl IntoIterator<Item = (&'a Qualified<'a>, &'a Qualified<'a>)>, refspecs: impl IntoIterator<Item = (&'a fmt::Qualified<'a>, &'a fmt::Qualified<'a>)>,
) -> Result<(), raw::Error> { ) -> Result<(), raw::Error> {
let refspecs = refspecs let refspecs = refspecs
.into_iter() .into_iter()

View File

@ -19,11 +19,12 @@ use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::ControlFlow; use std::ops::ControlFlow;
use git_ext::ref_format::Namespaced; use crate::git::fmt::Namespaced;
use crate::prelude::Did; 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 /// A marker for the initial state of [`Canonical`], after construction using
/// [`Canonical::new`]. /// [`Canonical::new`].
@ -510,20 +511,19 @@ mod tests {
/// Test helper to construct a Canonical and get the quorum /// Test helper to construct a Canonical and get the quorum
fn quorum( fn quorum(
heads: &[git::raw::Oid], heads: &[crate::git::Oid],
threshold: usize, threshold: usize,
repo: &git::raw::Repository, repo: &crate::git::raw::Repository,
) -> Result<Oid, QuorumError> { ) -> Result<Oid, QuorumError> {
let refname = let refname = git::refs::branch(crate::git::fmt::RefStr::try_from_str("master").unwrap());
git::refs::branch(git_ext::ref_format::RefStr::try_from_str("master").unwrap());
let mut delegates = Vec::new(); let mut delegates = Vec::new();
for (i, head) in heads.iter().enumerate() { for (i, head) in heads.iter().enumerate() {
let signer = Device::mock_from_seed([(i + 1) as u8; 32]); let signer = Device::mock_from_seed([(i + 1) as u8; 32]);
let did = Did::from(signer.public_key()); let did = Did::from(signer.public_key());
delegates.push(did); delegates.push(did);
let ns = git::Component::from(signer.public_key()); let ns = git::fmt::Component::from(signer.public_key());
repo.reference(refname.with_namespace(ns).as_str(), *head, true, "") repo.reference(refname.with_namespace(ns).as_str(), head.into(), true, "")
.unwrap(); .unwrap();
} }
@ -557,18 +557,18 @@ mod tests {
fn test_quorum_properties() { fn test_quorum_properties() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let (repo, c0) = fixtures::repository(tmp.path()); let (repo, c0) = fixtures::repository(tmp.path());
let c0: git::Oid = c0.into(); let c0: crate::git::Oid = c0.into();
let a1 = fixtures::commit("A1", &[*c0], &repo); let a1 = fixtures::commit("A1", &[c0.into()], &repo);
let a2 = fixtures::commit("A2", &[*a1], &repo); let a2 = fixtures::commit("A2", &[a1.into()], &repo);
let d1 = fixtures::commit("D1", &[*c0], &repo); let d1 = fixtures::commit("D1", &[c0.into()], &repo);
let c1 = fixtures::commit("C1", &[*c0], &repo); let c1 = fixtures::commit("C1", &[c0.into()], &repo);
let c2 = fixtures::commit("C2", &[*c1], &repo); let c2 = fixtures::commit("C2", &[c1.into()], &repo);
let b2 = fixtures::commit("B2", &[*c1], &repo); let b2 = fixtures::commit("B2", &[c1.into()], &repo);
let a1 = fixtures::commit("A1", &[*c0], &repo); let a1 = fixtures::commit("A1", &[c0.into()], &repo);
let m1 = fixtures::commit("M1", &[*c2, *b2], &repo); let m1 = fixtures::commit("M1", &[c2.into(), b2.into()], &repo);
let m2 = fixtures::commit("M2", &[*a1, *b2], &repo); let m2 = fixtures::commit("M2", &[a1.into(), b2.into()], &repo);
let mut rng = fastrand::Rng::new(); 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 { for _ in 0..100 {
let count = rng.usize(1..=choices.len()); let count = rng.usize(1..=choices.len());
@ -591,11 +591,10 @@ mod tests {
fn test_quorum_different_types() { fn test_quorum_different_types() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let (repo, c0) = fixtures::repository(tmp.path()); 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!( assert_matches!(
quorum(&[*c0, *t0], 1, &repo), quorum(&[c0.into(), t0], 1, &repo),
Err(QuorumError::DifferentTypes { .. }) Err(QuorumError::DifferentTypes { .. })
); );
} }

View File

@ -1,8 +1,9 @@
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use crate::git; use crate::git;
use crate::git::fmt::Qualified;
use crate::git::raw::ErrorExt as _; use crate::git::raw::ErrorExt as _;
use crate::git::{Oid, Qualified}; use crate::git::Oid;
use crate::prelude::Did; use crate::prelude::Did;
use super::{FoundObjects, GraphAheadBehind, MergeBase, Object}; use super::{FoundObjects, GraphAheadBehind, MergeBase, Object};
@ -40,7 +41,7 @@ pub enum FindObjectsError {
}, },
#[error("failed to find reference {refname} due to: {source}")] #[error("failed to find reference {refname} due to: {source}")]
FindReference { FindReference {
refname: git::Namespaced<'static>, refname: git::fmt::Namespaced<'static>,
source: Box<dyn std::error::Error + Send + Sync + 'static>, source: Box<dyn std::error::Error + Send + Sync + 'static>,
}, },
#[error("failed to find objects")] #[error("failed to find objects")]
@ -60,7 +61,7 @@ impl FindObjectsError {
} }
} }
pub fn find_reference<E>(refname: git::Namespaced<'static>, err: E) -> Self pub fn find_reference<E>(refname: git::fmt::Namespaced<'static>, err: E) -> Self
where where
E: std::error::Error + Send + Sync + 'static, E: std::error::Error + Send + Sync + 'static,
{ {
@ -170,7 +171,7 @@ pub struct GraphDescendant {
impl FindMergeBase for git::raw::Repository { impl FindMergeBase for git::raw::Repository {
fn merge_base(&self, a: Oid, b: Oid) -> Result<MergeBase, MergeBaseError> { fn merge_base(&self, a: Oid, b: Oid) -> Result<MergeBase, MergeBaseError> {
self.merge_base(*a, *b) self.merge_base(a.into(), b.into())
.map_err(|err| MergeBaseError { .map_err(|err| MergeBaseError {
a, a,
b, b,
@ -190,7 +191,7 @@ impl Ancestry for git::raw::Repository {
commit: Oid, commit: Oid,
upstream: Oid, upstream: Oid,
) -> Result<GraphAheadBehind, GraphDescendant> { ) -> Result<GraphAheadBehind, GraphDescendant> {
self.graph_ahead_behind(*commit, *upstream) self.graph_ahead_behind(commit.into(), upstream.into())
.map_err(|err| GraphDescendant { .map_err(|err| GraphDescendant {
commit, commit,
upstream, upstream,
@ -228,7 +229,7 @@ impl FindObjects for git::raw::Repository {
log::warn!(target: "radicle", "Missing target for reference `{name}`"); log::warn!(target: "radicle", "Missing target for reference `{name}`");
continue; 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(|| { Ok(object) => Object::new(&object).ok_or_else(|| {
FindObjectsError::invalid_object_type( FindObjectsError::invalid_object_type(
*did, *did,

Some files were not shown because too many files have changed in this diff Show More