Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
workspace: Refactor
Lorenz Leutgeb committed 7 months ago
commit a5d80e3e929f2c01d50bf2bb11a47a419e550027
parent 96f9a9e9faf48f8e11133e65e62d7fd3e574cf7f
114 files changed +1221 -1116
modified Cargo.lock
@@ -241,12 +241,6 @@ checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"

[[package]]
name = "base64"
-
version = "0.13.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
-

-
[[package]]
-
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
@@ -1120,9 +1114,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"

[[package]]
name = "git-ref-format"
-
version = "0.3.1"
+
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "7428e0d6e549a9a613d6f019b839a0f5142c331295b79e119ca8f4faac145da1"
+
checksum = "76314f6eb43910ebc5eb89a1f0728724a6b9144dabd18bca4e7cc7c01c3804e3"
dependencies = [
 "git-ref-format-core",
 "git-ref-format-macro",
@@ -1130,9 +1124,9 @@ dependencies = [

[[package]]
name = "git-ref-format-core"
-
version = "0.3.1"
+
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "bbaeb9672a55e9e32cb6d3ef781e7526b25ab97d499fae71615649340b143424"
+
checksum = "ae6d9ee666ca7d4ad49cbf7174f785f299a18b37565694f665e8c7df24999cdd"
dependencies = [
 "bstr",
 "serde",
@@ -1141,12 +1135,12 @@ dependencies = [

[[package]]
name = "git-ref-format-macro"
-
version = "0.3.1"
+
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "3b6ca5353accc201f6324dff744ba4660099546d4daf187ba868f07562e36ca4"
+
checksum = "2dc2ded12a6ea2b6e63afaf09415e4c15bf4baa74e530acd9daed9ff47e7dd41"
dependencies = [
 "git-ref-format-core",
-
 "proc-macro-error",
+
 "proc-macro-error2",
 "quote",
 "syn 2.0.89",
]
@@ -2267,12 +2261,6 @@ dependencies = [

[[package]]
name = "nonempty"
-
version = "0.5.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "9ff7ac1e5ea23db6d61ad103e91864675049644bf47c35912336352fa4e9c109"
-

-
[[package]]
-
name = "nonempty"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "995defdca0a589acfdd1bd2e8e3b896b4d4f7675a31fd14c32611440c7f608e6"
@@ -2679,27 +2667,25 @@ dependencies = [
]

[[package]]
-
name = "proc-macro-error"
-
version = "1.0.4"
+
name = "proc-macro-error-attr2"
+
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
-
 "proc-macro-error-attr",
 "proc-macro2",
 "quote",
-
 "syn 1.0.109",
-
 "version_check",
]

[[package]]
-
name = "proc-macro-error-attr"
-
version = "1.0.4"
+
name = "proc-macro-error2"
+
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
+
 "proc-macro-error-attr2",
 "proc-macro2",
 "quote",
-
 "version_check",
+
 "syn 2.0.89",
]

[[package]]
@@ -2787,13 +2773,15 @@ dependencies = [
 "localtime",
 "log",
 "multibase",
-
 "nonempty 0.9.0",
+
 "nonempty",
 "pretty_assertions",
 "qcheck",
 "qcheck-macros",
 "radicle-cob",
 "radicle-crypto",
-
 "radicle-git-ext",
+
 "radicle-git-ref-format",
+
 "radicle-git2-metadata",
+
 "radicle-oid",
 "radicle-ssh",
 "schemars",
 "serde",
@@ -2815,19 +2803,18 @@ dependencies = [
 "chrono",
 "clap",
 "dunce",
-
 "git-ref-format",
 "human-panic",
 "itertools",
 "lexopt",
 "localtime",
 "log",
-
 "nonempty 0.9.0",
+
 "nonempty",
 "pretty_assertions",
 "radicle",
 "radicle-cli-test",
 "radicle-cob",
 "radicle-crypto",
-
 "radicle-git-ext",
+
 "radicle-git-ref-format",
 "radicle-node",
 "radicle-surf",
 "radicle-term",
@@ -2875,7 +2862,7 @@ dependencies = [
 "fastrand",
 "git2",
 "log",
-
 "nonempty 0.9.0",
+
 "nonempty",
 "qcheck",
 "qcheck-macros",
 "radicle-crypto",
@@ -2901,7 +2888,7 @@ dependencies = [
 "multibase",
 "qcheck",
 "qcheck-macros",
-
 "radicle-git-ext",
+
 "radicle-git-ref-format",
 "radicle-ssh",
 "serde",
 "signature 2.2.0",
@@ -2932,17 +2919,18 @@ dependencies = [
 "gix-protocol",
 "gix-transport",
 "log",
-
 "nonempty 0.9.0",
+
 "nonempty",
 "radicle",
-
 "radicle-git-ext",
+
 "radicle-git-ref-format",
+
 "radicle-oid",
 "thiserror 1.0.69",
]

[[package]]
name = "radicle-git-ext"
-
version = "0.8.1"
+
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "4b78c26e67d1712ad5a0c602ae3b236609461372ac04e200bda359fe4a1c6650"
+
checksum = "fb3de6999a8ff570e0dd92a04ba3132853dd08a3bcfc2f7faf56de7b1bd36053"
dependencies = [
 "git-ref-format",
 "git2",
@@ -2985,13 +2973,12 @@ dependencies = [
 "localtime",
 "log",
 "netservices",
-
 "nonempty 0.9.0",
+
 "nonempty",
 "qcheck",
 "qcheck-macros",
 "radicle",
 "radicle-crypto",
 "radicle-fetch",
-
 "radicle-git-ext",
 "radicle-protocol",
 "radicle-signals",
 "radicle-systemd",
@@ -3032,14 +3019,13 @@ dependencies = [
 "fastrand",
 "localtime",
 "log",
-
 "nonempty 0.9.0",
+
 "nonempty",
 "paste",
 "qcheck",
 "qcheck-macros",
 "radicle",
 "radicle-crypto",
 "radicle-fetch",
-
 "radicle-git-ext",
 "scrypt",
 "serde",
 "serde_json",
@@ -3056,7 +3042,6 @@ dependencies = [
 "radicle",
 "radicle-cli",
 "radicle-crypto",
-
 "radicle-git-ext",
 "thiserror 1.0.69",
]

@@ -3090,22 +3075,22 @@ dependencies = [

[[package]]
name = "radicle-std-ext"
-
version = "0.1.0"
+
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "db20136bbc9ae63f3fec8e5a6c369f4902fac2244501b5dfc6d668e43475aaa4"
+
checksum = "5310e7a04506b6ce92dc9c47b26bd24c1c680937a3dcd13cd20847f89dbda32a"

[[package]]
name = "radicle-surf"
-
version = "0.22.0"
+
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "fb308c3989087f71e43d8c7a2737273fdc7fbcd3e6628af81a42f601ae64f314"
+
checksum = "2f08adc954d4a49287d86bd1ce14ca7e34a11a3acd904044db30551ca8f4e46a"
dependencies = [
 "anyhow",
-
 "base64 0.13.1",
+
 "base64 0.21.7",
 "flate2",
 "git2",
 "log",
-
 "nonempty 0.5.0",
+
 "nonempty",
 "radicle-git-ext",
 "radicle-std-ext",
 "tar",
modified Cargo.toml
@@ -28,7 +28,6 @@ crossbeam-channel = "0.5.6"
cyphernet = "0.5.2"
dunce = "1.0.5"
fastrand = { version = "2.0.0", default-features = false }
-
git-ref-format = { version = "0.3.0", default-features = false }
git2 = { version = "0.19.0", default-features = false }
human-panic = "2"
itertools = "0.14"
@@ -48,7 +47,6 @@ radicle-cob = { version = "0.16", path = "crates/radicle-cob" }
radicle-crypto = { version = "0.13", path = "crates/radicle-crypto" }
radicle-dag = { version = "0.10", path = "crates/radicle-dag" }
radicle-fetch = { version = "0.15", path = "crates/radicle-fetch" }
-
radicle-git-ext = { version = "0.8", default-features = false }
radicle-git-ref-format = { version = "0.1.0", path = "crates/radicle-git-ref-format", default-features = false }
radicle-git2-metadata = { version = "0.1.0", path = "crates/radicle-git2-metadata", default-features = false }
radicle-node = { version = "0.15", path = "crates/radicle-node" }
@@ -70,6 +68,15 @@ thiserror = "1.0"
winpipe = "0.1.1"
zeroize = "1.5.7"

+
# Crates from the "radicle-git" workspace. These should be synced manually.
+
# When updating, start from `radicle-surf`, which depends on the others:
+
# `radicle-surf` → `radicle-git-ext` → `git-ref-format` → `git-ref-format-core`
+
# Also note that `radicle-surf → git2` and `radicle-git-ext` → `git2`, so try
+
# to also sync with `git2` if possible.
+
git-ref-format-core = { version = "0.5.0", default-features = false }
+
radicle-git-ext = { version = "0.10.0", default-features = false }
+
radicle-surf = "0.25.0"
+

[workspace.lints]
clippy.type_complexity = "allow"
clippy.enum_variant_names = "allow"
modified crates/radicle-cli/Cargo.toml
@@ -18,20 +18,17 @@ anyhow = "1"
chrono = { workspace = true, features = ["clock", "std"] }
clap = { version = "4.5.44", features = ["derive"] }
dunce = { workspace = true }
-
git-ref-format = { version = "0.3.0", features = ["macro"] }
human-panic.workspace = true
itertools.workspace = true
lexopt = { workspace = true }
localtime = { workspace = true }
log = { workspace = true, features = ["std"] }
nonempty = { workspace = true }
-
radicle = { workspace = true, features = ["logger", "schemars"] }
+
radicle = { workspace = true, features = ["logger", "schemars", "radicle-git-ext"] }
radicle-cob = { workspace = true }
radicle-crypto = { workspace = true }
-
# N.b. this is required to use macros, even though it's re-exported
-
# through radicle
-
radicle-git-ext = { workspace = true, features = ["serde"] }
-
radicle-surf = "0.22.0"
+
radicle-git-ref-format = { workspace = true, features = ["macro"] }
+
radicle-surf = { workspace = true }
radicle-term = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true }
modified crates/radicle-cli/src/commands/checkout.rs
@@ -156,9 +156,9 @@ pub fn setup_remotes(
pub fn setup_remote(
    setup: &project::SetupRemote,
    remote_id: &NodeId,
-
    remote_name: Option<git::RefString>,
+
    remote_name: Option<git::fmt::RefString>,
    aliases: &impl AliasStore,
-
) -> anyhow::Result<git::RefString> {
+
) -> anyhow::Result<git::fmt::RefString> {
    let remote_name = if let Some(name) = remote_name {
        name
    } else {
@@ -167,7 +167,7 @@ pub fn setup_remote(
        } else {
            remote_id.to_human()
        };
-
        git::RefString::try_from(name.as_str())
+
        git::fmt::RefString::try_from(name.as_str())
            .map_err(|_| anyhow!("invalid remote name: '{name}'"))?
    };
    let (remote, branch) = setup.run(&remote_name, *remote_id)?;
modified crates/radicle-cli/src/commands/clone.rs
@@ -9,7 +9,7 @@ use radicle::issue::cache::Issues as _;
use radicle::patch::cache::Patches as _;
use thiserror::Error;

-
use radicle::git::raw;
+
use radicle::git2;
use radicle::identity::doc;
use radicle::identity::doc::RepoId;
use radicle::node;
@@ -384,7 +384,7 @@ enum CloneResult {
}

struct Success {
-
    working_copy: raw::Repository,
+
    working_copy: git2::Repository,
    repository: storage::git::Repository,
    doc: Doc,
    project: Project,
modified crates/radicle-cli/src/commands/diff.rs
@@ -2,7 +2,7 @@ use std::ffi::OsString;

use anyhow::anyhow;

-
use radicle::git;
+
use radicle::git2;
use radicle::rad;
use radicle_surf as surf;

@@ -98,12 +98,12 @@ pub fn run(options: Options, _ctx: impl term::Context) -> anyhow::Result<()> {
        })
        .collect::<Result<Vec<_>, _>>()?;

-
    let mut opts = git::raw::DiffOptions::new();
+
    let mut opts = git2::DiffOptions::new();
    opts.patience(true)
        .minimal(true)
        .context_lines(options.unified as u32);

-
    let mut find_opts = git::raw::DiffFindOptions::new();
+
    let mut find_opts = git2::DiffFindOptions::new();
    find_opts.exact_match_only(true);
    find_opts.all(true);

modified crates/radicle-cli/src/commands/id.rs
@@ -700,9 +700,9 @@ fn print_diff(
    let current = serde_json::to_string_pretty(&current.doc)?;

    let tmp = tempfile::tempdir()?;
-
    let repo = radicle::git::raw::Repository::init_opts(
+
    let repo = radicle::git2::Repository::init_opts(
        tmp.path(),
-
        radicle::git::raw::RepositoryInitOptions::new()
+
        radicle::git2::RepositoryInitOptions::new()
            .external_template(false)
            .bare(true),
    )?;
@@ -714,7 +714,7 @@ fn print_diff(
        None
    };
    let current = radicle::git::write_tree(&doc::PATH, current.as_bytes(), &repo)?;
-
    let mut opts = radicle::git::raw::DiffOptions::new();
+
    let mut opts = radicle::git2::DiffOptions::new();
    opts.context_lines(u32::MAX);

    let diff = repo.diff_tree_to_tree(previous.as_ref(), Some(&current), Some(&mut opts))?;
modified crates/radicle-cli/src/commands/inbox.rs
@@ -4,9 +4,9 @@ use std::process;

use anyhow::anyhow;

-
use git_ref_format::Qualified;
use localtime::LocalTime;
use radicle::cob::TypedId;
+
use radicle::git::fmt::Qualified;
use radicle::identity::Identity;
use radicle::issue::cache::Issues as _;
use radicle::node::notifications;
modified crates/radicle-cli/src/commands/init.rs
@@ -12,8 +12,9 @@ use serde_json as json;

use radicle::crypto::ssh;
use radicle::explorer::ExplorerUrl;
-
use radicle::git::raw;
-
use radicle::git::RefString;
+
use radicle::git::fmt::RefString;
+
use radicle::git2;
+
use radicle::git2::ErrorExt as _;
use radicle::identity::project::ProjectName;
use radicle::identity::{Doc, RepoId, Visibility};
use radicle::node::events::UploadPack;
@@ -189,7 +190,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let path = options.path.as_deref().unwrap_or(cwd.as_path());
    let repo = match git::Repository::open(path) {
        Ok(r) => r,
-
        Err(e) if radicle::git::ext::is_not_found_err(&e) => {
+
        Err(e) if e.is_not_found() => {
            anyhow::bail!("a Git repository was not found at the given path")
        }
        Err(e) => return Err(e.into()),
@@ -709,26 +710,26 @@ enum DefaultBranchError {
    #[error("in detached HEAD state")]
    Head,
    #[error("could not determine default branch in repository: {0}")]
-
    Git(raw::Error),
+
    Git(git2::Error),
}

-
fn find_default_branch(repo: &raw::Repository) -> Result<String, DefaultBranchError> {
+
fn find_default_branch(repo: &git2::Repository) -> Result<String, DefaultBranchError> {
    match find_init_default_branch(repo).ok().flatten() {
        Some(refname) => Ok(refname),
        None => Ok(find_repository_head(repo)?),
    }
}

-
fn find_init_default_branch(repo: &raw::Repository) -> Result<Option<String>, raw::Error> {
+
fn find_init_default_branch(repo: &git2::Repository) -> Result<Option<String>, git2::Error> {
    let config = repo.config().and_then(|mut c| c.snapshot())?;
    let default_branch = config.get_str("init.defaultbranch")?;
-
    let branch = repo.find_branch(default_branch, raw::BranchType::Local)?;
+
    let branch = repo.find_branch(default_branch, git2::BranchType::Local)?;
    Ok(branch.into_reference().shorthand().map(ToOwned::to_owned))
}

-
fn find_repository_head(repo: &raw::Repository) -> Result<String, DefaultBranchError> {
+
fn find_repository_head(repo: &git2::Repository) -> Result<String, DefaultBranchError> {
    match repo.head() {
-
        Err(e) if e.code() == raw::ErrorCode::UnbornBranch => Err(DefaultBranchError::NoHead),
+
        Err(e) if e.code() == git2::ErrorCode::UnbornBranch => Err(DefaultBranchError::NoHead),
        Err(e) => Err(DefaultBranchError::Git(e)),
        Ok(head) => head
            .shorthand()
modified crates/radicle-cli/src/commands/patch.rs
@@ -24,7 +24,7 @@ use anyhow::anyhow;

use radicle::cob::patch::PatchId;
use radicle::cob::{patch, Label, Reaction};
-
use radicle::git::RefString;
+
use radicle::git::fmt::RefString;
use radicle::patch::cache::Patches as _;
use radicle::storage::git::transport;
use radicle::{prelude::*, Node};
modified crates/radicle-cli/src/commands/patch/checkout.rs
@@ -1,9 +1,11 @@
use anyhow::anyhow;

-
use git_ref_format::Qualified;
use radicle::cob::patch;
use radicle::cob::patch::RevisionId;
-
use radicle::git::RefString;
+
use radicle::git::fmt::Qualified;
+
use radicle::git::fmt::RefString;
+
use radicle::git2;
+
use radicle::git2::ErrorExt as _;
use radicle::patch::cache::Patches as _;
use radicle::patch::PatchId;
use radicle::storage::git::Repository;
@@ -24,7 +26,7 @@ impl Options {
            Some(refname) => Ok(Qualified::from_refstr(refname)
                .map_or_else(|| refname.clone(), |q| q.to_ref_string())),
            // SAFETY: Patch IDs are valid refstrings.
-
            None => Ok(git::refname!("patch")
+
            None => Ok(git::fmt::refname!("patch")
                .join(RefString::try_from(term::format::cob(id).item).unwrap())),
        }
    }
@@ -34,7 +36,7 @@ pub fn run(
    patch_id: &PatchId,
    revision_id: Option<RevisionId>,
    stored: &Repository,
-
    working: &git::raw::Repository,
+
    working: &git2::Repository,
    profile: &Profile,
    opts: Options,
) -> anyhow::Result<()> {
@@ -56,34 +58,34 @@ pub fn run(
    let mut spinner = term::spinner("Performing checkout...");
    let patch_branch = opts.branch(patch_id)?;

-
    let commit =
-
        match working.find_branch(patch_branch.as_str(), radicle::git::raw::BranchType::Local) {
-
            Ok(branch) if opts.force => {
-
                let commit = find_patch_commit(revision, stored, working)?;
-
                let mut r = branch.into_reference();
-
                r.set_target(commit.id(), &format!("force update '{patch_branch}'"))?;
-
                commit
-
            }
-
            Ok(branch) => {
-
                let head = branch.get().peel_to_commit()?;
-
                if head.id() != *revision.head() {
-
                    anyhow::bail!(
-
                        "branch '{patch_branch}' already exists (use `--force` to overwrite)"
-
                    );
-
                }
-
                head
-
            }
-
            Err(e) if radicle::git::is_not_found_err(&e) => {
-
                let commit = find_patch_commit(revision, stored, working)?;
-
                // Create patch branch and switch to it.
-
                working.branch(patch_branch.as_str(), &commit, true)?;
-
                commit
+
    let commit = match working.find_branch(patch_branch.as_str(), radicle::git2::BranchType::Local)
+
    {
+
        Ok(branch) if opts.force => {
+
            let commit = find_patch_commit(revision, stored, working)?;
+
            let mut r = branch.into_reference();
+
            r.set_target(commit.id(), &format!("force update '{patch_branch}'"))?;
+
            commit
+
        }
+
        Ok(branch) => {
+
            let head = branch.get().peel_to_commit()?;
+
            if revision.head() != head.id() {
+
                anyhow::bail!(
+
                    "branch '{patch_branch}' already exists (use `--force` to overwrite)"
+
                );
            }
-
            Err(e) => return Err(e.into()),
-
        };
+
            head
+
        }
+
        Err(e) if e.is_not_found() => {
+
            let commit = find_patch_commit(revision, stored, working)?;
+
            // Create patch branch and switch to it.
+
            working.branch(patch_branch.as_str(), &commit, true)?;
+
            commit
+
        }
+
        Err(e) => return Err(e.into()),
+
    };

    if opts.force {
-
        let mut builder = radicle::git::raw::build::CheckoutBuilder::new();
+
        let mut builder = radicle::git2::build::CheckoutBuilder::new();
        builder.force();
        working.checkout_tree(commit.as_object(), Some(&mut builder))?;
    } else {
@@ -122,13 +124,13 @@ pub fn run(
fn find_patch_commit<'a>(
    revision: &patch::Revision,
    stored: &Repository,
-
    working: &'a git::raw::Repository,
-
) -> anyhow::Result<git::raw::Commit<'a>> {
-
    let head = *revision.head();
+
    working: &'a git2::Repository,
+
) -> anyhow::Result<git2::Commit<'a>> {
+
    let head = revision.head().into();

    match working.find_commit(head) {
        Ok(commit) => Ok(commit),
-
        Err(e) if git::ext::is_not_found_err(&e) => {
+
        Err(e) if e.is_not_found() => {
            let output = git::process::fetch_pack(
                Some(working.path()),
                stored,
modified crates/radicle-cli/src/commands/patch/review.rs
@@ -4,6 +4,7 @@ use anyhow::{anyhow, Context};

use radicle::cob::patch::{PatchId, RevisionId, Verdict};
use radicle::git;
+
use radicle::git2;
use radicle::prelude::*;
use radicle::storage::git::Repository;

@@ -84,7 +85,7 @@ pub fn run(
            hunk,
        } if by_hunk => {
            crate::warning::obsolete("rad patch review --patch");
-
            let mut opts = git::raw::DiffOptions::new();
+
            let mut opts = git2::DiffOptions::new();
            opts.patience(true)
                .minimal(true)
                .context_lines(unified as u32);
modified crates/radicle-cli/src/commands/patch/review/builder.rs
@@ -23,10 +23,11 @@ use radicle::cob::patch::{PatchId, Revision, Verdict};
use radicle::cob::{CodeLocation, CodeRange};
use radicle::crypto;
use radicle::git;
+
use radicle::git2;
use radicle::node::device::Device;
use radicle::prelude::*;
use radicle::storage::git::{cob::DraftStore, Repository};
-
use radicle_git_ext::Oid;
+
use radicle::Oid;
use radicle_surf::diff::*;
use radicle_term::{Element, VStack};

@@ -196,24 +197,24 @@ impl ReviewItem {

    fn paths(&self) -> (Option<(&Path, Oid)>, Option<(&Path, Oid)>) {
        match self {
-
            Self::FileAdded { path, new, .. } => (None, Some((path, new.oid))),
-
            Self::FileDeleted { path, old, .. } => (Some((path, old.oid)), None),
+
            Self::FileAdded { path, new, .. } => (None, Some((path, new.oid.into()))),
+
            Self::FileDeleted { path, old, .. } => (Some((path, old.oid.into())), None),
            Self::FileMoved { moved } => (
-
                Some((&moved.old_path, moved.old.oid)),
-
                Some((&moved.new_path, moved.new.oid)),
+
                Some((&moved.old_path, moved.old.oid.into())),
+
                Some((&moved.new_path, moved.new.oid.into())),
            ),
            Self::FileCopied { copied } => (
-
                Some((&copied.old_path, copied.old.oid)),
-
                Some((&copied.new_path, copied.new.oid)),
+
                Some((&copied.old_path, copied.old.oid.into())),
+
                Some((&copied.new_path, copied.new.oid.into())),
            ),
            Self::FileModified { path, old, new, .. } => {
-
                (Some((path, old.oid)), Some((path, new.oid)))
+
                (Some((path, old.oid.into())), Some((path, new.oid.into())))
            }
            Self::FileEofChanged { path, old, new, .. } => {
-
                (Some((path, old.oid)), Some((path, new.oid)))
+
                (Some((path, old.oid.into())), Some((path, new.oid.into())))
            }
            Self::FileModeChanged { path, old, new, .. } => {
-
                (Some((path, old.oid)), Some((path, new.oid)))
+
                (Some((path, old.oid.into())), Some((path, new.oid.into())))
            }
        }
    }
@@ -452,7 +453,7 @@ impl FileReviewBuilder {
        }
    }

-
    fn item_diff(&mut self, item: ReviewItem) -> Result<git::raw::Diff, Error> {
+
    fn item_diff(&mut self, item: ReviewItem) -> Result<git2::Diff, Error> {
        let mut buf = Vec::new();
        let mut writer = unified_diff::Writer::new(&mut buf);
        writer.encode(&self.header)?;
@@ -471,7 +472,7 @@ impl FileReviewBuilder {
        }
        drop(writer);

-
        git::raw::Diff::from_buffer(&buf).map_err(Error::from)
+
        git2::Diff::from_buffer(&buf).map_err(Error::from)
    }
}

@@ -479,11 +480,11 @@ impl FileReviewBuilder {
/// of changes introduced by a patch.
pub struct Brain<'a> {
    /// Where the review draft is being stored.
-
    refname: git::Namespaced<'a>,
+
    refname: git::fmt::Namespaced<'a>,
    /// The commit pointed to by the ref.
-
    head: git::raw::Commit<'a>,
+
    head: git2::Commit<'a>,
    /// The tree of accepted changes pointed to by the head commit.
-
    accepted: git::raw::Tree<'a>,
+
    accepted: git2::Tree<'a>,
}

impl<'a> Brain<'a> {
@@ -491,9 +492,9 @@ impl<'a> Brain<'a> {
    fn new(
        patch: PatchId,
        remote: &NodeId,
-
        base: git::raw::Commit,
-
        repo: &'a git::raw::Repository,
-
    ) -> Result<Self, git::raw::Error> {
+
        base: git2::Commit,
+
        repo: &'a git2::Repository,
+
    ) -> Result<Self, git2::Error> {
        let refname = Self::refname(&patch, remote);
        let author = repo.signature()?;
        let oid = repo.commit(
@@ -525,8 +526,8 @@ impl<'a> Brain<'a> {
    fn load(
        patch: PatchId,
        remote: &NodeId,
-
        repo: &'a git::raw::Repository,
-
    ) -> Result<Self, git::raw::Error> {
+
        repo: &'a git2::Repository,
+
    ) -> Result<Self, git2::Error> {
        // TODO: Validate this leads to correct UX for potentially abandoned drafts on
        // past revisions.
        let refname = Self::refname(&patch, remote);
@@ -541,11 +542,7 @@ impl<'a> Brain<'a> {
    }

    /// Accept changes to the brain.
-
    fn accept(
-
        &mut self,
-
        diff: git::raw::Diff,
-
        repo: &'a git::raw::Repository,
-
    ) -> Result<(), git::raw::Error> {
+
    fn accept(&mut self, diff: git2::Diff, repo: &'a git2::Repository) -> Result<(), git2::Error> {
        let mut index = repo.apply_to_tree(&self.accepted, &diff, None)?;
        let accepted = index.write_tree_to(repo)?;
        self.accepted = repo.find_tree(accepted)?;
@@ -565,7 +562,7 @@ impl<'a> Brain<'a> {
    }

    /// Get the brain's refname given the patch and remote.
-
    fn refname(patch: &PatchId, remote: &NodeId) -> git::Namespaced<'a> {
+
    fn refname(patch: &PatchId, remote: &NodeId) -> git::fmt::Namespaced<'a> {
        git::refs::storage::draft::review(remote, patch)
    }
}
@@ -609,7 +606,7 @@ impl<'a> ReviewBuilder<'a> {
    pub fn run<G>(
        self,
        revision: &Revision,
-
        opts: &mut git::raw::DiffOptions,
+
        opts: &mut git2::DiffOptions,
        signer: &Device<G>,
    ) -> anyhow::Result<()>
    where
@@ -778,12 +775,12 @@ impl<'a> ReviewBuilder<'a> {

    fn diff(
        &self,
-
        brain: &git::raw::Tree<'_>,
-
        tree: &git::raw::Tree<'_>,
-
        repo: &'a git::raw::Repository,
-
        opts: &mut git::raw::DiffOptions,
+
        brain: &git2::Tree<'_>,
+
        tree: &git2::Tree<'_>,
+
        repo: &'a git2::Repository,
+
        opts: &mut git2::DiffOptions,
    ) -> Result<Diff, Error> {
-
        let mut find_opts = git::raw::DiffFindOptions::new();
+
        let mut find_opts = git2::DiffFindOptions::new();
        find_opts.exact_match_only(true);
        find_opts.all(true);
        find_opts.copies(false); // We don't support finding copies at the moment.
@@ -835,7 +832,7 @@ enum Error {
    #[error(transparent)]
    Format(#[from] std::fmt::Error),
    #[error(transparent)]
-
    Git(#[from] git::raw::Error),
+
    Git(#[from] git2::Error),
}

#[derive(Debug)]
modified crates/radicle-cli/src/commands/patch/show.rs
@@ -1,7 +1,7 @@
use std::process;

use radicle::cob::patch;
-
use radicle::git;
+
use radicle::git2;
use radicle::storage::git::Repository;

use crate::terminal as term;
@@ -29,7 +29,7 @@ pub fn run(
    verbose: bool,
    profile: &Profile,
    stored: &Repository,
-
    workdir: Option<&git::raw::Repository>,
+
    workdir: Option<&git2::Repository>,
) -> anyhow::Result<()> {
    let patches = term::cob::patches(profile, stored)?;
    let Some(patch) = patches.get(patch_id).map_err(|e| Error::WithHint {
modified crates/radicle-cli/src/commands/patch/update.rs
@@ -1,7 +1,8 @@
use radicle::cob::patch;
-
use radicle::git;
+
use radicle::git2;
use radicle::prelude::*;
use radicle::storage::git::Repository;
+
use radicle::Oid;

use crate::terminal as term;
use crate::terminal::patch::*;
@@ -9,11 +10,11 @@ use crate::terminal::patch::*;
/// Run patch update.
pub fn run(
    patch_id: patch::PatchId,
-
    base_id: Option<git::raw::Oid>,
+
    base_id: Option<Oid>,
    message: term::patch::Message,
    profile: &Profile,
    repository: &Repository,
-
    workdir: &git::raw::Repository,
+
    workdir: &git2::Repository,
) -> anyhow::Result<()> {
    // `HEAD`; This is what we are proposing as a patch.
    let head_branch = try_branch(workdir.head()?)?;
@@ -27,14 +28,17 @@ pub fn run(
    let head_oid = branch_oid(&head_branch)?;
    let base_oid = match base_id {
        Some(oid) => oid,
-
        None => repository.backend.merge_base(*target_oid, *head_oid)?,
+
        None => repository
+
            .backend
+
            .merge_base(target_oid.into(), head_oid.into())?
+
            .into(),
    };

    // N.b. we don't update if both the head and base are the same as
    // any previous revision
    if patch
        .revisions()
-
        .any(|(_, revision)| revision.head() == head_oid && **revision.base() == base_oid)
+
        .any(|(_, revision)| revision.head() == head_oid && *revision.base() == base_oid)
    {
        return Ok(());
    }
@@ -42,7 +46,7 @@ pub fn run(
    let (_, revision) = patch.latest();
    let message = term::patch::get_update_message(message, workdir, revision, &head_oid)?;
    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);

modified crates/radicle-cli/src/commands/remote.rs
@@ -8,7 +8,7 @@ use std::ffi::OsString;

use anyhow::anyhow;

-
use radicle::git::RefString;
+
use radicle::git::fmt::RefString;
use radicle::prelude::NodeId;
use radicle::storage::ReadStorage;

modified crates/radicle-cli/src/commands/remote/add.rs
@@ -1,7 +1,7 @@
use std::str::FromStr;

-
use radicle::git;
-
use radicle::git::RefString;
+
use radicle::git::fmt::RefString;
+
use radicle::git2;
use radicle::prelude::*;
use radicle::Profile;
use radicle_crypto::PublicKey;
@@ -18,7 +18,7 @@ pub fn run(
    name: Option<RefString>,
    tracking: Option<BranchName>,
    profile: &Profile,
-
    repo: &git::raw::Repository,
+
    repo: &git2::Repository,
    fetch: bool,
    sync: bool,
) -> anyhow::Result<()> {
modified crates/radicle-cli/src/commands/stats.rs
@@ -71,7 +71,7 @@ pub fn run(_args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
            let remote = remote?;
            let sigrefs = repo.reference_oid(&remote, &git::refs::storage::SIGREFS_BRANCH)?;
            let mut walk = repo.raw().revwalk()?;
-
            walk.push(*sigrefs)?;
+
            walk.push(sigrefs.into())?;

            stats.local.pushes += walk.count();
            stats.local.forks += 1;
modified crates/radicle-cli/src/commands/watch.rs
@@ -4,6 +4,8 @@ use std::{thread, time};
use anyhow::{anyhow, Context as _};

use radicle::git;
+
use radicle::git2;
+
use radicle::git2::ErrorExt as _;
use radicle::prelude::{NodeId, RepoId};
use radicle::storage::{ReadRepository, ReadStorage};

@@ -40,7 +42,7 @@ Options

pub struct Options {
    rid: Option<RepoId>,
-
    refstr: git::RefString,
+
    refstr: git::fmt::RefString,
    target: Option<git::Oid>,
    nid: Option<NodeId>,
    interval: time::Duration,
@@ -55,7 +57,7 @@ impl Args for Options {
        let mut rid = None;
        let mut nid: Option<NodeId> = 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 timeout: time::Duration = time::Duration::MAX;

@@ -151,7 +153,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
            thread::sleep(options.interval);
            let oid = reference(&repo, &nid, &qualified)?;
            if oid != initial {
-
                term::info!("{}", oid.unwrap_or(git::raw::Oid::zero().into()));
+
                term::info!("{}", oid.unwrap_or(git2::Oid::zero().into()));
                break;
            }
            if now.elapsed()? >= options.timeout {
@@ -165,11 +167,11 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
fn reference<R: ReadRepository>(
    repo: &R,
    nid: &NodeId,
-
    qual: &git::Qualified,
-
) -> Result<Option<git::Oid>, git::raw::Error> {
+
    qual: &git::fmt::Qualified,
+
) -> Result<Option<git::Oid>, git2::Error> {
    match repo.reference_oid(nid, qual) {
        Ok(oid) => Ok(Some(oid)),
-
        Err(e) if git::ext::is_not_found_err(&e) => Ok(None),
+
        Err(e) if e.is_not_found() => Ok(None),
        Err(e) => Err(e),
    }
}
modified crates/radicle-cli/src/git.rs
@@ -20,14 +20,16 @@ use thiserror::Error;

use radicle::crypto::ssh;
use radicle::git;
-
use radicle::git::raw as git2;
use radicle::git::{Version, VERSION_REQUIRED};
+
use radicle::git2;
use radicle::prelude::{NodeId, RepoId};
use radicle::storage::git::transport;

-
pub use radicle::git::raw::{
+
pub use radicle::Oid;
+

+
pub use radicle::git2::{
    build::CheckoutBuilder, AnnotatedCommit, Commit, Direction, ErrorCode, MergeAnalysis,
-
    MergeOptions, Oid, Reference, Repository, Signature,
+
    MergeOptions, Reference, Repository, Signature,
};

pub const CONFIG_COMMIT_GPG_SIGN: &str = "commit.gpgsign";
modified crates/radicle-cli/src/git/pretty_diff.rs
@@ -2,7 +2,8 @@ use std::fs;
use std::path::{Path, PathBuf};

use radicle::git;
-
use radicle_git_ext::Oid;
+
use radicle::git2;
+
use radicle::Oid;
use radicle_surf::diff;
use radicle_surf::diff::{Added, Copied, Deleted, FileStats, Hunks, Modified, Moved};
use radicle_surf::diff::{Diff, DiffContent, FileDiff, Hunk, Modification};
@@ -26,14 +27,14 @@ pub enum Blob {
/// A repository of Git blobs.
pub trait Repo {
    /// Lookup a blob from the repo.
-
    fn blob(&self, oid: git::Oid) -> Result<Blob, git::raw::Error>;
+
    fn blob(&self, oid: git::Oid) -> Result<Blob, git2::Error>;
    /// Lookup a file in the workdir.
    fn file(&self, path: &Path) -> Option<Blob>;
}

-
impl Repo for git::raw::Repository {
-
    fn blob(&self, oid: git::Oid) -> Result<Blob, git::raw::Error> {
-
        let blob = self.find_blob(*oid)?;
+
impl Repo for git2::Repository {
+
    fn blob(&self, oid: git::Oid) -> Result<Blob, git2::Error> {
+
        let blob = self.find_blob(oid.into())?;

        if blob.is_binary() {
            Ok(Blob::Binary)
@@ -338,7 +339,7 @@ impl ToPretty for Added {
        repo: &R,
    ) -> Self::Output {
        let old = None;
-
        let new = Some((self.path.as_path(), self.new.oid));
+
        let new = Some((self.path.as_path(), self.new.oid.into()));

        pretty_modification(header, &self.diff, old, new, repo, hi)
    }
@@ -354,7 +355,7 @@ impl ToPretty for Deleted {
        header: &Self::Context,
        repo: &R,
    ) -> Self::Output {
-
        let old = Some((self.path.as_path(), self.old.oid));
+
        let old = Some((self.path.as_path(), self.old.oid.into()));
        let new = None;

        pretty_modification(header, &self.diff, old, new, repo, hi)
@@ -371,8 +372,8 @@ impl ToPretty for Modified {
        header: &Self::Context,
        repo: &R,
    ) -> Self::Output {
-
        let old = Some((self.path.as_path(), self.old.oid));
-
        let new = Some((self.path.as_path(), self.new.oid));
+
        let old = Some((self.path.as_path(), self.old.oid.into()));
+
        let new = Some((self.path.as_path(), self.new.oid.into()));

        pretty_modification(header, &self.diff, old, new, repo, hi)
    }
@@ -595,8 +596,8 @@ mod test {
    use term::Element;

    use super::*;
-
    use radicle::git::raw::RepositoryOpenFlags;
-
    use radicle::git::raw::{Oid, Repository};
+
    use radicle::git2::RepositoryOpenFlags;
+
    use radicle::git2::{Oid, Repository};

    #[test]
    #[ignore]
modified crates/radicle-cli/src/git/unified_diff.rs
@@ -6,8 +6,8 @@ use std::path::PathBuf;
use radicle_surf::diff::FileStats;
use thiserror::Error;

-
use radicle::git;
-
use radicle::git::raw::Oid;
+
use radicle::git2;
+
use radicle::git2::Oid;
use radicle_surf::diff;
use radicle_surf::diff::{Diff, DiffContent, DiffFile, FileDiff, Hunk, Hunks, Line, Modification};

@@ -201,7 +201,7 @@ impl Decode for Diff {

        r.read_to_string(&mut s)?;

-
        let d = git::raw::Diff::from_buffer(s.as_ref())
+
        let d = git2::Diff::from_buffer(s.as_ref())
            .map_err(|e| Error::syntax(format!("decoding unified diff: {e}")))?;
        let d =
            Diff::try_from(d).map_err(|e| Error::syntax(format!("decoding unified diff: {e}")))?;
modified crates/radicle-cli/src/project.rs
@@ -1,7 +1,7 @@
use radicle::prelude::*;

use crate::git;
-
use radicle::git::RefStr;
+
use radicle::git::fmt::RefStr;
use radicle::node::NodeId;

/// Setup a repository remote and tracking branch.
modified crates/radicle-cli/src/terminal/args.rs
@@ -7,7 +7,7 @@ use anyhow::anyhow;

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

modified crates/radicle-cli/src/terminal/patch.rs
@@ -12,6 +12,7 @@ use radicle::cob;
use radicle::cob::patch;
use radicle::cob::Title;
use radicle::git;
+
use radicle::git2;
use radicle::patch::{Patch, PatchId};
use radicle::prelude::Profile;
use radicle::storage::git::Repository;
@@ -27,7 +28,7 @@ pub enum Error {
    #[error(transparent)]
    Fmt(#[from] fmt::Error),
    #[error("git: {0}")]
-
    Git(#[from] git::raw::Error),
+
    Git(#[from] git2::Error),
    #[error("i/o error: {0}")]
    Io(#[from] io::Error),
    #[error("invalid utf-8 string")]
@@ -145,7 +146,7 @@ pub fn message(title: &str, description: &str) -> String {
}

/// Create a helpful default `Patch` message out of one or more commit messages.
-
fn message_from_commits(name: &str, commits: Vec<git::raw::Commit>) -> Result<String, Error> {
+
fn message_from_commits(name: &str, commits: Vec<git2::Commit>) -> Result<String, Error> {
    let mut commits = commits.into_iter().rev();
    let count = commits.len();
    let Some(commit) = commits.next() else {
@@ -187,10 +188,10 @@ fn message_from_commits(name: &str, commits: Vec<git::raw::Commit>) -> Result<St

/// Return commits between the merge base and a head.
pub fn patch_commits<'a>(
-
    repo: &'a git::raw::Repository,
+
    repo: &'a git2::Repository,
    base: &git::Oid,
    head: &git::Oid,
-
) -> Result<Vec<git::raw::Commit<'a>>, git::raw::Error> {
+
) -> Result<Vec<git2::Commit<'a>>, git2::Error> {
    let mut commits = Vec::new();
    let mut revwalk = repo.revwalk()?;
    revwalk.push_range(&format!("{base}..{head}"))?;
@@ -204,7 +205,7 @@ pub fn patch_commits<'a>(

/// The message shown in the editor when creating a `Patch`.
fn create_display_message(
-
    repo: &git::raw::Repository,
+
    repo: &git2::Repository,
    base: &git::Oid,
    head: &git::Oid,
) -> Result<String, Error> {
@@ -225,7 +226,7 @@ fn create_display_message(
/// The user can bail out if an empty title is entered.
pub fn get_create_message(
    message: term::patch::Message,
-
    repo: &git::raw::Repository,
+
    repo: &git2::Repository,
    base: &git::Oid,
    head: &git::Oid,
) -> Result<(Title, String), Error> {
@@ -278,11 +279,11 @@ pub fn get_edit_message(

/// The message shown in the editor when updating a `Patch`.
fn update_display_message(
-
    repo: &git::raw::Repository,
-
    last_rev_head: &git::Oid,
-
    head: &git::Oid,
+
    repo: &git2::Repository,
+
    last_rev_head: &radicle::Oid,
+
    head: &radicle::Oid,
) -> Result<String, Error> {
-
    if !repo.graph_descendant_of(**head, **last_rev_head)? {
+
    if !repo.graph_descendant_of(head.into(), last_rev_head.into())? {
        return Ok(REVISION_MSG.trim_start().to_string());
    }

@@ -300,7 +301,7 @@ fn update_display_message(
/// Get a patch update message.
pub fn get_update_message(
    message: term::patch::Message,
-
    repo: &git::raw::Repository,
+
    repo: &git2::Repository,
    latest: &patch::Revision,
    head: &git::Oid,
) -> Result<String, Error> {
@@ -312,7 +313,7 @@ pub fn get_update_message(
}

/// List the given commits in a table.
-
pub fn list_commits(commits: &[git::raw::Commit]) -> anyhow::Result<()> {
+
pub fn list_commits(commits: &[git2::Commit]) -> anyhow::Result<()> {
    commits
        .iter()
        .map(|commit| {
@@ -333,9 +334,9 @@ pub fn list_commits(commits: &[git::raw::Commit]) -> anyhow::Result<()> {

/// Print commits ahead and behind.
pub fn print_commits_ahead_behind(
-
    repo: &git::raw::Repository,
-
    left: git::raw::Oid,
-
    right: git::raw::Oid,
+
    repo: &git2::Repository,
+
    left: git2::Oid,
+
    right: git2::Oid,
) -> anyhow::Result<()> {
    let (ahead, behind) = repo.graph_ahead_behind(left, right)?;

@@ -356,7 +357,7 @@ pub fn show(
    id: &PatchId,
    verbose: bool,
    stored: &Repository,
-
    workdir: Option<&git::raw::Repository>,
+
    workdir: Option<&git2::Repository>,
    profile: &Profile,
) -> anyhow::Result<()> {
    let (_, revision) = patch.latest();
@@ -366,11 +367,8 @@ pub fn show(
    } else {
        vec![]
    };
-
    let ahead_behind = common::ahead_behind(
-
        stored.raw(),
-
        *revision.head(),
-
        *patch.target().head(stored)?,
-
    )?;
+
    let ahead_behind =
+
        common::ahead_behind(stored.raw(), revision.head(), patch.target().head(stored)?)?;
    let author = patch.author();
    let author = term::format::Author::new(author.id(), profile, verbose);
    let labels = patch.labels().map(|l| l.to_string()).collect::<Vec<_>>();
@@ -484,23 +482,23 @@ fn patch_commit_lines(
#[cfg(test)]
mod test {
    use super::*;
-
    use radicle::git::refname;
+
    use radicle::git::fmt::refname;
    use radicle::test::fixtures;
    use std::path;

    fn commit(
-
        repo: &git::raw::Repository,
-
        branch: &git::RefStr,
+
        repo: &git2::Repository,
+
        branch: &git::fmt::RefStr,
        parent: &git::Oid,
        msg: &str,
    ) -> git::Oid {
-
        let sig = git::raw::Signature::new(
+
        let sig = git2::Signature::new(
            "anonymous",
            "anonymous@radicle.example.com",
-
            &git::raw::Time::new(0, 0),
+
            &git2::Time::new(0, 0),
        )
        .unwrap();
-
        let head = repo.find_commit(**parent).unwrap();
+
        let head = repo.find_commit(parent.into()).unwrap();
        let tree =
            git::write_tree(path::Path::new("README"), "Hello World!\n".as_bytes(), repo).unwrap();

@@ -514,7 +512,6 @@ mod test {
    fn test_create_display_message() {
        let tmpdir = tempfile::tempdir().unwrap();
        let (repo, commit_0) = fixtures::repository(&tmpdir);
-
        let commit_0 = commit_0.into();
        let commit_1 = commit(
            &repo,
            &refname!("feature"),
@@ -625,7 +622,6 @@ mod test {
    fn test_update_display_message() {
        let tmpdir = tempfile::tempdir().unwrap();
        let (repo, commit_0) = fixtures::repository(&tmpdir);
-
        let commit_0 = commit_0.into();

        let commit_1 = commit(&repo, &refname!("feature"), &commit_0, "commit 1\n");
        let commit_2 = commit(&repo, &refname!("feature"), &commit_1, "commit 2\n");
modified crates/radicle-cli/src/terminal/patch/common.rs
@@ -1,15 +1,16 @@
use anyhow::anyhow;

use radicle::git;
-
use radicle::git::raw::Oid;
+
use radicle::git2;
use radicle::prelude::*;
use radicle::storage::git::Repository;
+
use radicle::Oid;

use crate::terminal as term;

/// Give the oid of the branch or an appropriate error.
#[inline]
-
pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result<git::Oid> {
+
pub fn branch_oid(branch: &git2::Branch) -> anyhow::Result<Oid> {
    let oid = branch
        .get()
        .target()
@@ -18,7 +19,7 @@ pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result<git::Oid> {
}

#[inline]
-
fn get_branch(git_ref: git::Qualified) -> git::RefString {
+
fn get_branch(git_ref: git::fmt::Qualified) -> git::fmt::RefString {
    let (_, _, head, tail) = git_ref.non_empty_components();
    std::iter::once(head).chain(tail).collect()
}
@@ -27,32 +28,34 @@ fn get_branch(git_ref: git::Qualified) -> git::RefString {
/// as well as your own (eg. `rad/master`).
pub fn get_merge_target(
    storage: &Repository,
-
    head_branch: &git::raw::Branch,
-
) -> anyhow::Result<(git::RefString, git::Oid)> {
+
    head_branch: &git2::Branch,
+
) -> anyhow::Result<(git::fmt::RefString, git::Oid)> {
    let (qualified_ref, target_oid) = storage.canonical_head()?;
    let head_oid = branch_oid(head_branch)?;
-
    let merge_base = storage.raw().merge_base(*head_oid, *target_oid)?;
+
    let merge_base = storage
+
        .raw()
+
        .merge_base(head_oid.into(), target_oid.into())?;

-
    if head_oid == merge_base.into() {
+
    if head_oid == merge_base {
        anyhow::bail!("commits are already included in the target branch; nothing to do");
    }

-
    Ok((get_branch(qualified_ref), (*target_oid).into()))
+
    Ok((get_branch(qualified_ref), (target_oid)))
}

/// Get the diff stats between two commits.
/// Should match the default output of `git diff <old> <new> --stat` exactly.
pub fn diff_stats(
-
    repo: &git::raw::Repository,
+
    repo: &git2::Repository,
    old: &Oid,
    new: &Oid,
-
) -> Result<git::raw::DiffStats, git::raw::Error> {
-
    let old = repo.find_commit(*old)?;
-
    let new = repo.find_commit(*new)?;
+
) -> Result<git2::DiffStats, git2::Error> {
+
    let old = repo.find_commit(old.into())?;
+
    let new = repo.find_commit(new.into())?;
    let old_tree = old.tree()?;
    let new_tree = new.tree()?;
    let mut diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None)?;
-
    let mut find_opts = git::raw::DiffFindOptions::new();
+
    let mut find_opts = git2::DiffFindOptions::new();

    diff.find_similar(Some(&mut find_opts))?;
    diff.stats()
@@ -60,11 +63,11 @@ pub fn diff_stats(

/// Create a human friendly message about git's sync status.
pub fn ahead_behind(
-
    repo: &git::raw::Repository,
+
    repo: &git2::Repository,
    revision_oid: Oid,
    head_oid: Oid,
) -> 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 {
        return Ok(term::Line::new(term::format::dim("up to date")));
    }
@@ -80,7 +83,7 @@ pub fn ahead_behind(
}

/// Get the branches that point to a commit.
-
pub fn branches(target: &Oid, repo: &git::raw::Repository) -> anyhow::Result<Vec<String>> {
+
pub fn branches(target: &Oid, repo: &git2::Repository) -> anyhow::Result<Vec<String>> {
    let mut branches: Vec<String> = vec![];

    for r in repo.references()?.flatten() {
@@ -88,7 +91,7 @@ pub fn branches(target: &Oid, repo: &git::raw::Repository) -> anyhow::Result<Vec
            continue;
        }
        if let (Some(oid), Some(name)) = (&r.target(), &r.shorthand()) {
-
            if oid == target {
+
            if target == oid {
                branches.push(name.to_string());
            };
        };
@@ -97,9 +100,9 @@ pub fn branches(target: &Oid, repo: &git::raw::Repository) -> anyhow::Result<Vec
}

#[inline]
-
pub fn try_branch(reference: git::raw::Reference<'_>) -> anyhow::Result<git::raw::Branch> {
+
pub fn try_branch(reference: git2::Reference<'_>) -> anyhow::Result<git2::Branch> {
    let branch = if reference.is_branch() {
-
        git::raw::Branch::wrap(reference)
+
        git2::Branch::wrap(reference)
    } else {
        anyhow::bail!("cannot create patch from detached head; aborting")
    };
modified crates/radicle-cob/Cargo.toml
@@ -38,6 +38,6 @@ thiserror = { workspace = true }
fastrand = { workspace = true }
qcheck = { workspace = true }
qcheck-macros = { workspace = true }
-
radicle-crypto = { workspace = true, features = ["test", "radicle-git-ext"] }
+
radicle-crypto = { workspace = true, features = ["test", "radicle-git-ref-format"] }
radicle-oid = { workspace = true, features = ["qcheck"] }
tempfile = { workspace = true }
modified crates/radicle-crypto/Cargo.toml
@@ -23,7 +23,7 @@ ec25519 = "0.1.0"
fastrand = { workspace = true, optional = true }
multibase = { workspace = true }
qcheck = { workspace = true, optional = true }
-
radicle-git-ext = { workspace = true, optional = true }
+
radicle-git-ref-format = { workspace = true, optional = true, features = ["macro"] }
radicle-ssh = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
signature = { workspace = true }
modified crates/radicle-crypto/src/lib.rs
@@ -350,20 +350,20 @@ impl PublicKey {
        multibase::encode(multibase::Base::Base58Btc, buf)
    }

-
    #[cfg(feature = "radicle-git-ext")]
-
    pub fn to_namespace(&self) -> radicle_git_ext::ref_format::RefString {
-
        use radicle_git_ext::ref_format::{refname, Component};
+
    #[cfg(feature = "radicle-git-ref-format")]
+
    pub fn to_namespace(&self) -> radicle_git_ref_format::RefString {
+
        use radicle_git_ref_format::{refname, Component};
        refname!("refs/namespaces").join(Component::from(self))
    }

-
    #[cfg(feature = "radicle-git-ext")]
-
    pub fn to_component(&self) -> radicle_git_ext::ref_format::Component {
-
        radicle_git_ext::ref_format::Component::from(self)
+
    #[cfg(feature = "radicle-git-ref-format")]
+
    pub fn to_component(&self) -> radicle_git_ref_format::Component {
+
        radicle_git_ref_format::Component::from(self)
    }

-
    #[cfg(feature = "radicle-git-ext")]
+
    #[cfg(feature = "radicle-git-ref-format")]
    pub fn from_namespaced(
-
        refstr: &radicle_git_ext::ref_format::Namespaced,
+
        refstr: &radicle_git_ref_format::Namespaced,
    ) -> Result<Self, PublicKeyError> {
        let name = refstr.namespace().into_inner();

@@ -403,10 +403,10 @@ impl Deref for PublicKey {
    }
}

-
#[cfg(feature = "radicle-git-ext")]
-
impl From<&PublicKey> for radicle_git_ext::ref_format::Component<'_> {
+
#[cfg(feature = "radicle-git-ref-format")]
+
impl From<&PublicKey> for radicle_git_ref_format::Component<'_> {
    fn from(id: &PublicKey) -> Self {
-
        use radicle_git_ext::ref_format::{Component, RefString};
+
        use radicle_git_ref_format::{Component, RefString};
        let refstr =
            RefString::try_from(id.to_string()).expect("encoded public keys are valid ref strings");
        Component::from_refstr(refstr).expect("encoded public keys are valid refname components")
modified crates/radicle-fetch/Cargo.toml
@@ -20,5 +20,6 @@ gix-transport = { version = "0.44.0", features = ["blocking-client"] }
log = { workspace = true, features = ["std"] }
nonempty = { workspace = true }
radicle = { workspace = true }
-
radicle-git-ext = { workspace = true, features = ["bstr"] }
+
radicle-oid = { workspace = true, features = ["gix"] }
+
radicle-git-ref-format = { workspace = true, features = ["bstr"] }
thiserror = { workspace = true }

\ No newline at end of file
modified crates/radicle-fetch/src/git.rs
@@ -13,7 +13,7 @@ pub(crate) mod 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")
+
        Oid::from(oid)
    }

    /// Convert from an [`Oid`] to an [`ObjectId`].
modified crates/radicle-fetch/src/git/mem.rs
@@ -1,6 +1,7 @@
use std::collections::HashMap;

-
use radicle::git::{Component, Oid, Qualified, RefString};
+
use radicle::git::fmt::{Component, Qualified, RefString};
+
use radicle::git::Oid;
use radicle::prelude::PublicKey;

use super::refs::{Applied, RefUpdate, Update};
@@ -43,7 +44,7 @@ impl Refdb {
                    let name = name.into_qualified().into_owned();
                    let prev = match self.0.insert(name.clone(), target) {
                        Some(prev) => prev,
-
                        None => radicle::git::raw::Oid::zero().into(),
+
                        None => radicle::git2::Oid::zero().into(),
                    };
                    ap.updated.push(RefUpdate::Updated {
                        name: name.to_ref_string(),
modified crates/radicle-fetch/src/git/refs/update.rs
@@ -18,7 +18,8 @@
use std::collections::BTreeMap;

use either::Either;
-
use radicle::git::{Namespaced, Oid, Qualified};
+
use radicle::git::fmt::{Namespaced, Qualified};
+
use radicle::git::Oid;
use radicle::prelude::PublicKey;

pub use radicle::storage::RefUpdate;
modified crates/radicle-fetch/src/git/repository.rs
@@ -1,7 +1,11 @@
pub mod error;

use either::Either;
-
use radicle::git::{self, Namespaced, Oid, Qualified};
+
use radicle::git::{
+
    fmt::{Namespaced, Qualified},
+
    Oid,
+
};
+
use radicle::git2;
use radicle::storage::git::Repository;

use super::refs::{Applied, Policy, RefUpdate, Update};
@@ -47,13 +51,13 @@ pub fn contains(repo: &Repository, oid: Oid) -> Result<bool, error::Contains> {
/// - The object does not peel to a commit
/// - Attempting to find the object fails
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
-
            .peel(git::raw::ObjectType::Commit)
+
            .peel(git2::ObjectType::Commit)
            .map_err(|err| error::Ancestry::Peel { oid, err })?
            .id()
            .into()),
-
        Err(e) if git::is_not_found_err(&e) => Err(error::Ancestry::Missing { oid }),
+
        Err(e) if e.code() == git2::ErrorCode::NotFound => Err(error::Ancestry::Missing { oid }),
        Err(err) => Err(error::Ancestry::Object { oid, err }),
    }
}
@@ -79,7 +83,7 @@ pub fn ahead_behind(

    let (ahead, behind) = repo
        .backend
-
        .graph_ahead_behind(*new_commit, *old_commit)
+
        .graph_ahead_behind(new_commit.into(), old_commit.into())
        .map_err(|err| error::Ancestry::Check {
            old: old_commit,
            new: new_commit,
@@ -99,7 +103,7 @@ pub fn refname_to_id<'a, N>(repo: &Repository, refname: N) -> Result<Option<Oid>
where
    N: Into<Qualified<'a>>,
{
-
    use radicle::git::raw::ErrorCode::NotFound;
+
    use radicle::git2::ErrorCode::NotFound;

    let refname = refname.into();
    match repo.backend.refname_to_id(refname.as_ref()) {
@@ -169,7 +173,7 @@ fn direct<'a>(
        });
    };

-
    if prev == *target {
+
    if target == prev {
        // If the two objects are identical, their ancestry does not matter,
        // we can always skip the update.
        return Ok(RefUpdate::Skipped {
@@ -180,7 +184,7 @@ fn direct<'a>(
    }

    let ancestry = {
-
        use git::raw::ObjectType::{self, *};
+
        use git2::ObjectType::{self, *};
        const ANY_KIND: Option<ObjectType> = Some(Any);

        let prev = repo.backend.find_object(prev, ANY_KIND).map_err(|err| {
@@ -192,7 +196,7 @@ fn direct<'a>(

        let target = repo
            .backend
-
            .find_object(*target, ANY_KIND)
+
            .find_object(target.into(), ANY_KIND)
            .map_err(|err| error::Update::Ancestry(error::Ancestry::Object { oid: target, err }))?;

        match (prev.kind(), target.kind()) {
@@ -275,7 +279,7 @@ fn prune<'a>(
    name: Namespaced<'a>,
    prev: Either<Oid, Qualified<'a>>,
) -> Result<Updated<'a>, error::Update> {
-
    use radicle::git::raw::ObjectType;
+
    use radicle::git2::ObjectType;

    match find(repo, &name)? {
        Some(mut r) => {
@@ -303,10 +307,10 @@ fn prune<'a>(
fn find<'a>(
    repo: &'a Repository,
    name: &Namespaced<'_>,
-
) -> Result<Option<radicle::git::raw::Reference<'a>>, error::Update> {
+
) -> Result<Option<radicle::git2::Reference<'a>>, error::Update> {
    match repo.backend.find_reference(name.as_ref()) {
        Ok(r) => Ok(Some(r)),
-
        Err(e) if matches!(e.code(), radicle::git::raw::ErrorCode::NotFound) => Ok(None),
+
        Err(e) if matches!(e.code(), radicle::git2::ErrorCode::NotFound) => Ok(None),
        Err(err) => Err(error::Update::Find {
            name: name.clone().into_owned(),
            err,
modified crates/radicle-fetch/src/git/repository/error.rs
@@ -1,9 +1,13 @@
-
use radicle::git::{ext, raw, Namespaced, Oid, Qualified};
+
use radicle::git::{
+
    fmt::{Namespaced, Qualified},
+
    Oid,
+
};
+
use radicle::git2;
use thiserror::Error;

#[derive(Debug, Error)]
#[error("could not open Git ODB")]
-
pub struct Contains(#[source] pub raw::Error);
+
pub struct Contains(#[source] pub git2::Error);

#[derive(Debug, Error)]
pub enum Ancestry {
@@ -14,19 +18,19 @@ pub enum Ancestry {
        old: Oid,
        new: Oid,
        #[source]
-
        err: raw::Error,
+
        err: git2::Error,
    },
    #[error("failed to peel object to commit {oid}: {err}")]
    Peel {
        oid: Oid,
        #[source]
-
        err: raw::Error,
+
        err: git2::Error,
    },
    #[error("failed to find object {oid}: {err}")]
    Object {
        oid: Oid,
        #[source]
-
        err: raw::Error,
+
        err: git2::Error,
    },
}

@@ -35,15 +39,15 @@ pub enum Ancestry {
pub struct Resolve {
    pub name: Qualified<'static>,
    #[source]
-
    pub err: raw::Error,
+
    pub err: git2::Error,
}

#[derive(Debug, Error)]
#[error("failed to scan for refs matching {pattern}")]
pub struct Scan {
-
    pub pattern: radicle::git::PatternString,
+
    pub pattern: radicle::git::fmt::refspec::PatternString,
    #[source]
-
    pub err: ext::Error,
+
    pub err: git2::Error,
}

#[derive(Debug, Error)]
@@ -55,19 +59,19 @@ pub enum Update {
        name: Namespaced<'static>,
        target: Oid,
        #[source]
-
        err: raw::Error,
+
        err: git2::Error,
    },
    #[error("failed to delete reference {name}")]
    Delete {
        name: Namespaced<'static>,
        #[source]
-
        err: raw::Error,
+
        err: git2::Error,
    },
    #[error("failed to find ref {name}")]
    Find {
        name: Namespaced<'static>,
        #[source]
-
        err: raw::Error,
+
        err: git2::Error,
    },
    #[error("non-fast-forward update of {name} (current: {cur}, new: {new})")]
    NonFF {
@@ -76,7 +80,7 @@ pub enum Update {
        cur: Oid,
    },
    #[error("failed to peel ref to object")]
-
    Peel(#[source] raw::Error),
+
    Peel(#[source] git2::Error),
    #[error(transparent)]
    Resolve(#[from] Resolve),

modified crates/radicle-fetch/src/handle.rs
@@ -85,7 +85,7 @@ impl<S> Handle<S> {
pub mod error {
    use radicle::node::policy;
    use radicle::prelude::RepoId;
-
    use radicle::{git, storage};
+
    use radicle::{git2, storage};
    use thiserror::Error;

    #[derive(Debug, Error)]
@@ -115,7 +115,7 @@ pub mod error {
        Storage(#[from] storage::Error),

        #[error(transparent)]
-
        Git(#[from] git::raw::Error),
+
        Git(#[from] git2::Error),

        #[error(transparent)]
        Refs(#[from] storage::refs::Error),
modified crates/radicle-fetch/src/policy.rs
@@ -126,7 +126,7 @@ pub mod error {
        Storage(#[from] storage::Error),

        #[error(transparent)]
-
        Git(#[from] radicle::git::raw::Error),
+
        Git(#[from] radicle::git2::Error),

        #[error(transparent)]
        Refs(#[from] storage::refs::Error),
modified crates/radicle-fetch/src/refs.rs
@@ -1,7 +1,11 @@
use bstr::{BString, ByteSlice};
use either::Either;
use radicle::crypto::PublicKey;
-
use radicle::git::{self, Component, Namespaced, Oid, Qualified};
+
use radicle::git::{
+
    self,
+
    fmt::{Component, Namespaced, Qualified},
+
    Oid,
+
};
use thiserror::Error;

pub use radicle::git::refs::storage::Special;
modified crates/radicle-fetch/src/stage.rs
@@ -37,7 +37,7 @@ use either::Either;
use gix_protocol::handshake::Ref;
use nonempty::NonEmpty;
use radicle::crypto::PublicKey;
-
use radicle::git::{refname, Component, Namespaced, Qualified};
+
use radicle::git::fmt::{Component, Namespaced, Qualified};
use radicle::storage::git::Repository;
use radicle::storage::refs::{RefsAt, Special};
use radicle::storage::ReadRepository;
@@ -52,7 +52,7 @@ use crate::{policy, refs};

pub mod error {
    use radicle::crypto::PublicKey;
-
    use radicle::git::RefString;
+
    use radicle::git::fmt::RefString;
    use thiserror::Error;

    use crate::transport::WantsHavesError;
@@ -484,7 +484,8 @@ impl ProtocolStage for DataRefs {
            }

            // Prune refs not in signed
-
            let prefix_rad = refname!("refs/rad");
+
            let prefix_rad =
+
                radicle::git::fmt::RefString::try_from("refs/rad").expect("reference is valid");
            for (name, target) in repo.references_of(remote)? {
                // 'rad/' refs are never subject to pruning
                if name.starts_with(prefix_rad.as_str()) {
modified crates/radicle-fetch/src/state.rs
@@ -3,7 +3,7 @@ use std::time::Instant;

use gix_protocol::handshake;
use radicle::crypto::PublicKey;
-
use radicle::git::{Oid, Qualified};
+
use radicle::git::{fmt::Qualified, Oid};
use radicle::identity::{Did, Doc, DocError};

use radicle::prelude::Verified;
@@ -71,7 +71,7 @@ pub mod error {
        #[error(transparent)]
        RemoteRefs(#[from] sigrefs::error::RemoteRefs),
        #[error("failed to get remote namespaces: {0}")]
-
        RemoteIds(#[source] radicle::git::raw::Error),
+
        RemoteIds(#[source] radicle::git2::Error),
        #[error(transparent)]
        Step(#[from] Step),
        #[error(transparent)]
modified crates/radicle-fetch/src/transport.rs
@@ -13,8 +13,8 @@ use gix_protocol::handshake;
use gix_transport::client;
use gix_transport::Protocol;
use gix_transport::Service;
+
use radicle::git::fmt::Qualified;
use radicle::git::Oid;
-
use radicle::git::Qualified;
use radicle::storage::git::Repository;
use thiserror::Error;

modified crates/radicle-git-ref-format/Cargo.toml
@@ -11,6 +11,7 @@ rust-version.workspace = true

[features]
macro = []
+
bstr = ["git-ref-format-core/bstr"]

[dependencies]
-
git-ref-format-core = { version = "0.3.0", default-features = false }

\ No newline at end of file
+
git-ref-format-core = { workspace = true, default-features = false }

\ No newline at end of file
modified crates/radicle-node/Cargo.toml
@@ -34,9 +34,6 @@ nonempty = { workspace = true, features = ["serialize"] }
qcheck = { workspace = true, optional = true }
radicle = { workspace = true, features = ["logger"] }
radicle-fetch = { workspace = true }
-
# N.b. this is required to use macros, even though it's re-exported
-
# through radicle
-
radicle-git-ext = { workspace = true, features = ["serde"] }
radicle-protocol = { workspace = true }
radicle-signals = { workspace = true }
sqlite = { workspace = true, features = ["bundled"] }
modified crates/radicle-node/src/test/environment.rs
@@ -15,7 +15,7 @@ use radicle::crypto;
use radicle::crypto::ssh::{keystore::MemorySigner, Keystore};
use radicle::crypto::test::signer::MockSigner;
use radicle::crypto::{KeyPair, Seed};
-
use radicle::git::refname;
+
use radicle::git::fmt::refname;
use radicle::identity::{RepoId, Visibility};
use radicle::node::config::ConnectAddress;
use radicle::node::device::Device;
@@ -547,7 +547,7 @@ where
        &mut self,
        name: &str,
        description: &str,
-
        repo: &git::raw::Repository,
+
        repo: &git2::Repository,
    ) -> RepoId {
        transport::local::register(self.storage.clone());

@@ -572,14 +572,14 @@ where
        );

        // Push local branches to storage.
-
        let mut refs = Vec::<(git::Qualified, git::Qualified)>::new();
-
        for branch in repo.branches(Some(git::raw::BranchType::Local)).unwrap() {
+
        let mut refs = Vec::<(git::fmt::Qualified, git::fmt::Qualified)>::new();
+
        for branch in repo.branches(Some(git2::BranchType::Local)).unwrap() {
            let (branch, _) = branch.unwrap();
-
            let name = git::RefString::try_from(branch.name().unwrap().unwrap()).unwrap();
+
            let name = git::fmt::RefString::try_from(branch.name().unwrap().unwrap()).unwrap();

            refs.push((
-
                git::lit::refs_heads(&name).into(),
-
                git::lit::refs_heads(&name).into(),
+
                git::fmt::lit::refs_heads(&name).into(),
+
                git::fmt::lit::refs_heads(&name).into(),
            ));
        }
        git::push(repo, "rad", refs.iter().map(|(a, b)| (a, b))).unwrap();
modified crates/radicle-node/src/test/handle.rs
@@ -3,7 +3,7 @@ use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time;

-
use radicle::git;
+
use radicle::git2;
use radicle::storage::refs::RefsAt;

use crate::identity::RepoId;
@@ -96,7 +96,7 @@ impl radicle::node::Handle for Handle {

        Ok(RefsAt {
            remote: self.nid()?,
-
            at: git::raw::Oid::zero().into(),
+
            at: git2::Oid::zero().into(),
        })
    }

modified crates/radicle-node/src/test/node.rs
@@ -16,7 +16,8 @@ use radicle::crypto::ssh::keystore::MemorySigner;
use radicle::crypto::test::signer::MockSigner;
use radicle::crypto::Signature;
use radicle::git;
-
use radicle::git::refname;
+
use radicle::git::fmt::refname;
+
use radicle::git2;
use radicle::identity::{RepoId, Visibility};
use radicle::node::config::ConnectAddress;
use radicle::node::policy::store as policy;
@@ -363,7 +364,7 @@ impl<G: Signer<Signature> + cyphernet::Ecdh> NodeHandle<G> {
    /// of the new commit, and the reference will be updated.
    ///
    /// The `rad/sigrefs` are then updated to reflect the new change.
-
    pub fn commit_to(&self, rid: RepoId, refname: impl AsRef<git::RefStr>) {
+
    pub fn commit_to(&self, rid: RepoId, refname: impl AsRef<git::fmt::RefStr>) {
        use radicle::test::arbitrary;

        let refname = refname.as_ref();
@@ -371,7 +372,7 @@ impl<G: Signer<Signature> + cyphernet::Ecdh> NodeHandle<G> {
        let raw = &repo.backend;

        let info = self.storage.info();
-
        let author = git::raw::Signature::now(&info.name(), &info.email()).unwrap();
+
        let author = git2::Signature::now(&info.name(), &info.email()).unwrap();

        let tree = {
            let mut tb = raw.treebuilder(None).unwrap();
@@ -379,7 +380,7 @@ impl<G: Signer<Signature> + cyphernet::Ecdh> NodeHandle<G> {
            tb.insert(
                arbitrary::alphanumeric(10),
                blob,
-
                git::raw::FileMode::Blob.into(),
+
                git2::FileMode::Blob.into(),
            )
            .unwrap();
            let oid = tb.write().unwrap();
@@ -491,7 +492,7 @@ impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer<Signature> + Clone> Node<G> {
        &mut self,
        name: &str,
        description: &str,
-
        repo: &git::raw::Repository,
+
        repo: &git2::Repository,
    ) -> RepoId {
        transport::local::register(self.storage.clone());

@@ -516,14 +517,14 @@ impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer<Signature> + Clone> Node<G> {
        );

        // Push local branches to storage.
-
        let mut refs = Vec::<(git::Qualified, git::Qualified)>::new();
-
        for branch in repo.branches(Some(git::raw::BranchType::Local)).unwrap() {
+
        let mut refs = Vec::<(git::fmt::Qualified, git::fmt::Qualified)>::new();
+
        for branch in repo.branches(Some(git2::BranchType::Local)).unwrap() {
            let (branch, _) = branch.unwrap();
-
            let name = git::RefString::try_from(branch.name().unwrap().unwrap()).unwrap();
+
            let name = git::fmt::RefString::try_from(branch.name().unwrap().unwrap()).unwrap();

            refs.push((
-
                git::lit::refs_heads(&name).into(),
-
                git::lit::refs_heads(&name).into(),
+
                git::fmt::lit::refs_heads(&name).into(),
+
                git::fmt::lit::refs_heads(&name).into(),
            ));
        }
        git::push(repo, "rad", refs.iter().map(|(a, b)| (a, b))).unwrap();
modified crates/radicle-node/src/test/peer.rs
@@ -138,7 +138,7 @@ impl<G: crypto::signature::Signer<crypto::Signature>> Peer<Storage, G> {
            &repo,
            name.try_into().unwrap(),
            description,
-
            radicle::git::refname!("master"),
+
            radicle::git::fmt::refname!("master"),
            Visibility::default(),
            self.signer(),
            self.storage(),
modified crates/radicle-node/src/tests.rs
@@ -1567,7 +1567,7 @@ fn test_queued_fetch_from_ann_same_rid() {
    let refname = carol
        .id()
        .to_namespace()
-
        .join(git::refname!("refs/sigrefs"));
+
        .join(git::fmt::refname!("refs/sigrefs"));

    // Finish the 1st fetch.
    // Ensure the ref is in the storage and cache.
@@ -1749,7 +1749,7 @@ fn test_init_and_seed() {
        &repo,
        "alice".try_into().unwrap(),
        "alice's repo",
-
        git::refname!("master"),
+
        git::fmt::refname!("master"),
        Visibility::default(),
        alice.signer(),
        alice.storage(),
modified crates/radicle-node/src/tests/e2e.rs
@@ -3,6 +3,7 @@ use std::{collections::HashSet, thread, time};
use radicle::cob::Title;
use test_log::test;

+
use radicle::git2::ErrorExt as _;
use radicle::node::device::Device;
use radicle::node::policy::Scope;
use radicle::node::Event;
@@ -248,7 +249,7 @@ fn test_replication_ref_in_sigrefs() {
    bob.storage
        .repository_mut(acme)
        .unwrap()
-
        .reference(&bob.id, &git::qualified!("refs/heads/master"))
+
        .reference(&bob.id, &git::fmt::qualified!("refs/heads/master"))
        .unwrap()
        .delete()
        .unwrap();
@@ -271,7 +272,7 @@ fn test_replication_ref_in_sigrefs() {
            .storage
            .repository(acme)
            .unwrap()
-
            .reference(&bob.id, &git::qualified!("refs/heads/master"))
+
            .reference(&bob.id, &git::fmt::qualified!("refs/heads/master"))
            .is_ok(),
        "refs/namespaces/{}/refs/heads/master does not exist",
        bob.id
@@ -292,8 +293,8 @@ fn test_replication_invalid() {
    // Create some unsigned refs for Carol in Bob's storage.
    repo.raw()
        .reference(
-
            &git::qualified!("refs/heads/carol").with_namespace(carol.public_key().into()),
-
            *head,
+
            &git::fmt::qualified!("refs/heads/carol").with_namespace(carol.public_key().into()),
+
            head.into(),
            true,
            &String::default(),
        )
@@ -579,7 +580,7 @@ fn test_clone() {
        .canonical_head()
        .unwrap();

-
    assert_eq!(oid, *canonical);
+
    assert_eq!(canonical, oid);

    // Make sure that bob has refs/rad/id set
    assert!(bob
@@ -1193,7 +1194,6 @@ fn missing_default_branch() {

#[test]
fn missing_delegate_default_branch() {
-
    use radicle::git::raw;
    use radicle::identity::Identity;
    use radicle::storage::git::Repository;
    let tmp = tempfile::tempdir().unwrap();
@@ -1238,7 +1238,7 @@ fn missing_delegate_default_branch() {
        );
        assert!(matches!(
            default_branch,
-
            Err(radicle::git::Error::Git(e)) if e.code() == raw::ErrorCode::NotFound
+
            Err(e) if e.is_not_found()
        ));
    };

@@ -1528,10 +1528,10 @@ fn test_fetch_emits_canonical_ref_update() {
    let result = bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap();
    assert!(result.is_success());

-
    let default_branch: git::Qualified = {
+
    let default_branch: git::fmt::Qualified = {
        let repo = alice.storage.repository(rid).unwrap();
        let proj = repo.project().unwrap();
-
        git::lit::refs_heads(proj.default_branch()).into()
+
        git::fmt::lit::refs_heads(proj.default_branch()).into()
    };
    alice.commit_to(rid, &default_branch);

modified crates/radicle-node/src/worker/fetch.rs
@@ -218,7 +218,7 @@ fn notify(
                // for sigref verification.
                continue;
            }
-
            if let Some(rest) = r.strip_prefix(git::refname!("refs/heads/patches")) {
+
            if let Some(rest) = r.strip_prefix(git::fmt::refname!("refs/heads/patches")) {
                if radicle::cob::ObjectId::from_str(rest.as_str()).is_ok() {
                    // Don't notify about patch branches, since we already get
                    // notifications about patch updates.
@@ -422,7 +422,7 @@ fn set_canonical_refs(
                let oid = object.id();
                if let Err(e) = repo.backend.reference(
                    refname.clone().as_str(),
-
                    *oid,
+
                    oid.into(),
                    true,
                    "set-canonical-reference from fetch (radicle)",
                ) {
modified crates/radicle-protocol/Cargo.toml
@@ -23,7 +23,6 @@ nonempty = { workspace = true, features = ["serialize"] }
qcheck = { workspace = true, optional = true }
radicle = { workspace = true, features = ["logger"] }
radicle-fetch = { workspace = true }
-
radicle-git-ext = { workspace = true, features = ["serde"] }
sqlite = { workspace = true, features = ["bundled"] }
scrypt = { version = "0.11.0", default-features = false }
serde = { workspace = true, features = ["derive"] }
modified crates/radicle-protocol/src/service.rs
@@ -184,9 +184,7 @@ struct Peer {
#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error(transparent)]
-
    Git(#[from] radicle::git::raw::Error),
-
    #[error(transparent)]
-
    GitExt(#[from] radicle::git::ext::Error),
+
    Git(#[from] radicle::git2::Error),
    #[error(transparent)]
    Storage(#[from] storage::Error),
    #[error(transparent)]
modified crates/radicle-protocol/src/service/message.rs
@@ -679,7 +679,7 @@ mod tests {
    use fastrand;
    use localtime::LocalTime;
    use qcheck_macros::quickcheck;
-
    use radicle::git::raw;
+
    use radicle::git2;
    use radicle::test::arbitrary;

    use crate::wire::Decode as _;
@@ -690,7 +690,7 @@ mod tests {
    fn test_ref_remote_limit() {
        let mut refs = BoundedVec::<_, REF_REMOTE_LIMIT>::new();
        let signer = Device::mock();
-
        let at = raw::Oid::zero().into();
+
        let at = git2::Oid::zero().into();

        assert_eq!(refs.capacity(), REF_REMOTE_LIMIT);

@@ -748,7 +748,7 @@ mod tests {
    fn prop_refs_announcement_signing(rid: RepoId) {
        let signer = Device::mock_rng(&mut fastrand::Rng::new());
        let timestamp = Timestamp::EPOCH;
-
        let at = raw::Oid::zero().into();
+
        let at = git2::Oid::zero().into();
        let refs = BoundedVec::collect_from(
            &mut [RefsAt {
                remote: *signer.public_key(),
modified crates/radicle-protocol/src/wire.rs
@@ -20,6 +20,7 @@ use cyphernet::addr::tor;
use radicle::crypto::{PublicKey, Signature, Unverified};
use radicle::git;
use radicle::git::fmt;
+
use radicle::git2;
use radicle::identity::RepoId;
use radicle::node;
use radicle::node::Alias;
@@ -285,7 +286,7 @@ where
    }
}

-
impl Encode for git::RefString {
+
impl Encode for git::fmt::RefString {
    fn encode(&self, buf: &mut impl BufMut) {
        self.as_str().encode(buf)
    }
@@ -300,7 +301,7 @@ impl Encode for Signature {
impl Encode for git::Oid {
    fn encode(&self, buf: &mut impl BufMut) {
        // Nb. We use length-encoding here to support future SHA-2 object ids.
-
        self.as_bytes().encode(buf)
+
        self.as_ref().encode(buf)
    }
}

@@ -321,7 +322,7 @@ impl Decode for Refs {

        for _ in 0..len {
            let name = String::decode(buf)?;
-
            let name = git::RefString::try_from(name).map_err(Invalid::from)?;
+
            let name = git::fmt::RefString::try_from(name).map_err(Invalid::from)?;
            let oid = git::Oid::decode(buf)?;

            refs.insert(name, oid);
@@ -330,10 +331,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> {
        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 +366,7 @@ where

impl Decode for git::Oid {
    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::<git2::Oid>();

        let len = Size::decode(buf)? as usize;

@@ -378,7 +379,7 @@ impl Decode for git::Oid {
        }

        let buf: [u8; LEN_EXPECTED] = Decode::decode(buf)?;
-
        let oid = git::raw::Oid::from_bytes(&buf).expect("the buffer is exactly the right size");
+
        let oid = git2::Oid::from_bytes(&buf).expect("the buffer is exactly the right size");
        let oid = git::Oid::from(oid);

        Ok(oid)
@@ -627,7 +628,7 @@ mod tests {

    #[quickcheck]
    fn prop_oid(input: [u8; 20]) {
-
        roundtrip(git::Oid::try_from(input.as_slice()).unwrap());
+
        roundtrip(git::Oid::from_sha1(input));
    }

    #[test]
modified crates/radicle-protocol/src/worker/fetch.rs
@@ -36,12 +36,12 @@ impl FetchResult {
/// corresponding targets.
#[derive(Clone, Default, Debug)]
pub struct UpdatedCanonicalRefs {
-
    inner: BTreeMap<git::Qualified<'static>, git::Oid>,
+
    inner: BTreeMap<git::fmt::Qualified<'static>, git::Oid>,
}

impl IntoIterator for UpdatedCanonicalRefs {
-
    type Item = (git::Qualified<'static>, git::Oid);
-
    type IntoIter = std::collections::btree_map::IntoIter<git::Qualified<'static>, git::Oid>;
+
    type Item = (git::fmt::Qualified<'static>, git::Oid);
+
    type IntoIter = std::collections::btree_map::IntoIter<git::fmt::Qualified<'static>, git::Oid>;

    fn into_iter(self) -> Self::IntoIter {
        self.inner.into_iter()
@@ -51,12 +51,12 @@ impl IntoIterator for UpdatedCanonicalRefs {
impl UpdatedCanonicalRefs {
    /// Insert a new updated entry for the canonical reference identified by
    /// `refname` and its new `target.`
-
    pub fn updated(&mut self, refname: git::Qualified<'static>, target: git::Oid) {
+
    pub fn updated(&mut self, refname: git::fmt::Qualified<'static>, target: git::Oid) {
        self.inner.insert(refname, target);
    }

    /// Return an iterator of all the updates.
-
    pub fn iter(&self) -> impl Iterator<Item = (&git::Qualified<'static>, &git::Oid)> {
+
    pub fn iter(&self) -> impl Iterator<Item = (&git::fmt::Qualified<'static>, &git::Oid)> {
        self.inner.iter()
    }
}
modified crates/radicle-protocol/src/worker/fetch/error.rs
@@ -2,7 +2,7 @@ use std::io;

use thiserror::Error;

-
use radicle::{cob, git, identity, storage};
+
use radicle::{cob, git2, identity, storage};
use radicle_fetch as fetch;

#[derive(Debug, Error)]
@@ -10,7 +10,7 @@ pub enum Fetch {
    #[error(transparent)]
    Run(#[from] fetch::Error),
    #[error(transparent)]
-
    Git(#[from] git::raw::Error),
+
    Git(#[from] git2::Error),
    #[error(transparent)]
    Storage(#[from] storage::Error),
    #[error(transparent)]
modified crates/radicle-remote-helper/Cargo.toml
@@ -19,5 +19,4 @@ log = { workspace = true }
radicle = { workspace = true }
radicle-cli = { workspace = true }
radicle-crypto = { workspace = true }
-
radicle-git-ext = { workspace = true }
thiserror = { workspace = true }

\ No newline at end of file
modified crates/radicle-remote-helper/src/fetch.rs
@@ -19,9 +19,8 @@ pub enum Error {
    /// Invalid reference name.
    #[error("invalid ref: {0}")]
    InvalidRef(#[from] radicle::git::fmt::Error),
-
    /// Git error.
-
    #[error("git: {0}")]
-
    InvalidOid(#[source] git::raw::Error),
+
    #[error("invalid oid")]
+
    InvalidOid,

    /// Error fetching pack from storage to working copy.
    #[error("`git fetch-pack` failed with exit status {status}, stderr and stdout follow:\n{stderr}\n{stdout}")]
@@ -34,7 +33,7 @@ pub enum Error {

/// Run a git fetch command.
pub fn run<R: ReadRepository>(
-
    mut refs: Vec<(git::Oid, git::RefString)>,
+
    mut refs: Vec<(git::Oid, git::fmt::RefString)>,
    stored: R,
    stdin: &io::Stdin,
    verbosity: Verbosity,
@@ -45,8 +44,8 @@ pub fn run<R: ReadRepository>(
        let tokens = read_line(stdin, &mut line)?;
        match tokens.as_slice() {
            ["fetch", oid, refstr] => {
-
                let oid = git::Oid::from_str(oid).map_err(Error::InvalidOid)?;
-
                let refstr = git::RefString::try_from(*refstr)?;
+
                let oid = git::Oid::from_str(oid).map_err(|_| Error::InvalidOid)?;
+
                let refstr = git::fmt::RefString::try_from(*refstr)?;

                refs.push((oid, refstr));
            }
modified crates/radicle-remote-helper/src/list.rs
@@ -19,7 +19,7 @@ pub enum Error {
    Identity(#[from] radicle::identity::DocError),
    /// Git error.
    #[error(transparent)]
-
    Git(#[from] radicle::git::ext::Error),
+
    Git(#[from] radicle::git2::Error),
    /// Profile error.
    #[error(transparent)]
    Profile(#[from] profile::Error),
@@ -56,8 +56,8 @@ pub fn for_fetch<R: ReadRepository + cob::Store<Namespace = NodeId> + 'static>(
        // List canonical references.
        // Skip over `refs/rad/*`, since those are not meant to be fetched into a working copy.
        for glob in [
-
            git::refspec::pattern!("refs/heads/*"),
-
            git::refspec::pattern!("refs/tags/*"),
+
            git::fmt::refspec::pattern!("refs/heads/*"),
+
            git::fmt::refspec::pattern!("refs/tags/*"),
        ] {
            for (name, oid) in stored.references_glob(&glob)? {
                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.
    for (name, oid) in stored.references_of(profile.id())? {
        // Only branches and tags can be pushed to.
-
        if name.starts_with(git::refname!("refs/heads").as_str())
-
            || name.starts_with(git::refname!("refs/tags").as_str())
+
        if name.starts_with(git::fmt::refname!("refs/heads").as_str())
+
            || name.starts_with(git::fmt::refname!("refs/tags").as_str())
        {
            println!("{oid} {name}");
        }
modified crates/radicle-remote-helper/src/main.rs
@@ -13,7 +13,7 @@
//! mentioned in the documentation on Git remote helpers.
//!
//! For example, the following two mechanisms rely on `GIT_DIR` being set:
-
//!  - [`git::raw::Repository::open_from_env`] to open the repository
+
//!  - [`git2::Repository::open_from_env`] to open the repository
//!  - [`radicle::git::run`] (with [`None`] as first argument) to invoke `git`

mod fetch;
@@ -27,6 +27,7 @@ use std::{env, fmt, io};

use thiserror::Error;

+
use radicle::git2;
use radicle::prelude::NodeId;
use radicle::storage::git::transport::local::{Url, UrlError};
use radicle::storage::{ReadRepository, WriteStorage};
@@ -77,7 +78,7 @@ fn main() {
pub enum Error {
    /// Failed to parse `base`.
    #[error("failed to parse base revision: {0}")]
-
    Base(#[source] git::raw::Error),
+
    Base(#[source] git2::Error),
    /// Base is not a commit.
    #[error("base must be of type 'commit' but it is of type '{actual_type}'")]
    BaseNotCommit { actual_type: String },
@@ -101,7 +102,7 @@ pub enum Error {
    Io(#[from] io::Error),
    /// Git error.
    #[error("git: {0}")]
-
    Git(#[from] git::raw::Error),
+
    Git(#[from] git2::Error),
    /// Invalid reference name.
    #[error("invalid ref: {0}")]
    InvalidRef(#[from] radicle::git::fmt::Error),
@@ -117,6 +118,8 @@ pub enum Error {
    /// List error.
    #[error(transparent)]
    List(#[from] list::Error),
+
    #[error("invalid oid")]
+
    InvalidOid,
}

/// 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>`).
    MirrorUpstream,
    /// Create a branch with the provided name.
-
    Provided(git::RefString),
+
    Provided(git::fmt::RefString),
}

impl Branch {
    /// Return the branch name to be used for the local branch when creating a
    /// patch.
-
    pub fn to_branch_name(self, object: &radicle::patch::PatchId) -> Option<git::Qualified> {
+
    pub fn to_branch_name(self, object: &radicle::patch::PatchId) -> Option<git::fmt::Qualified> {
        match self {
            Self::None => None,
            Self::MirrorUpstream => Some(git::refs::patch(object)),
@@ -207,12 +210,15 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
    // module is aware of that.
    cli::Paint::set_terminal(cli::TerminalFile::Stderr);

-
    let (remote, url): (Option<git::RefString>, Url) = {
+
    let (remote, url): (Option<git::fmt::RefString>, Url) = {
        let args = env::args().skip(1).take(2).collect::<Vec<_>>();

        match args.as_slice() {
            [url] => (None, url.parse()?),
-
            [remote, url] => (git::RefString::try_from(remote.as_str()).ok(), url.parse()?),
+
            [remote, url] => (
+
                git::fmt::RefString::try_from(remote.as_str()).ok(),
+
                url.parse()?,
+
            ),

            _ => {
                return Err(Error::InvalidArguments(args));
@@ -273,8 +279,8 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
                println!("unsupported");
            }
            ["fetch", oid, refstr] => {
-
                let oid = git::Oid::from_str(oid)?;
-
                let refstr = git::RefString::try_from(*refstr)?;
+
                let oid = git::Oid::from_str(oid).map_err(|_| Error::InvalidOid)?;
+
                let refstr = git::fmt::RefString::try_from(*refstr)?;

                return Ok(fetch::run(
                    vec![(oid, refstr)],
@@ -333,7 +339,7 @@ fn push_option(args: &[&str], opts: &mut Options) -> Result<(), Error> {
                    opts.message.append(val);
                }
                "patch.base" => {
-
                    let repo = git::raw::Repository::open_from_env().map_err(Error::Base)?;
+
                    let repo = git2::Repository::open_from_env().map_err(Error::Base)?;
                    let commit = repo
                        .revparse_single(val)
                        .map_err(Error::Base)?
@@ -347,7 +353,9 @@ fn push_option(args: &[&str], opts: &mut Options) -> Result<(), Error> {

                    opts.base = Some(git::Oid::from(commit.id()));
                }
-
                "patch.branch" => opts.branch = Branch::Provided(git::RefString::try_from(val)?),
+
                "patch.branch" => {
+
                    opts.branch = Branch::Provided(git::fmt::RefString::try_from(val)?)
+
                }
                other => {
                    return Err(Error::UnsupportedPushOption(other.to_owned()));
                }
modified crates/radicle-remote-helper/src/push.rs
@@ -20,6 +20,7 @@ use radicle::cob::patch;
use radicle::cob::patch::cache::Patches as _;
use radicle::crypto;
use radicle::explorer::ExplorerResource;
+
use radicle::git2;
use radicle::identity::{CanonicalRefs, Did};
use radicle::node;
use radicle::node::{Handle, NodeId};
@@ -43,7 +44,7 @@ pub enum Error {
    NoKey,
    /// User tried to delete the canonical branch.
    #[error("refusing to delete default branch ref '{0}'")]
-
    DeleteForbidden(git::RefString),
+
    DeleteForbidden(git::fmt::RefString),
    /// Identity document error.
    #[error("doc: {0}")]
    Doc(#[from] radicle::identity::doc::DocError),
@@ -64,10 +65,7 @@ pub enum Error {
    InvalidRef(#[from] radicle::git::fmt::Error),
    /// Git error.
    #[error("git: {0}")]
-
    Git(#[from] git::raw::Error),
-
    /// Git extension error.
-
    #[error("git: {0}")]
-
    GitExt(#[from] git::ext::Error),
+
    Git(#[from] git2::Error),
    /// Storage error.
    #[error(transparent)]
    Storage(#[from] radicle::storage::Error),
@@ -136,9 +134,9 @@ pub enum Error {
/// Push command.
enum Command {
    /// Update ref.
-
    Push(git::Refspec<git::Oid, git::RefString>),
+
    Push(git::fmt::refspec::Refspec<git::Oid, git::fmt::RefString>),
    /// Delete ref.
-
    Delete(git::RefString),
+
    Delete(git::fmt::RefString),
}

#[derive(Debug, thiserror::Error)]
@@ -152,10 +150,7 @@ enum CommandError {
        err: git::fmt::Error,
    },
    #[error("failed to parse source revision ({rev}): {source}")]
-
    Revision {
-
        rev: String,
-
        source: git::raw::Error,
-
    },
+
    Revision { rev: String, source: git2::Error },
}

impl Command {
@@ -169,11 +164,11 @@ impl Command {
    /// being updating with the `src` value.
    ///
    /// [revision]: https://git-scm.com/docs/revisions
-
    fn parse(s: &str, repo: &git::raw::Repository) -> Result<Self, CommandError> {
+
    fn parse(s: &str, repo: &git2::Repository) -> Result<Self, CommandError> {
        let Some((src, dst)) = s.split_once(':') else {
            return Err(CommandError::Empty { rev: s.to_string() });
        };
-
        let dst = git::RefString::try_from(dst).map_err(|err| CommandError::Delete {
+
        let dst = git::fmt::RefString::try_from(dst).map_err(|err| CommandError::Delete {
            rev: dst.to_string(),
            err,
        })?;
@@ -195,12 +190,12 @@ impl Command {
                .id()
                .into();

-
            Ok(Self::Push(git::Refspec { src, dst, force }))
+
            Ok(Self::Push(git::fmt::refspec::Refspec { src, dst, force }))
        }
    }

    /// Return the destination refname.
-
    fn dst(&self) -> &git::RefStr {
+
    fn dst(&self) -> &git::fmt::RefStr {
        match self {
            Self::Push(rs) => rs.dst.as_refstr(),
            Self::Delete(rs) => rs,
@@ -211,30 +206,29 @@ impl Command {
enum PushAction {
    OpenPatch,
    UpdatePatch {
-
        dst: git::Qualified<'static>,
+
        dst: git::fmt::Qualified<'static>,
        patch: patch::PatchId,
    },
    PushRef {
-
        dst: git::Qualified<'static>,
+
        dst: git::fmt::Qualified<'static>,
    },
}

impl PushAction {
-
    fn new(dst: &git::RefString) -> Result<Self, error::PushAction> {
+
    fn new(dst: &git::fmt::RefString) -> Result<Self, error::PushAction> {
        if dst == &*rad::PATCHES_REFNAME {
            Ok(Self::OpenPatch)
        } else {
-
            let dst = git::Qualified::from_refstr(dst)
+
            let dst = git::fmt::Qualified::from_refstr(dst)
                .ok_or_else(|| error::PushAction::InvalidRef {
                    refname: dst.clone(),
                })?
                .to_owned();

-
            if let Some(oid) = dst.strip_prefix(git::refname!("refs/heads/patches")) {
+
            if let Some(oid) = dst.strip_prefix(git::fmt::refname!("refs/heads/patches")) {
                let patch = git::Oid::from_str(oid)
-
                    .map_err(|err| error::PushAction::InvalidPatchId {
+
                    .map_err(|_| error::PushAction::InvalidPatchId {
                        suffix: oid.to_string(),
-
                        source: err,
                    })
                    .map(patch::PatchId::from)?;
                Ok(Self::UpdatePatch { dst, patch })
@@ -248,7 +242,7 @@ impl PushAction {
/// Run a git push command.
pub fn run(
    mut specs: Vec<String>,
-
    remote: Option<git::RefString>,
+
    remote: Option<git::fmt::RefString>,
    url: Url,
    stored: &storage::git::Repository,
    profile: &Profile,
@@ -290,11 +284,11 @@ pub fn run(
    let identity = stored.identity()?;
    let project = identity.project()?;
    let canonical_ref = git::refs::branch(project.default_branch());
-
    let mut set_canonical_refs: Vec<(git::Qualified, git::canonical::Object)> =
+
    let mut set_canonical_refs: Vec<(git::fmt::Qualified, git::canonical::Object)> =
        Vec::with_capacity(specs.len());

    // Rely on the environment variable `GIT_DIR`.
-
    let working = git::raw::Repository::open_from_env()?;
+
    let working = git2::Repository::open_from_env()?;

    // For each refspec, push a ref or delete a ref.
    for spec in specs {
@@ -317,7 +311,7 @@ pub fn run(
                    .map(|_| None)
                    .map_err(Error::from)
            }
-
            Command::Push(git::Refspec { src, dst, force }) => {
+
            Command::Push(git::fmt::refspec::Refspec { src, dst, force }) => {
                let patches = crate::patches_mut(profile, stored)?;
                let action = PushAction::new(dst)?;

@@ -373,7 +367,7 @@ pub fn run(
                        // canonical branch.
                        if let Some(canonical) = rules.canonical(dst.clone(), stored) {
                            let object = working
-
                                .find_object(**src, None)
+
                                .find_object(src.into(), None)
                                .map(|obj| git::canonical::Object::new(&obj))?
                                .ok_or(Error::UnknownObjectType { oid: *src })?;

@@ -429,19 +423,19 @@ pub fn run(
            }

            match stored.backend.refname_to_id(refname.as_str()) {
-
                Ok(new) if new != *oid => {
+
                Ok(new) if oid != new => {
                    stored.backend.reference(
                        refname.as_str(),
-
                        *oid,
+
                        oid.into(),
                        true,
                        "set-canonical-reference from git-push (radicle)",
                    )?;
                    print_update();
                }
-
                Err(e) if e.code() == git::raw::ErrorCode::NotFound => {
+
                Err(e) if e.code() == git2::ErrorCode::NotFound => {
                    stored.backend.reference(
                        refname.as_str(),
-
                        *oid,
+
                        oid.into(),
                        true,
                        "set-canonical-reference from git-push (radicle)",
                    )?;
@@ -505,7 +499,7 @@ fn patch_base(
/// [`Drop::drop`].
struct TempPatchRef<'a> {
    stored: &'a storage::git::Repository,
-
    reference: git::Namespaced<'a>,
+
    reference: git::fmt::Namespaced<'a>,
}

impl<'a> TempPatchRef<'a> {
@@ -539,9 +533,9 @@ impl<'a> Drop for TempPatchRef<'a> {
/// Open a new patch.
fn patch_open<G>(
    head: &git::Oid,
-
    upstream: &Option<git::RefString>,
+
    upstream: &Option<git::fmt::RefString>,
    nid: &NodeId,
-
    working: &git::raw::Repository,
+
    working: &git2::Repository,
    stored: &storage::git::Repository,
    mut patches: patch::Cache<
        patch::Patches<'_, storage::git::Repository>,
@@ -607,24 +601,29 @@ where
    let refname = git::refs::patch(&patch).with_namespace(nid.into());
    let _ = stored.raw().reference(
        refname.as_str(),
-
        **head,
+
        head.into(),
        true,
        "Create reference for patch head",
    )?;

    if let Some(upstream) = upstream {
        if let Some(local_branch) = opts.branch.to_branch_name(&patch) {
-
            fn strip_refs_heads(qualified: git::Qualified) -> git::RefString {
+
            fn strip_refs_heads(qualified: git::fmt::Qualified) -> git::fmt::RefString {
                let (_refs, _heads, x, xs) = qualified.non_empty_components();
                std::iter::once(x).chain(xs).collect()
            }

-
            working.reference(&local_branch, **head, true, "Create local branch for patch")?;
+
            working.reference(
+
                &local_branch,
+
                head.into(),
+
                true,
+
                "Create local branch for patch",
+
            )?;

            let remote_branch = git::refs::workdir::patch_upstream(&patch);
            let remote_branch = working.reference(
                &remote_branch,
-
                **head,
+
                head.into(),
                true,
                "Create remote tracking branch for patch",
            )?;
@@ -667,11 +666,11 @@ where
#[allow(clippy::too_many_arguments)]
fn patch_update<G>(
    head: &git::Oid,
-
    dst: &git::Qualified,
+
    dst: &git::fmt::Qualified,
    force: bool,
    patch_id: patch::PatchId,
    nid: &NodeId,
-
    working: &git::raw::Repository,
+
    working: &git2::Repository,
    stored: &storage::git::Repository,
    mut patches: patch::Cache<
        patch::Patches<'_, storage::git::Repository>,
@@ -744,10 +743,10 @@ where

fn push<G>(
    src: &git::Oid,
-
    dst: &git::Qualified,
+
    dst: &git::fmt::Qualified,
    force: bool,
    nid: &NodeId,
-
    working: &git::raw::Repository,
+
    working: &git2::Repository,
    stored: &storage::git::Repository,
    mut patches: patch::Cache<
        patch::Patches<'_, storage::git::Repository>,
@@ -768,7 +767,7 @@ where

    if let Some(old) = old {
        let proj = stored.project()?;
-
        let master = &*git::Qualified::from(git::lit::refs_heads(proj.default_branch()));
+
        let master = &*git::fmt::Qualified::from(git::fmt::lit::refs_heads(proj.default_branch()));

        // If we're pushing to the project's default branch, we want to see if any patches got
        // merged or reverted, and if so, update the patch COB.
@@ -788,7 +787,7 @@ where
fn patch_revert_all<G>(
    old: git::Oid,
    new: git::Oid,
-
    stored: &git::raw::Repository,
+
    stored: &git2::Repository,
    patches: &mut patch::Cache<
        patch::Patches<'_, storage::git::Repository>,
        cob::cache::StoreWriter,
@@ -800,8 +799,8 @@ where
{
    // Find all commits reachable from the old OID but not from the new OID.
    let mut revwalk = stored.revwalk()?;
-
    revwalk.push(*old)?;
-
    revwalk.hide(*new)?;
+
    revwalk.push(old.into())?;
+
    revwalk.hide(new.into())?;

    // List of commits that have been dropped.
    let dropped = revwalk
@@ -853,7 +852,7 @@ where
fn patch_merge_all<G>(
    old: git::Oid,
    new: git::Oid,
-
    working: &git::raw::Repository,
+
    working: &git2::Repository,
    patches: &mut patch::Cache<
        patch::Patches<'_, storage::git::Repository>,
        cob::cache::StoreWriter,
@@ -907,7 +906,7 @@ fn patch_merge<C, G>(
    mut patch: patch::PatchMut<storage::git::Repository, C>,
    revision: patch::RevisionId,
    commit: git::Oid,
-
    working: &git::raw::Repository,
+
    working: &git2::Repository,
    signer: &Device<G>,
) -> Result<(), Error>
where
@@ -943,15 +942,15 @@ where
/// Push a single reference to storage.
fn push_ref(
    src: &git::Oid,
-
    dst: &git::Namespaced,
+
    dst: &git::fmt::Namespaced,
    force: bool,
-
    stored: &git::raw::Repository,
+
    stored: &git2::Repository,
    verbosity: Verbosity,
) -> Result<(), Error> {
    let path = dunce::canonicalize(stored.path())?.display().to_string();
    // Nb. The *force* indicator (`+`) is processed by Git tooling before we even reach this code.
    // This happens during the `list for-push` phase.
-
    let refspec = git::Refspec { src, dst, force };
+
    let refspec = git::fmt::refspec::Refspec { src, dst, force };

    let mut args = vec!["send-pack".to_string()];

modified crates/radicle-remote-helper/src/push/canonical.rs
@@ -40,7 +40,7 @@ where
    /// copy, and that checks that any two commits are related in the graph.
    ///
    /// Ensures that the new head and the canonical commit do not diverge.
-
    pub fn quorum(self) -> Result<(git::Qualified<'a>, canonical::Object), QuorumError> {
+
    pub fn quorum(self) -> Result<(git::fmt::Qualified<'a>, canonical::Object), QuorumError> {
        self.canonical
            .quorum()
            .map(|QuorumWithConvergence { quorum, .. }| (quorum.refname, quorum.object))
modified crates/radicle-remote-helper/src/push/error.rs
@@ -1,5 +1,6 @@
use radicle::git;
use radicle::git::canonical;
+
use radicle::git2;
use thiserror::Error;

#[derive(Debug, Error)]
@@ -15,7 +16,7 @@ pub enum CanonicalUnrecoverable {
    #[error(transparent)]
    HeadsDiverge(#[from] HeadsDiverge),
    #[error("failure while computing canonical reference: {source}")]
-
    Git { source: git::raw::Error },
+
    Git { source: git2::Error },
}

#[derive(Debug, Error)]
@@ -23,7 +24,7 @@ pub enum CanonicalUnrecoverable {
pub struct GraphDescendant {
    head: git::Oid,
    canonical: git::Oid,
-
    source: git::raw::Error,
+
    source: git2::Error,
}

#[derive(Debug, Error)]
@@ -37,10 +38,7 @@ pub struct HeadsDiverge {
#[derive(Debug, Error)]
pub enum PushAction {
    #[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")]
-
    InvalidPatchId {
-
        suffix: String,
-
        source: git::raw::Error,
-
    },
+
    InvalidPatchId { suffix: String },
}
modified crates/radicle/Cargo.toml
@@ -13,6 +13,8 @@ rust-version.workspace = true
default = []
test = ["qcheck", "radicle-crypto/test", "radicle-cob/test"]
logger = ["colored", "chrono"]
+
schemars = ["radicle-oid/schemars", "dep:schemars"]
+
radicle-git-ext = ["radicle-oid/radicle-git-ext"]

[dependencies]
amplify = { workspace = true, features = ["std"] }
@@ -33,8 +35,9 @@ multibase = { workspace = true }
nonempty = { workspace = true, features = ["serialize"] }
qcheck = { workspace = true, optional = true }
radicle-cob = { workspace = true, features = ["git2"] }
-
radicle-crypto = { workspace = true, features = ["radicle-git-ext", "ssh", "sqlite", "cyphernet"] }
-
radicle-git-ext = { workspace = true, features = ["serde"] }
+
radicle-crypto = { workspace = true, features = ["radicle-git-ref-format", "ssh", "sqlite", "cyphernet"] }
+
radicle-git-ref-format = { workspace = true }
+
radicle-oid = { workspace = true, features = ["git2", "serde", "std" ] }
radicle-ssh = { workspace = true }
schemars = { workspace = true, optional = true, features = ["derive", "std"] }
serde = { workspace = true, features = ["derive"] }
@@ -60,3 +63,4 @@ qcheck = { workspace = true }
qcheck-macros = { workspace = true }
radicle-cob = { workspace = true, features = ["stable-commit-ids", "test"] }
radicle-crypto = { workspace = true, features = ["test"] }
+
radicle-git2-metadata = { workspace = true, features = ["serde"] }

\ No newline at end of file
modified crates/radicle/src/cob.rs
@@ -13,6 +13,9 @@ pub mod thread;
#[cfg(test)]
pub mod test;

+
#[cfg(test)]
+
pub use radicle_cob::stable;
+

pub use cache::{migrate, MigrateCallback};
pub use common::*;
pub use op::{ActorId, Op};
@@ -21,7 +24,7 @@ pub use radicle_cob::{
    CollaborativeObject, Contents, Create, Embed, Entry, Evaluate, History, Manifest, ObjectId,
    Store, TypeName, Update, Updated, Version,
};
-
pub use radicle_cob::{create, get, git, list, remove, update};
+
pub use radicle_cob::{create, get, list, remove, update};

/// The exact identifier for a particular COB.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
@@ -66,17 +69,17 @@ impl TypedId {
        self.type_name == *identity::TYPENAME
    }

-
    /// Parse a [`crate::git::Namespaced`] refname into a [`TypedId`].
+
    /// Parse a [`crate::git::fmt::Namespaced`] refname into a [`TypedId`].
    ///
    /// All namespaces are stripped before parsing the suffix for the
    /// [`TypedId`] (see [`TypedId::from_qualified`]).
    pub fn from_namespaced(
-
        n: &crate::git::Namespaced,
+
        n: &crate::git::fmt::Namespaced,
    ) -> Result<Option<Self>, ParseIdentifierError> {
        Self::from_qualified(&n.strip_namespace_recursive())
    }

-
    /// Parse a [`crate::git::Qualified`] refname into a [`TypedId`].
+
    /// Parse a [`crate::git::fmt::Qualified`] refname into a [`TypedId`].
    ///
    /// The refname is expected to be of the form:
    ///     `refs/cobs/<type name>/<object id>`
@@ -87,7 +90,9 @@ impl TypedId {
    ///
    /// This will fail if the refname is of the correct form, but the
    /// type name or object id fail to parse.
-
    pub fn from_qualified(q: &crate::git::Qualified) -> Result<Option<Self>, ParseIdentifierError> {
+
    pub fn from_qualified(
+
        q: &crate::git::fmt::Qualified,
+
    ) -> Result<Option<Self>, ParseIdentifierError> {
        match q.non_empty_iter() {
            ("refs", "cobs", type_name, mut id) => {
                let Some(id) = id.next() else {
modified crates/radicle/src/cob/common.rs
@@ -9,8 +9,8 @@ use localtime::LocalTime;
use serde::{Deserialize, Serialize};
use thiserror::Error;

-
use crate::git::Oid;
use crate::prelude::{Did, PublicKey};
+
use crate::Oid;

/// Timestamp used for COB operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
@@ -355,7 +355,7 @@ impl From<Oid> for Uri {
    }
}

-
impl TryFrom<&Uri> for Oid {
+
impl TryFrom<&Uri> for crate::git2::Oid {
    type Error = Uri;

    fn try_from(value: &Uri) -> Result<Self, Self::Error> {
@@ -368,6 +368,14 @@ impl TryFrom<&Uri> for Oid {
    }
}

+
impl TryFrom<&Uri> for crate::Oid {
+
    type Error = Uri;
+

+
    fn try_from(value: &Uri) -> Result<Self, Self::Error> {
+
        crate::git2::Oid::try_from(value).map(crate::Oid::from)
+
    }
+
}
+

impl std::fmt::Display for Uri {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
modified crates/radicle/src/cob/external.rs
@@ -92,8 +92,8 @@ use serde_json::{from_slice, to_writer, Error as JsonError, Map, Value};
use crate::cob::object::collaboration::Evaluate;
use crate::cob::op::{Op as CobOp, OpEncodingError};
use crate::cob::store::{Cob, CobAction};
-
use crate::git::Oid;
use crate::storage::ReadRepository;
+
use crate::Oid;

/// This prefix is used to generate the name of the command,
/// which is executed by the helper to apply operations.
@@ -225,7 +225,7 @@ impl<R: ReadRepository> Evaluate<R> for External {
        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::Oid, &'a radicle_cob::Entry)>>(
        &mut self,
        entry: &radicle_cob::Entry,
        concurrent: I,
modified crates/radicle/src/cob/identity.rs
@@ -4,8 +4,6 @@ use std::{fmt, ops::Deref, str::FromStr};

use crypto::{PublicKey, Signature};
use radicle_cob::{Embed, ObjectId, TypeName};
-
use radicle_git_ext as git_ext;
-
use radicle_git_ext::Oid;
use serde::{Deserialize, Serialize};
use thiserror::Error;

@@ -13,6 +11,7 @@ use crate::identity::doc::Doc;
use crate::node::device::Device;
use crate::node::NodeId;
use crate::storage;
+
use crate::Oid;
use crate::{
    cob,
    cob::{
@@ -126,9 +125,7 @@ pub enum ApplyError {
    #[error("document does not contain any changes to current identity")]
    DocUnchanged,
    #[error("git: {0}")]
-
    Git(#[from] git2::Error),
-
    #[error("git: {0}")]
-
    GitExt(#[from] git_ext::Error),
+
    Git(#[from] crate::git2::Error),
    #[error("identity document error: {0}")]
    Doc(#[from] DocError),
}
@@ -1377,15 +1374,14 @@ mod test {
        eve.repo.fetch(bob);

        let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
-
        let b1 = cob::git::stable::with_advanced_timestamp(|| {
-
            bob_identity.accept(&a2, &bob.signer).unwrap()
-
        });
+
        let b1 =
+
            cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap());
        assert_eq!(bob_identity.current, a2);

        let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap();
        let mut eve_doc = eve_identity.doc().clone().edit();
        eve_doc.visibility = Visibility::private([eve.signer.public_key().into()]);
-
        let e1 = cob::git::stable::with_advanced_timestamp(|| {
+
        let e1 = cob::stable::with_advanced_timestamp(|| {
            eve_identity
                .update(
                    cob::Title::new("Change visibility").unwrap(),
@@ -1568,7 +1564,7 @@ mod test {
        assert_eq!(eve_identity.revision(&e1).unwrap().state, State::Active);

        alice_identity.reload().unwrap();
-
        let a2 = cob::git::stable::with_advanced_timestamp(|| {
+
        let a2 = cob::stable::with_advanced_timestamp(|| {
            alice_identity.accept(&b1, &alice.signer).unwrap()
        });

modified crates/radicle/src/cob/issue.rs
@@ -982,9 +982,9 @@ mod test {

    use super::*;
    use crate::cob::{store::CobWithType, ActorId, Reaction};
-
    use crate::git::Oid;
    use crate::issue::cache::Issues as _;
    use crate::test::arbitrary;
+
    use crate::Oid;
    use crate::{assert_matches, test};

    #[test]
modified crates/radicle/src/cob/op.rs
@@ -8,9 +8,9 @@ use radicle_crypto::PublicKey;

use crate::cob;
use crate::cob::Timestamp;
+
use crate::identity;
use crate::identity::DocAt;
use crate::storage::ReadRepository;
-
use crate::{git, identity};

/// The author of an [`Op`].
pub type ActorId = PublicKey;
@@ -21,13 +21,13 @@ pub enum OpEncodingError {
    #[error("encoding failed: {0}")]
    Encoding(#[from] serde_json::Error),
    #[error("git: {0}")]
-
    Git(#[from] git2::Error),
+
    Git(#[from] crate::git2::Error),
}

#[derive(Error, Debug)]
#[error("failed to load manifest of '{object}': {err}")]
pub struct ManifestError {
-
    object: git::Oid,
+
    object: crate::Oid,
    #[source]
    err: Box<dyn std::error::Error + Send + Sync + 'static>,
}
@@ -37,12 +37,12 @@ pub struct ManifestError {
pub enum LoadError {
    #[error("failed to load Op at '{object}': {source}")]
    Load {
-
        object: git::Oid,
+
        object: crate::Oid,
        source: Box<dyn std::error::Error + Send + Sync + 'static>,
    },
    #[error("failed to decode Op at '{object}': {source}")]
    Encoding {
-
        object: git::Oid,
+
        object: crate::Oid,
        source: OpEncodingError,
    },
}
@@ -64,9 +64,9 @@ pub struct Op<A> {
    /// Parent operations.
    pub parents: Vec<EntryId>,
    /// Related objects.
-
    pub related: Vec<git::Oid>,
+
    pub related: Vec<crate::Oid>,
    /// Head of identity document committed to by this operation.
-
    pub identity: Option<git::Oid>,
+
    pub identity: Option<crate::Oid>,
    /// Object manifest.
    pub manifest: Manifest,
}
@@ -89,7 +89,7 @@ impl<A> Op<A> {
        actions: impl Into<NonEmpty<A>>,
        author: ActorId,
        timestamp: impl Into<Timestamp>,
-
        identity: Option<git::Oid>,
+
        identity: Option<crate::Oid>,
        manifest: Manifest,
    ) -> Self {
        Self {
@@ -118,11 +118,11 @@ impl<A> Op<A> {
        }
    }

-
    pub fn manifest_of<S>(store: &S, id: &git::Oid) -> Result<Manifest, ManifestError>
+
    pub fn manifest_of<S>(store: &S, id: &crate::Oid) -> Result<Manifest, ManifestError>
    where
        S: cob::change::Storage<
-
            ObjectId = git::Oid,
-
            Parent = git::Oid,
+
            ObjectId = crate::Oid,
+
            Parent = crate::Oid,
            Signatures = crypto::ssh::ExtendedSignature,
        >,
    {
@@ -133,11 +133,11 @@ impl<A> Op<A> {
    }

    /// Get the `Op` identified by the `id` in the provided `store`.
-
    pub fn load<S>(store: &S, id: git::Oid) -> Result<Self, LoadError>
+
    pub fn load<S>(store: &S, id: crate::Oid) -> Result<Self, LoadError>
    where
        S: cob::change::Storage<
-
            ObjectId = git::Oid,
-
            Parent = git::Oid,
+
            ObjectId = crate::Oid,
+
            Parent = crate::Oid,
            Signatures = crypto::ssh::ExtendedSignature,
        >,
        for<'de> A: serde::Deserialize<'de>,
modified crates/radicle/src/cob/patch.rs
@@ -130,7 +130,7 @@ pub enum Error {
    Payload(#[from] PayloadError),
    /// Git error.
    #[error("git: {0}")]
-
    Git(#[from] git::ext::Error),
+
    Git(#[from] crate::git2::Error),
    /// Store error.
    #[error("store: {0}")]
    Store(#[from] store::Error),
@@ -188,7 +188,7 @@ pub enum Action {
    #[serde(rename = "merge")]
    Merge {
        revision: RevisionId,
-
        commit: git::Oid,
+
        commit: crate::Oid,
    },

    //
@@ -255,8 +255,8 @@ pub enum Action {
    #[serde(rename = "revision")]
    Revision {
        description: String,
-
        base: git::Oid,
-
        oid: git::Oid,
+
        base: crate::Oid,
+
        oid: crate::Oid,
        /// Review comments resolved by this revision.
        #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
        resolves: BTreeSet<(EntryId, CommentId)>,
@@ -330,7 +330,7 @@ pub enum Action {
}

impl CobAction for Action {
-
    fn parents(&self) -> Vec<git::Oid> {
+
    fn parents(&self) -> Vec<crate::Oid> {
        match self {
            Self::Revision { base, oid, .. } => {
                vec![*base, *oid]
@@ -370,7 +370,7 @@ impl<R: WriteRepository> Merged<'_, R> {
    /// and the stored copy; and updates `rad/sigrefs`.
    pub fn cleanup<G>(
        self,
-
        working: &git::raw::Repository,
+
        working: &crate::git2::Repository,
        signer: &Device<G>,
    ) -> Result<(), storage::RepositoryError>
    where
@@ -410,7 +410,7 @@ pub enum MergeTarget {

impl MergeTarget {
    /// Get the head of the target branch.
-
    pub fn head<R: ReadRepository>(&self, repo: &R) -> Result<git::Oid, RepositoryError> {
+
    pub fn head<R: ReadRepository>(&self, repo: &R) -> Result<crate::Oid, RepositoryError> {
        match self {
            MergeTarget::Delegates => {
                let (_, target) = repo.head()?;
@@ -590,23 +590,26 @@ impl Patch {
    }

    /// Reference to the Git object containing the code on the latest revision.
-
    pub fn head(&self) -> &git::Oid {
+
    pub fn head(&self) -> &crate::Oid {
        &self.latest().1.oid
    }

    /// Get the commit of the target branch on which this patch is based.
    /// This can change via a patch update.
-
    pub fn base(&self) -> &git::Oid {
+
    pub fn base(&self) -> &crate::Oid {
        &self.latest().1.base
    }

    /// 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::Oid, crate::git2::Error> {
        repo.merge_base(self.base(), self.head())
    }

    /// Get the commit range of this patch.
-
    pub fn range(&self) -> Result<(git::Oid, git::Oid), git::ext::Error> {
+
    pub fn range(&self) -> Result<(crate::Oid, crate::Oid), crate::git2::Error> {
        Ok((*self.base(), *self.head()))
    }

@@ -1108,7 +1111,7 @@ impl Patch {
                );

                let mut merges = self.merges.iter().fold(
-
                    HashMap::<(RevisionId, git::Oid), usize>::new(),
+
                    HashMap::<(RevisionId, crate::Oid), usize>::new(),
                    |mut acc, (_, merge)| {
                        *acc.entry((merge.revision, merge.commit)).or_default() += 1;
                        acc
@@ -1415,9 +1418,9 @@ pub struct Revision {
    /// Revision description.
    pub(super) description: NonEmpty<Edit>,
    /// Base branch commit, used as a merge base.
-
    pub(super) base: git::Oid,
+
    pub(super) base: crate::Oid,
    /// Reference to the Git object containing the code (revision head).
-
    pub(super) oid: git::Oid,
+
    pub(super) oid: crate::Oid,
    /// Discussion around this revision.
    pub(super) discussion: Thread<Comment<CodeLocation>>,
    /// Reviews of this revision's changes (all review edits are kept).
@@ -1439,8 +1442,8 @@ impl Revision {
        id: RevisionId,
        author: Author,
        description: String,
-
        base: git::Oid,
-
        oid: git::Oid,
+
        base: crate::Oid,
+
        oid: crate::Oid,
        timestamp: Timestamp,
        resolves: BTreeSet<(EntryId, CommentId)>,
    ) -> Self {
@@ -1486,17 +1489,17 @@ impl Revision {
    }

    /// Base branch commit, used as a merge base.
-
    pub fn base(&self) -> &git::Oid {
+
    pub fn base(&self) -> &crate::Oid {
        &self.base
    }

    /// Reference to the Git object containing the code (revision head).
-
    pub fn head(&self) -> git::Oid {
+
    pub fn head(&self) -> crate::Oid {
        self.oid
    }

    /// Get the commit range of this revision.
-
    pub fn range(&self) -> (git::Oid, git::Oid) {
+
    pub fn range(&self) -> (crate::Oid, crate::Oid) {
        (self.base, self.oid)
    }

@@ -1540,14 +1543,14 @@ pub enum State {
        /// Revisions that were merged and are conflicting.
        #[serde(skip_serializing_if = "Vec::is_empty")]
        #[serde(default)]
-
        conflicts: Vec<(RevisionId, git::Oid)>,
+
        conflicts: Vec<(RevisionId, crate::Oid)>,
    },
    Archived,
    Merged {
        /// The revision that was merged.
        revision: RevisionId,
        /// The commit in the target branch that contains the changes.
-
        commit: git::Oid,
+
        commit: crate::Oid,
    },
}

@@ -1618,7 +1621,7 @@ pub struct Merge {
    /// Revision that was merged.
    pub revision: RevisionId,
    /// Base branch commit that contains the revision.
-
    pub commit: git::Oid,
+
    pub commit: crate::Oid,
    /// When this merge was performed.
    pub timestamp: Timestamp,
}
@@ -1998,7 +2001,7 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
    }

    /// Merge a patch revision.
-
    pub fn merge(&mut self, revision: RevisionId, commit: git::Oid) -> Result<(), store::Error> {
+
    pub fn merge(&mut self, revision: RevisionId, commit: crate::Oid) -> Result<(), store::Error> {
        self.push(Action::Merge { revision, commit })
    }

@@ -2006,8 +2009,8 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
    pub fn revision(
        &mut self,
        description: impl ToString,
-
        base: impl Into<git::Oid>,
-
        oid: impl Into<git::Oid>,
+
        base: impl Into<crate::Oid>,
+
        oid: impl Into<crate::Oid>,
    ) -> Result<(), store::Error> {
        self.push(Action::Revision {
            description: description.to_string(),
@@ -2417,7 +2420,7 @@ where
    pub fn merge<G>(
        &mut self,
        revision: RevisionId,
-
        commit: git::Oid,
+
        commit: crate::Oid,
        signer: &Device<G>,
    ) -> Result<Merged<R>, Error>
    where
@@ -2437,8 +2440,8 @@ where
    pub fn update<G>(
        &mut self,
        description: impl ToString,
-
        base: impl Into<git::Oid>,
-
        oid: impl Into<git::Oid>,
+
        base: impl Into<crate::Oid>,
+
        oid: impl Into<crate::Oid>,
        signer: &Device<G>,
    ) -> Result<RevisionId, Error>
    where
@@ -2685,8 +2688,8 @@ where
        title: cob::Title,
        description: impl ToString,
        target: MergeTarget,
-
        base: impl Into<git::Oid>,
-
        oid: impl Into<git::Oid>,
+
        base: impl Into<crate::Oid>,
+
        oid: impl Into<crate::Oid>,
        labels: &[Label],
        cache: &'g mut C,
        signer: &Device<G>,
@@ -2714,8 +2717,8 @@ where
        title: cob::Title,
        description: impl ToString,
        target: MergeTarget,
-
        base: impl Into<git::Oid>,
-
        oid: impl Into<git::Oid>,
+
        base: impl Into<crate::Oid>,
+
        oid: impl Into<crate::Oid>,
        labels: &[Label],
        cache: &'g mut C,
        signer: &Device<G>,
@@ -2762,8 +2765,8 @@ where
        title: cob::Title,
        description: impl ToString,
        target: MergeTarget,
-
        base: impl Into<git::Oid>,
-
        oid: impl Into<git::Oid>,
+
        base: impl Into<crate::Oid>,
+
        oid: impl Into<crate::Oid>,
        labels: &[Label],
        state: Lifecycle,
        cache: &'g mut C,
@@ -2804,8 +2807,8 @@ where
/// See <https://git-scm.com/docs/git-range-diff>.
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub struct RangeDiff {
-
    old: (git::Oid, git::Oid),
-
    new: (git::Oid, git::Oid),
+
    old: (crate::Oid, crate::Oid),
+
    new: (crate::Oid, crate::Oid),
}

impl RangeDiff {
@@ -3245,8 +3248,8 @@ mod test {

    #[test]
    fn test_revision_review_merge_redacted() {
-
        let base = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
-
        let oid = git::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap();
+
        let base = crate::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
+
        let oid = crate::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap();
        let mut alice = Actor::<MockSigner>::default();
        let rid = gen::<RepoId>(1);
        let doc = RawDoc::new(
@@ -3334,7 +3337,7 @@ mod test {
            },
            &alice,
        );
-
        let patch = Patch::from_history(&h0, &repo).unwrap();
+
        let patch: Patch = Patch::from_history(&h0, &repo).unwrap();
        assert_eq!(patch.revisions().count(), 2);

        let mut h1 = h0.clone();
@@ -3364,8 +3367,8 @@ mod test {

    #[test]
    fn test_revision_reaction() {
-
        let base = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
-
        let oid = git::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap();
+
        let base = crate::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
+
        let oid = crate::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap();
        let mut alice = Actor::<MockSigner>::default();
        let repo = gen::<MockRepository>(1);
        let reaction = Reaction::new('👍').expect("failed to create a reaction");
modified crates/radicle/src/cob/patch/cache.rs
@@ -9,7 +9,6 @@ use crate::cob::cache::{self, StoreReader};
use crate::cob::cache::{Remove, StoreWriter, Update};
use crate::cob::store;
use crate::cob::{Label, ObjectId, TypeName};
-
use crate::git;
use crate::node::device::Device;
use crate::prelude::RepoId;
use crate::storage::{HasRepoId, ReadRepository, RepositoryError, SignRepository, WriteRepository};
@@ -113,8 +112,8 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
        title: cob::Title,
        description: impl ToString,
        target: MergeTarget,
-
        base: impl Into<git::Oid>,
-
        oid: impl Into<git::Oid>,
+
        base: impl Into<crate::Oid>,
+
        oid: impl Into<crate::Oid>,
        labels: &[Label],
        signer: &Device<G>,
    ) -> Result<PatchMut<'a, 'g, R, C>, super::Error>
@@ -143,8 +142,8 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
        title: cob::Title,
        description: impl ToString,
        target: MergeTarget,
-
        base: impl Into<git::Oid>,
-
        oid: impl Into<git::Oid>,
+
        base: impl Into<crate::Oid>,
+
        oid: impl Into<crate::Oid>,
        labels: &[Label],
        signer: &Device<G>,
    ) -> Result<PatchMut<'a, 'g, R, C>, super::Error>
modified crates/radicle/src/cob/store.rs
@@ -20,7 +20,7 @@ use crate::{cob, identity};
pub trait CobAction {
    /// Parent objects this action depends on. For example, patch revisions
    /// have the commit objects as their parent.
-
    fn parents(&self) -> Vec<git::Oid> {
+
    fn parents(&self) -> Vec<crate::Oid> {
        Vec::new()
    }

@@ -122,12 +122,12 @@ pub enum Error {
    #[error("invalid or unknown embed URI: {0}")]
    EmbedUri(Uri),
    #[error(transparent)]
-
    Git(git::raw::Error),
+
    Git(crate::git2::Error),
    #[error("failed to find reference '{name}': {err}")]
    RefLookup {
-
        name: git::RefString,
+
        name: git::fmt::RefString,
        #[source]
-
        err: git::raw::Error,
+
        err: crate::git2::Error,
    },
    #[error("transaction already contains action {0} which produces an identifier, denying to add action {1} which also produces an identifier")]
    ClashingIdentifiers(String, String),
@@ -135,7 +135,7 @@ pub enum Error {

/// Storage for collaborative objects of a specific type `T` in a single repository.
pub struct Store<'a, T, R> {
-
    identity: Option<git::Oid>,
+
    identity: Option<crate::Oid>,
    repo: &'a R,
    witness: PhantomData<T>,
    type_name: &'a TypeName,
@@ -162,7 +162,7 @@ where
    }

    /// Return a new store with the attached identity.
-
    pub fn identity(self, identity: git::Oid) -> Self {
+
    pub fn identity(self, identity: crate::Oid) -> Self {
        Self {
            repo: self.repo,
            witness: self.witness,
@@ -228,7 +228,7 @@ where
            .into_iter()
            .map(|e| {
                Ok::<_, Error>(Embed {
-
                    content: git::Oid::try_from(&e.content).map_err(Error::EmbedUri)?,
+
                    content: crate::Oid::try_from(&e.content).map_err(Error::EmbedUri)?,
                    name: e.name.clone(),
                })
            })
@@ -272,7 +272,7 @@ where
            .into_iter()
            .map(|e| {
                Ok::<_, Error>(Embed {
-
                    content: git::Oid::try_from(&e.content).map_err(Error::EmbedUri)?,
+
                    content: crate::Oid::try_from(&e.content).map_err(Error::EmbedUri)?,
                    name: e.name.clone(),
                })
            })
@@ -319,7 +319,7 @@ where
                    .map_err(|e| Error::SignRefs(Box::new(e)))?;
                Ok(())
            }
-
            Err(err) if err.code() == git::raw::ErrorCode::NotFound => Ok(()),
+
            Err(err) if err.code() == crate::git2::ErrorCode::NotFound => Ok(()),
            Err(err) => Err(Error::RefLookup {
                name: name.to_ref_string(),
                err,
modified crates/radicle/src/cob/stream.rs
@@ -8,7 +8,7 @@ use std::marker::PhantomData;

use serde::Deserialize;

-
use crate::git::Oid;
+
use crate::Oid;

use super::{ObjectId, Op, TypeName};

@@ -92,7 +92,7 @@ impl HasRoot for CobRange {
///
/// To construct a `Stream`, use [`Stream::new`].
pub struct Stream<'a, A> {
-
    repo: &'a git2::Repository,
+
    repo: &'a crate::git2::Repository,
    range: CobRange,
    typename: TypeName,
    marker: PhantomData<A>,
@@ -101,7 +101,7 @@ pub struct Stream<'a, A> {
impl<'a, A> Stream<'a, A> {
    /// Construct a new stream providing the underlying `repo`, a [`CobRange`],
    /// and the [`TypeName`] of the COB that is being streamed.
-
    pub fn new(repo: &'a git2::Repository, range: CobRange, typename: TypeName) -> Self {
+
    pub fn new(repo: &'a crate::git2::Repository, range: CobRange, typename: TypeName) -> Self {
        Self {
            repo,
            range,
@@ -185,7 +185,7 @@ mod tests {
        "xyz.radicle.test".parse::<TypeName>().unwrap()
    }

-
    fn gen_ops(repo: &git2::Repository, signer: &MockSigner) -> Vec<cob::Entry> {
+
    fn gen_ops(repo: &crate::git2::Repository, signer: &MockSigner) -> Vec<cob::Entry> {
        // Number of ops
        let n = gen::<u8>(1).clamp(1, 10);
        let mut entries = Vec::with_capacity(n.into());
@@ -213,7 +213,7 @@ mod tests {
    }

    fn create_entry(
-
        repo: &git2::Repository,
+
        repo: &crate::git2::Repository,
        signer: &MockSigner,
        contents: NonEmpty<Vec<u8>>,
        parent: Option<Oid>,
modified crates/radicle/src/cob/stream/error.rs
@@ -22,7 +22,7 @@ impl Stream {
#[derive(Debug, Error)]
pub enum Ops {
    #[error("failed to get a commit while iterating over stream: {source}")]
-
    Commit { source: git2::Error },
+
    Commit { source: crate::git2::Error },
    #[error("failed to load COB operation: {source}")]
    Load { source: op::LoadError },
    #[error("failed to load COB manifest: {source}")]
modified crates/radicle/src/cob/stream/iter.rs
@@ -3,7 +3,8 @@ use std::marker::PhantomData;
use serde::Deserialize;

use crate::cob::{Op, TypeName};
-
use crate::git::{self, Oid, PatternString};
+
use crate::git::fmt::refspec::PatternString;
+
use crate::Oid;

use super::error;
use super::CobRange;
@@ -39,7 +40,7 @@ impl From<PatternString> for Until {
/// from.
pub(super) struct WalkIter<'a> {
    /// Git repository for looking up the commit object during the revwalk.
-
    repo: &'a git2::Repository,
+
    repo: &'a crate::git2::Repository,
    /// The root commit that is being walked from.
    ///
    /// N.b. This is required since ranges are non-inclusive in Git, and if the
@@ -47,7 +48,7 @@ pub(super) struct WalkIter<'a> {
    /// error.
    from: Option<Oid>,
    /// The revwalk that is being iterated over.
-
    inner: git2::Revwalk<'a>,
+
    inner: crate::git2::Revwalk<'a>,
}

impl From<CobRange> for Walk {
@@ -76,14 +77,17 @@ impl Walk {
    }

    /// Get the iterator for the walk.
-
    pub(super) fn iter(self, repo: &git2::Repository) -> Result<WalkIter<'_>, git2::Error> {
+
    pub(super) fn iter(
+
        self,
+
        repo: &crate::git2::Repository,
+
    ) -> Result<WalkIter<'_>, crate::git2::Error> {
        let mut walk = repo.revwalk()?;
        // N.b. ensure that we start from the `self.from` commit.
-
        walk.set_sorting(git2::Sort::TOPOLOGICAL.union(git2::Sort::REVERSE))?;
+
        walk.set_sorting(crate::git2::Sort::TOPOLOGICAL.union(crate::git2::Sort::REVERSE))?;
        match self.until {
            Until::Tip(tip) => walk.push_range(&format!("{}..{}", self.from, tip))?,
            Until::Glob(glob) => {
-
                walk.push(*self.from)?;
+
                walk.push(self.from.into())?;
                walk.push_glob(glob.as_str())?
            }
        }
@@ -97,13 +101,13 @@ impl Walk {
}

impl<'a> Iterator for WalkIter<'a> {
-
    type Item = Result<git2::Commit<'a>, git2::Error>;
+
    type Item = Result<crate::git2::Commit<'a>, crate::git2::Error>;

    fn next(&mut self) -> Option<Self::Item> {
        // N.b. ensure that we start using the `from` commit and use the revwalk
        // after that.
        if let Some(from) = self.from.take() {
-
            return Some(self.repo.find_commit(*from));
+
            return Some(self.repo.find_commit(from.into()));
        }
        let oid = self.inner.next()?;
        Some(oid.and_then(|oid| self.repo.find_commit(oid)))
@@ -132,7 +136,7 @@ where
        let commit = self.walk.next()?;
        match commit {
            Ok(commit) => {
-
                let entry = git::Oid::from(commit.id());
+
                let entry = crate::Oid::from(commit.id());
                // N.b. mark this commit as seen, so that it is not walked again
                self.walk.inner.hide(commit.id()).ok();
                // Skip any Op that do not match the manifest
@@ -155,7 +159,7 @@ impl<'a, A> OpsIter<'a, A> {

    /// Load the `Op` for the given `entry`, ensuring that manifest matches with
    /// the expected manifest.
-
    fn load(&self, entry: git::Oid) -> Result<Option<Op<A>>, error::Ops>
+
    fn load(&self, entry: crate::Oid) -> Result<Option<Op<A>>, error::Ops>
    where
        A: for<'de> Deserialize<'de>,
    {
modified crates/radicle/src/cob/test.rs
@@ -12,16 +12,15 @@ use crate::cob::store::encoding;
use crate::cob::{patch, Title};
use crate::cob::{Entry, History, Manifest, Timestamp, Version};
use crate::crypto::Signer;
-
use crate::git;
-
use crate::git::ext::author::Author;
-
use crate::git::ext::commit::headers::Headers;
-
use crate::git::ext::commit::{trailers::OwnedTrailer, Commit};
-
use crate::git::Oid;
+
use crate::git2::ext::author::Author;
+
use crate::git2::ext::commit::headers::Headers;
+
use crate::git2::ext::commit::{trailers::OwnedTrailer, Commit};
use crate::node::device::Device;
use crate::prelude::Did;
use crate::profile::env;
use crate::storage::ReadRepository;
use crate::test::arbitrary;
+
use crate::Oid;

use super::store::{Cob, CobWithType};
use super::thread;
@@ -101,7 +100,7 @@ where
        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::Oid {
        let timestamp = self.time;
        let tips = self.tips();
        let revision = arbitrary::oid();
@@ -182,7 +181,7 @@ impl<G: Signer> Actor<G> {
            "nonce": fastrand::u64(..),
        }))
        .unwrap();
-
        let oid = git::raw::Oid::hash_object(git::raw::ObjectType::Blob, &data).unwrap();
+
        let oid = crate::git2::Oid::hash_object(crate::git2::ObjectType::Blob, &data).unwrap();
        let id = oid.into();
        let author = *self.signer.public_key();
        let actions = NonEmpty::from_vec(actions).unwrap();
@@ -226,8 +225,8 @@ impl<G: Signer> Actor<G> {
        &mut self,
        title: Title,
        description: impl ToString,
-
        base: git::Oid,
-
        oid: git::Oid,
+
        base: crate::Oid,
+
        oid: crate::Oid,
        repo: &R,
    ) -> Result<Patch, patch::Error> {
        Patch::from_root(
@@ -257,14 +256,14 @@ pub fn encoded<T: Cob, G: Signer>(
    timestamp: Timestamp,
    parents: impl IntoIterator<Item = Oid>,
    signer: &G,
-
) -> (Vec<u8>, git::ext::Oid) {
+
) -> (Vec<u8>, crate::Oid) {
    let data = encoding::encode(action).unwrap();
-
    let oid = git::raw::Oid::hash_object(git::raw::ObjectType::Blob, &data).unwrap();
-
    let parents = parents.into_iter().map(|o| *o);
+
    let oid = crate::git2::Oid::hash_object(crate::git2::ObjectType::Blob, &data).unwrap();
+
    let parents = parents.into_iter().map(|o| o.into());
    let author = Author {
        name: "radicle".to_owned(),
        email: signer.public_key().to_human(),
-
        time: git_ext::author::Time::new(timestamp.as_secs() as i64, 0),
+
        time: crate::git2::ext::author::Time::new(timestamp.as_secs() as i64, 0),
    };
    let commit = Commit::new::<_, _, OwnedTrailer>(
        oid,
@@ -277,7 +276,8 @@ pub fn encoded<T: Cob, G: Signer>(
    )
    .to_string();

-
    let hash = git::raw::Oid::hash_object(git::raw::ObjectType::Commit, commit.as_bytes()).unwrap();
+
    let hash =
+
        crate::git2::Oid::hash_object(crate::git2::ObjectType::Commit, commit.as_bytes()).unwrap();

    (data, hash.into())
}
modified crates/radicle/src/cob/thread.rs
@@ -10,7 +10,6 @@ use crate::cob;
use crate::cob::common::{Reaction, Timestamp, Uri};
use crate::cob::store::Cob;
use crate::cob::{op, ActorId, Embed, EntryId, Op};
-
use crate::git;
use crate::prelude::ReadRepository;

/// Type name of a thread, as well as the domain for all thread operations.
@@ -368,7 +367,7 @@ impl Thread {
        author: ActorId,
        timestamp: Timestamp,
        _concurrent: &[&cob::Entry],
-
        _identity: git::Oid,
+
        _identity: crate::Oid,
        _repo: &R,
    ) -> Result<(), Error> {
        match action {
modified crates/radicle/src/explorer.rs
@@ -2,8 +2,8 @@ use std::str::FromStr;

use thiserror::Error;

+
use crate::cob;
use crate::prelude::RepoId;
-
use crate::{cob, git};

#[derive(Debug, Error)]
pub enum ExplorerError {
@@ -21,7 +21,7 @@ pub enum ExplorerError {
#[derive(Debug, Hash, PartialEq, Eq)]
pub enum ExplorerResource {
    /// Git tree object. Used for the repository root.
-
    Tree { oid: git::Oid },
+
    Tree { oid: crate::Oid },
    /// A Patch COB.
    Patch { id: cob::ObjectId },
}
modified crates/radicle/src/git.rs
@@ -6,8 +6,6 @@ use std::process::Command;
use std::str::FromStr;
use std::sync::LazyLock;

-
use git_ext::ref_format as format;
-

use crate::collections::RandomMap;
use crate::crypto::PublicKey;
use crate::node::Alias;
@@ -16,21 +14,21 @@ use crate::storage;
use crate::storage::refs::Refs;
use crate::storage::RemoteId;

-
pub use ext::is_not_found_err;
-
pub use ext::Error;
-
pub use ext::NotFound;
-
pub use ext::Oid;
-
pub use git2 as raw;
-
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 crate::Oid;
+

+
pub extern crate radicle_git_ref_format as fmt;
+

+
use fmt::*;
+

pub use storage::git::transport::local::Url;
pub use storage::BranchName;

+
// This migth seem weird, but will make the code in this file look more like in
+
// other modules in the crate.
+
use crate::git;
+

+
use crate::git2::ErrorExt as _;
+

/// Default port of the `git` transport protocol.
pub const PROTOCOL_PORT: u16 = 9418;
/// Minimum required git version.
@@ -161,25 +159,25 @@ pub enum RefError {
    #[error("unexpected unqualified ref: {0}")]
    Unqualified(RefString),
    #[error("invalid ref format: {0}")]
-
    Format(#[from] format::Error),
+
    Format(#[from] git::fmt::Error),
    #[error("reference has no target")]
    NoTarget,
    #[error("expected ref to begin with 'refs/namespaces' but found '{0}'")]
-
    MissingNamespace(format::RefString),
+
    MissingNamespace(git::fmt::RefString),
    #[error("ref name contains invalid namespace identifier '{name}'")]
    InvalidNamespace {
-
        name: format::RefString,
+
        name: git::fmt::RefString,
        #[source]
        err: Box<dyn std::error::Error + Send + Sync + 'static>,
    },
    #[error(transparent)]
-
    Other(#[from] git2::Error),
+
    Other(#[from] crate::git2::Error),
}

#[derive(thiserror::Error, Debug)]
pub enum ListRefsError {
    #[error("git error: {0}")]
-
    Git(#[from] git2::Error),
+
    Git(#[from] crate::git2::Error),
    #[error("invalid ref: {0}")]
    InvalidRef(#[from] RefError),
}
@@ -189,7 +187,9 @@ pub mod refs {
    use radicle_cob as cob;

    /// Try to get a qualified reference from a generic reference.
-
    pub fn qualified_from<'a>(r: &'a git2::Reference) -> Result<(Qualified<'a>, Oid), RefError> {
+
    pub fn qualified_from<'a>(
+
        r: &'a crate::git2::Reference,
+
    ) -> Result<(Qualified<'a>, Oid), RefError> {
        let name = r.name().ok_or(RefError::InvalidName)?;
        let refstr = RefStr::try_from_str(name)?;
        let target = r.resolve()?.target().ok_or(RefError::NoTarget)?;
@@ -220,7 +220,7 @@ pub mod refs {
    }

    pub mod storage {
-
        use format::{
+
        use super::fmt::{
            lit,
            name::component,
            refspec::{self, PatternString},
@@ -422,7 +422,7 @@ pub mod refs {

    pub mod workdir {
        use super::*;
-
        use format::name::component;
+
        use fmt::name::component;

        /// Create a [`RefString`] that corresponds to `refs/heads/<branch>`.
        pub fn branch(branch: &RefStr) -> RefString {
@@ -462,9 +462,9 @@ pub mod refs {
pub fn remote_refs(url: &Url) -> Result<RandomMap<RemoteId, Refs>, ListRefsError> {
    let url = url.to_string();
    let mut remotes = RandomMap::default();
-
    let mut remote = git2::Remote::create_detached(url)?;
+
    let mut remote = crate::git2::Remote::create_detached(url)?;

-
    remote.connect(git2::Direction::Fetch)?;
+
    remote.connect(crate::git2::Direction::Fetch)?;

    let refs = remote.list()?;
    for r in refs {
@@ -497,7 +497,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
/// wanted to parse the namespace as a `PublicKey`, then you would the function
/// 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
    T: FromStr,
    T::Err: std::error::Error + Send + Sync + 'static,
@@ -526,12 +526,12 @@ where
/// The `T` can be specified when calling the function. For example, if you
/// wanted to parse the namespace as a `PublicKey`, then you would the function
/// like so, `parse_ref::<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
    T: FromStr,
    T::Err: std::error::Error + Send + Sync + 'static,
{
-
    let input = format::RefStr::try_from_str(s)?;
+
    let input = fmt::RefStr::try_from_str(s)?;
    match input.to_namespaced() {
        None => {
            let refname = Qualified::from_refstr(input)
@@ -557,9 +557,9 @@ where

/// Create an initial empty commit.
pub fn initial_commit<'a>(
-
    repo: &'a git2::Repository,
-
    sig: &git2::Signature,
-
) -> Result<git2::Commit<'a>, git2::Error> {
+
    repo: &'a crate::git2::Repository,
+
    sig: &crate::git2::Signature,
+
) -> Result<crate::git2::Commit<'a>, crate::git2::Error> {
    let tree_id = repo.index()?.write_tree()?;
    let tree = repo.find_tree(tree_id)?;
    let oid = repo.commit(None, sig, sig, "Initial commit", &tree, &[])?;
@@ -570,13 +570,13 @@ pub fn initial_commit<'a>(

/// Create a commit and update the given ref to it.
pub fn commit<'a>(
-
    repo: &'a git2::Repository,
-
    parent: &'a git2::Commit,
+
    repo: &'a crate::git2::Repository,
+
    parent: &'a crate::git2::Commit,
    target: &RefStr,
    message: &str,
-
    sig: &git2::Signature,
-
    tree: &git2::Tree,
-
) -> Result<git2::Commit<'a>, git2::Error> {
+
    sig: &crate::git2::Signature,
+
    tree: &crate::git2::Tree,
+
) -> Result<crate::git2::Commit<'a>, crate::git2::Error> {
    let oid = repo.commit(Some(target.as_str()), sig, sig, message, tree, &[parent])?;
    let commit = repo.find_commit(oid)?;

@@ -585,12 +585,12 @@ pub fn commit<'a>(

/// Create an empty commit on top of the parent.
pub fn empty_commit<'a>(
-
    repo: &'a git2::Repository,
-
    parent: &'a git2::Commit,
+
    repo: &'a crate::git2::Repository,
+
    parent: &'a crate::git2::Commit,
    target: &RefStr,
    message: &str,
-
    sig: &git2::Signature,
-
) -> Result<git2::Commit<'a>, git2::Error> {
+
    sig: &crate::git2::Signature,
+
) -> Result<crate::git2::Commit<'a>, crate::git2::Error> {
    let tree = parent.tree()?;
    let oid = repo.commit(Some(target.as_str()), sig, sig, message, &tree, &[parent])?;
    let commit = repo.find_commit(oid)?;
@@ -599,7 +599,7 @@ pub fn empty_commit<'a>(
}

/// Get the repository head.
-
pub fn head(repo: &git2::Repository) -> Result<git2::Commit, git2::Error> {
+
pub fn head(repo: &crate::git2::Repository) -> Result<crate::git2::Commit, crate::git2::Error> {
    let head = repo.head()?.peel_to_commit()?;

    Ok(head)
@@ -609,8 +609,8 @@ pub fn head(repo: &git2::Repository) -> Result<git2::Commit, git2::Error> {
pub fn write_tree<'r>(
    path: &Path,
    bytes: &[u8],
-
    repo: &'r git2::Repository,
-
) -> Result<git2::Tree<'r>, Error> {
+
    repo: &'r crate::git2::Repository,
+
) -> Result<crate::git2::Tree<'r>, crate::git2::Error> {
    let blob_id = repo.blob(bytes)?;
    let mut builder = repo.treebuilder(None)?;
    builder.insert(path, blob_id, 0o100_644)?;
@@ -624,7 +624,7 @@ pub fn write_tree<'r>(
/// Configure a radicle repository.
///
/// * Sets `push.default = upstream`.
-
pub fn configure_repository(repo: &git2::Repository) -> Result<(), git2::Error> {
+
pub fn configure_repository(repo: &crate::git2::Repository) -> Result<(), crate::git2::Error> {
    let mut cfg = repo.config()?;
    cfg.set_str("push.default", "upstream")?;

@@ -652,11 +652,11 @@ pub fn configure_repository(repo: &git2::Repository) -> Result<(), git2::Error>
///  2. `tagOpt = --no-tags` to ensure that tags are not fetched and stored
///     under `refs/tags`, again, because these are fetched by the `rad` remote.
pub fn configure_remote<'r>(
-
    repo: &'r git2::Repository,
+
    repo: &'r crate::git2::Repository,
    name: &str,
    fetch: &Url,
    push: &Url,
-
) -> Result<git2::Remote<'r>, git2::Error> {
+
) -> Result<crate::git2::Remote<'r>, crate::git2::Error> {
    let fetchspec = format!("+refs/heads/*:refs/remotes/{name}/*");
    let remote = repo.remote_with_fetch(name, fetch.to_string().as_str(), &fetchspec)?;

@@ -679,14 +679,14 @@ pub fn configure_remote<'r>(
}

/// Fetch from the given `remote`.
-
pub fn fetch(repo: &git2::Repository, remote: &str) -> Result<(), git2::Error> {
+
pub fn fetch(repo: &crate::git2::Repository, remote: &str) -> Result<(), crate::git2::Error> {
    repo.find_remote(remote)?.fetch::<&str>(
        &[],
        Some(
-
            git2::FetchOptions::new()
+
            crate::git2::FetchOptions::new()
                .update_fetchhead(false)
-
                .prune(git2::FetchPrune::On)
-
                .download_tags(git2::AutotagOption::None),
+
                .prune(crate::git2::FetchPrune::On)
+
                .download_tags(crate::git2::AutotagOption::None),
        ),
        None,
    )
@@ -694,10 +694,10 @@ pub fn fetch(repo: &git2::Repository, remote: &str) -> Result<(), git2::Error> {

/// Push `refspecs` to the given `remote` using the provided `namespace`.
pub fn push<'a>(
-
    repo: &git2::Repository,
+
    repo: &crate::git2::Repository,
    remote: &str,
    refspecs: impl IntoIterator<Item = (&'a Qualified<'a>, &'a Qualified<'a>)>,
-
) -> Result<(), git2::Error> {
+
) -> Result<(), crate::git2::Error> {
    let refspecs = refspecs
        .into_iter()
        .map(|(src, dst)| format!("{src}:{dst}"));
@@ -719,11 +719,11 @@ pub fn push<'a>(
///     merge = refs/heads/main
/// ```
pub fn set_upstream(
-
    repo: &git2::Repository,
+
    repo: &crate::git2::Repository,
    remote: impl AsRef<str>,
    branch: impl AsRef<str>,
    merge: impl AsRef<str>,
-
) -> Result<(), git2::Error> {
+
) -> Result<(), crate::git2::Error> {
    let remote = remote.as_ref();
    let branch = branch.as_ref();
    let merge = merge.as_ref();
@@ -733,14 +733,14 @@ pub fn set_upstream(
    let branch_merge = format!("branch.{branch}.merge");

    config.remove_multivar(&branch_remote, ".*").or_else(|e| {
-
        if ext::is_not_found_err(&e) {
+
        if e.is_not_found() {
            Ok(())
        } else {
            Err(e)
        }
    })?;
    config.remove_multivar(&branch_merge, ".*").or_else(|e| {
-
        if ext::is_not_found_err(&e) {
+
        if e.is_not_found() {
            Ok(())
        } else {
            Err(e)
@@ -752,14 +752,16 @@ pub fn set_upstream(
    Ok(())
}

-
pub fn init_default_branch(repo: &git2::Repository) -> Result<Option<String>, git2::Error> {
+
pub fn init_default_branch(
+
    repo: &crate::git2::Repository,
+
) -> Result<Option<String>, crate::git2::Error> {
    let config = repo.config().and_then(|mut c| c.snapshot())?;
    let default_branch = config.get_str("init.defaultbranch")?;
-
    let branch = repo.find_branch(default_branch, git2::BranchType::Local)?;
+
    let branch = repo.find_branch(default_branch, crate::git2::BranchType::Local)?;
    Ok(branch.into_reference().shorthand().map(ToOwned::to_owned))
}

-
pub fn head_refname(repo: &git2::Repository) -> Result<Option<String>, git2::Error> {
+
pub fn head_refname(repo: &crate::git2::Repository) -> Result<Option<String>, crate::git2::Error> {
    let head = repo.head()?;
    match head.shorthand() {
        Some("HEAD") => Ok(None),
modified crates/radicle/src/git/canonical.rs
@@ -19,11 +19,12 @@ use std::fmt;
use std::marker::PhantomData;
use std::ops::ControlFlow;

-
use git_ext::ref_format::Namespaced;
+
use crate::git::fmt::Namespaced;

use crate::prelude::Did;

-
use super::{Oid, Qualified};
+
use super::fmt::Qualified;
+
use crate::Oid;

/// A marker for the initial state of [`Canonical`], after construction using
/// [`Canonical::new`].
@@ -390,12 +391,12 @@ pub enum Object {
}

impl Object {
-
    pub fn new(obj: &crate::git::raw::Object) -> Option<Self> {
+
    pub fn new(obj: &crate::git2::Object) -> Option<Self> {
        obj.kind().and_then(|kind| match kind {
-
            crate::git::raw::ObjectType::Commit => Some(Self::Commit {
+
            crate::git2::ObjectType::Commit => Some(Self::Commit {
                id: obj.id().into(),
            }),
-
            crate::git::raw::ObjectType::Tag => Some(Self::Tag {
+
            crate::git2::ObjectType::Tag => Some(Self::Tag {
                id: obj.id().into(),
            }),
            _ => None,
@@ -510,20 +511,19 @@ mod tests {

    /// Test helper to construct a Canonical and get the quorum
    fn quorum(
-
        heads: &[git::raw::Oid],
+
        heads: &[crate::Oid],
        threshold: usize,
-
        repo: &git::raw::Repository,
+
        repo: &crate::git2::Repository,
    ) -> Result<Oid, QuorumError> {
-
        let refname =
-
            git::refs::branch(git_ext::ref_format::RefStr::try_from_str("master").unwrap());
+
        let refname = git::refs::branch(crate::git::fmt::RefStr::try_from_str("master").unwrap());

        let mut delegates = Vec::new();
        for (i, head) in heads.iter().enumerate() {
            let signer = Device::mock_from_seed([(i + 1) as u8; 32]);
            let did = Did::from(signer.public_key());
            delegates.push(did);
-
            let ns = git::Component::from(signer.public_key());
-
            repo.reference(refname.with_namespace(ns).as_str(), *head, true, "")
+
            let ns = git::fmt::Component::from(signer.public_key());
+
            repo.reference(refname.with_namespace(ns).as_str(), head.into(), true, "")
                .unwrap();
        }

@@ -557,18 +557,18 @@ mod tests {
    fn test_quorum_properties() {
        let tmp = tempfile::tempdir().unwrap();
        let (repo, c0) = fixtures::repository(tmp.path());
-
        let c0: git::Oid = c0.into();
-
        let a1 = fixtures::commit("A1", &[*c0], &repo);
-
        let a2 = fixtures::commit("A2", &[*a1], &repo);
-
        let d1 = fixtures::commit("D1", &[*c0], &repo);
-
        let c1 = fixtures::commit("C1", &[*c0], &repo);
-
        let c2 = fixtures::commit("C2", &[*c1], &repo);
-
        let b2 = fixtures::commit("B2", &[*c1], &repo);
-
        let a1 = fixtures::commit("A1", &[*c0], &repo);
-
        let m1 = fixtures::commit("M1", &[*c2, *b2], &repo);
-
        let m2 = fixtures::commit("M2", &[*a1, *b2], &repo);
+
        let c0: crate::Oid = c0;
+
        let a1 = fixtures::commit("A1", &[c0], &repo);
+
        let a2 = fixtures::commit("A2", &[a1], &repo);
+
        let d1 = fixtures::commit("D1", &[c0], &repo);
+
        let c1 = fixtures::commit("C1", &[c0], &repo);
+
        let c2 = fixtures::commit("C2", &[c1], &repo);
+
        let b2 = fixtures::commit("B2", &[c1], &repo);
+
        let a1 = fixtures::commit("A1", &[c0], &repo);
+
        let m1 = fixtures::commit("M1", &[c2, b2], &repo);
+
        let m2 = fixtures::commit("M2", &[a1, b2], &repo);
        let mut rng = fastrand::Rng::new();
-
        let choices = [*c0, *c1, *c2, *b2, *a1, *a2, *d1, *m1, *m2];
+
        let choices = [c0, c1, c2, b2, a1, a2, d1, m1, m2];

        for _ in 0..100 {
            let count = rng.usize(1..=choices.len());
@@ -591,11 +591,11 @@ mod tests {
    fn test_quorum_different_types() {
        let tmp = tempfile::tempdir().unwrap();
        let (repo, c0) = fixtures::repository(tmp.path());
-
        let c0: git::Oid = c0.into();
-
        let t0 = fixtures::tag("v1", "", *c0, &repo);
+
        let c0: crate::Oid = c0;
+
        let t0 = fixtures::tag("v1", "", c0, &repo);

        assert_matches!(
-
            quorum(&[*c0, *t0], 1, &repo),
+
            quorum(&[c0, t0], 1, &repo),
            Err(QuorumError::DifferentTypes { .. })
        );
    }
modified crates/radicle/src/git/canonical/convergence.rs
@@ -1,7 +1,7 @@
use std::{fmt, ops::ControlFlow};

-
use crate::git::Oid;
use crate::prelude::Did;
+
use crate::Oid;

use super::{effects, error, Object};

modified crates/radicle/src/git/canonical/effects.rs
@@ -1,8 +1,10 @@
use std::collections::{BTreeMap, BTreeSet};

use crate::git;
-
use crate::git::{Oid, Qualified};
+
use crate::git::fmt::Qualified;
+
use crate::git2::ErrorExt as _;
use crate::prelude::Did;
+
use crate::Oid;

use super::{FoundObjects, GraphAheadBehind, MergeBase, Object};

@@ -39,7 +41,7 @@ pub enum FindObjectsError {
    },
    #[error("failed to find reference {refname} due to: {source}")]
    FindReference {
-
        refname: git::Namespaced<'static>,
+
        refname: git::fmt::Namespaced<'static>,
        source: Box<dyn std::error::Error + Send + Sync + 'static>,
    },
    #[error("failed to find objects")]
@@ -59,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
        E: std::error::Error + Send + Sync + 'static,
    {
@@ -167,9 +169,9 @@ pub struct GraphDescendant {
// `git2` implementations of the above effects
// ===========================================

-
impl FindMergeBase for git2::Repository {
+
impl FindMergeBase for crate::git2::Repository {
    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 {
                a,
                b,
@@ -183,13 +185,13 @@ impl FindMergeBase for git2::Repository {
    }
}

-
impl Ancestry for git2::Repository {
+
impl Ancestry for crate::git2::Repository {
    fn graph_ahead_behind(
        &self,
        commit: Oid,
        upstream: Oid,
    ) -> Result<GraphAheadBehind, GraphDescendant> {
-
        self.graph_ahead_behind(*commit, *upstream)
+
        self.graph_ahead_behind(commit.into(), upstream.into())
            .map_err(|err| GraphDescendant {
                commit,
                upstream,
@@ -199,7 +201,7 @@ impl Ancestry for git2::Repository {
    }
}

-
impl FindObjects for git2::Repository {
+
impl FindObjects for crate::git2::Repository {
    fn find_objects<'a, 'b, I>(
        &self,
        refname: &Qualified,
@@ -215,7 +217,7 @@ impl FindObjects for git2::Repository {
            let name = &refname.with_namespace(did.as_key().into());
            let reference = match self.find_reference(name.as_str()) {
                Ok(reference) => reference,
-
                Err(e) if git::ext::is_not_found_err(&e) => {
+
                Err(e) if e.is_not_found() => {
                    missing_refs.insert(name.to_owned());
                    continue;
                }
@@ -227,7 +229,7 @@ impl FindObjects for git2::Repository {
                log::warn!(target: "radicle", "Missing target for reference `{name}`");
                continue;
            };
-
            let object = match self.find_object(*oid, None) {
+
            let object = match self.find_object(oid.into(), None) {
                Ok(object) => Object::new(&object).ok_or_else(|| {
                    FindObjectsError::invalid_object_type(
                        *did,
@@ -235,7 +237,7 @@ impl FindObjects for git2::Repository {
                        object.kind().map(|kind| kind.to_string()),
                    )
                }),
-
                Err(err) if git::ext::is_not_found_err(&err) => {
+
                Err(err) if err.is_not_found() => {
                    missing_objects.insert(*did, oid);
                    continue;
                }
modified crates/radicle/src/git/canonical/error.rs
@@ -1,6 +1,6 @@
use thiserror::Error;

-
use crate::git::Oid;
+
use crate::Oid;

use super::{effects, ObjectType};
pub use effects::{FindObjectsError, MergeBaseError};
modified crates/radicle/src/git/canonical/quorum.rs
@@ -1,6 +1,6 @@
use std::{cmp::Ordering, collections::BTreeMap};

-
use crate::git::Oid;
+
use crate::Oid;

use super::voting::{CommitVoting, TagVoting};
use super::{MergeBase, Object};
@@ -239,7 +239,8 @@ pub enum CommitQuorumFailure {
#[allow(clippy::unwrap_used)]
#[cfg(test)]
mod test {
-
    use crate::git::{canonical::MergeBase, Oid};
+
    use crate::git::canonical::MergeBase;
+
    use crate::Oid;

    use super::MergeBases;

modified crates/radicle/src/git/canonical/rules.rs
@@ -22,9 +22,9 @@ use thiserror::Error;
use crate::git;
use crate::git::canonical;
use crate::git::canonical::Canonical;
+
use crate::git::fmt::Qualified;
use crate::git::fmt::{refname, RefString};
use crate::git::refspec::QualifiedPattern;
-
use crate::git::Qualified;
use crate::identity::{doc, Did};

const ASTERISK: char = '*';
@@ -214,7 +214,7 @@ impl Ord for Pattern {
            }
        }

-
        use git::refspec::Component;
+
        use git::fmt::refspec::Component;

        fn cmp_component(lhs: Component<'_>, rhs: Component<'_>) -> ComponentOrdering {
            let (l, r) = (lhs.as_str(), rhs.as_str());
@@ -401,7 +401,10 @@ impl ValidRule {
    /// # Errors
    ///
    /// If the `name` reference begins with `refs/rad`.
-
    pub fn default_branch(did: Did, name: &git::RefStr) -> Result<(Pattern, Self), PatternError> {
+
    pub fn default_branch(
+
        did: Did,
+
        name: &git::fmt::RefStr,
+
    ) -> Result<(Pattern, Self), PatternError> {
        let pattern = Pattern::try_from(git::refs::branch(name).to_owned())?;
        let rule = Self {
            allow: ResolvedDelegates::Delegates(doc::Delegates::from(did)),
@@ -732,9 +735,7 @@ pub enum ValidationError {
#[derive(Debug, Error)]
pub enum CanonicalError {
    #[error(transparent)]
-
    Git(#[from] git::raw::Error),
-
    #[error(transparent)]
-
    References(#[from] git::ext::Error),
+
    Git(#[from] crate::git2::Error),
}

#[cfg(test)]
@@ -746,8 +747,8 @@ mod tests {

    use crate::crypto::{test::signer::MockSigner, Signer};
    use crate::git;
-
    use crate::git::refspec::qualified_pattern;
-
    use crate::git::RefString;
+
    use crate::git::fmt::refspec::qualified_pattern;
+
    use crate::git::fmt::RefString;
    use crate::identity::doc::Doc;
    use crate::identity::Visibility;
    use crate::node::device::Device;
@@ -780,9 +781,9 @@ mod tests {
        doc.delegates().clone()
    }

-
    fn tag(name: RefString, head: git2::Oid, repo: &git2::Repository) -> git::Oid {
+
    fn tag(name: RefString, head: crate::Oid, repo: &crate::git2::Repository) -> crate::Oid {
        let commit = fixtures::commit(name.as_str(), &[head], repo);
-
        let target = repo.find_object(*commit, None).unwrap();
+
        let target = repo.find_object(commit.into(), None).unwrap();
        let tagger = repo.signature().unwrap();
        repo.tag(name.as_str(), &target, &tagger, name.as_str(), false)
            .unwrap()
@@ -898,23 +899,28 @@ mod tests {
            "example 1"
        );
        assert!(
-
            pattern(qualified_pattern!("a")) < pattern(qualified_pattern!("*")),
+
            pattern(qualified_pattern!("refs/heads/a"))
+
                < pattern(qualified_pattern!("refs/heads/*")),
            "example 2.a"
        );
        assert!(
-
            pattern(qualified_pattern!("abc")) < pattern(qualified_pattern!("a*")),
+
            pattern(qualified_pattern!("refs/heads/abc"))
+
                < pattern(qualified_pattern!("refs/heads/a*")),
            "example 2.a"
        );
        assert!(
-
            pattern(qualified_pattern!("a/b/*")) < pattern(qualified_pattern!("a/*/c")),
+
            pattern(qualified_pattern!("refs/heads/a/b/*"))
+
                < pattern(qualified_pattern!("refs/heads/a/*/c")),
            "example 2.a"
        );
        assert!(
-
            pattern(qualified_pattern!("aa*")) < pattern(qualified_pattern!("a*")),
+
            pattern(qualified_pattern!("refs/heads/aa*"))
+
                < pattern(qualified_pattern!("refs/heads/a*")),
            "example 2.b.A"
        );
        assert!(
-
            pattern(qualified_pattern!("a*b")) < pattern(qualified_pattern!("a*")),
+
            pattern(qualified_pattern!("refs/heads/a*b"))
+
                < pattern(qualified_pattern!("refs/heads/a*")),
            "example 2.b.B"
        );

@@ -1112,7 +1118,7 @@ mod tests {
            &repo,
            "heartwood".try_into().unwrap(),
            "Radicle Heartwood Protocol & Stack",
-
            git::refname!("master"),
+
            git::fmt::refname!("master"),
            Visibility::default(),
            &delegate,
            &storage,
@@ -1126,15 +1132,15 @@ mod tests {
        // Create tags and keep track of their OIDs
        //
        // follows the `refs/tags/release/candidates/*` rule
-
        let failing_tag = git::refname!("release/candidates/v1.0");
+
        let failing_tag = git::fmt::refname!("release/candidates/v1.0");
        let tags = [
            // follows the `refs/tags/*` rule
-
            git::refname!("v1.0"),
+
            git::fmt::refname!("v1.0"),
            // follows the `refs/tags/release/*` rule
-
            git::refname!("release/v1.0"),
+
            git::fmt::refname!("release/v1.0"),
            failing_tag.clone(),
            // follows the `refs/tags/*` rule
-
            git::refname!("qa/v1.0"),
+
            git::fmt::refname!("qa/v1.0"),
        ]
        .into_iter()
        .map(|name| {
@@ -1150,20 +1156,20 @@ mod tests {
            &rad::REMOTE_NAME,
            [
                (
-
                    &git::qualified!("refs/tags/v1.0"),
-
                    &git::qualified!("refs/tags/v1.0"),
+
                    &git::fmt::qualified!("refs/tags/v1.0"),
+
                    &git::fmt::qualified!("refs/tags/v1.0"),
                ),
                (
-
                    &git::qualified!("refs/tags/release/v1.0"),
-
                    &git::qualified!("refs/tags/release/v1.0"),
+
                    &git::fmt::qualified!("refs/tags/release/v1.0"),
+
                    &git::fmt::qualified!("refs/tags/release/v1.0"),
                ),
                (
-
                    &git::qualified!("refs/tags/release/candidates/v1.0"),
-
                    &git::qualified!("refs/tags/release/candidates/v1.0"),
+
                    &git::fmt::qualified!("refs/tags/release/candidates/v1.0"),
+
                    &git::fmt::qualified!("refs/tags/release/candidates/v1.0"),
                ),
                (
-
                    &git::qualified!("refs/tags/qa/v1.0"),
-
                    &git::qualified!("refs/tags/qa/v1.0"),
+
                    &git::fmt::qualified!("refs/tags/qa/v1.0"),
+
                    &git::fmt::qualified!("refs/tags/qa/v1.0"),
                ),
            ],
        )
@@ -1194,7 +1200,7 @@ mod tests {
        // All tags should succeed at getting their canonical commit other than the
        // candidates tag.
        let stored = storage.repository(rid).unwrap();
-
        let failing = git::Qualified::from(git::lit::refs_tags(failing_tag));
+
        let failing = git::fmt::Qualified::from(git::lit::refs_tags(failing_tag));
        for (refname, oid) in tags.into_iter() {
            let canonical = rules
                .canonical(refname.clone(), &stored)
modified crates/radicle/src/git/canonical/voting.rs
@@ -1,6 +1,6 @@
use std::collections::BTreeMap;

-
use crate::git::Oid;
+
use crate::Oid;

use super::MergeBase;

added crates/radicle/src/git2.rs
@@ -0,0 +1,39 @@
+
// Anything inside this module may refer to [`git2`] directly, proceed with caution!
+
// Created by manually scanning the `heartwood` workspace on 2025-10-04.
+

+
// Re-exports
+
pub use git2::{
+
    message_trailers_strs, AnnotatedCommit, AutotagOption, Blob, Branch, BranchType, Commit,
+
    Config, Diff, DiffFindOptions, DiffOptions, DiffStats, Direction, Error, ErrorClass, ErrorCode,
+
    FetchOptions, FetchPrune, FileMode, MergeAnalysis, MergeOptions, Object, ObjectType, Oid,
+
    Reference, Remote, RemoteCallbacks, Repository, RepositoryInitOptions, RepositoryOpenFlags,
+
    Revwalk, Signature, Sort, Time, Tree,
+
};
+

+
// Re-exports
+
pub mod build {
+
    pub use git2::build::CheckoutBuilder;
+
}
+

+
// Re-exports
+
pub mod transport {
+
    pub use git2::transport::{
+
        register, Service, SmartSubtransport, SmartSubtransportStream, Transport,
+
    };
+
}
+

+
// Implementation-dependent extensions.
+
pub mod ext {
+
    #[cfg(test)]
+
    pub use radicle_git2_metadata::*;
+
}
+

+
pub trait ErrorExt {
+
    fn is_not_found(&self) -> bool;
+
}
+

+
impl ErrorExt for git2::Error {
+
    fn is_not_found(&self) -> bool {
+
        self.code() == git2::ErrorCode::NotFound
+
    }
+
}
modified crates/radicle/src/identity/doc.rs
@@ -10,9 +10,9 @@ use std::path::Path;
use std::str::FromStr;
use std::sync::LazyLock;

+
use crate::Oid;
use nonempty::NonEmpty;
use radicle_cob::type_name::{TypeName, TypeNameParse};
-
use radicle_git_ext::Oid;
use serde::{de, Deserialize, Serialize};
use thiserror::Error;

@@ -22,6 +22,7 @@ use crate::crypto;
use crate::crypto::Signature;
use crate::git;
use crate::git::canonical::rules;
+
use crate::git2::ErrorExt as _;
use crate::identity::{project::Project, Did};
use crate::node::device::Device;
use crate::storage;
@@ -52,9 +53,7 @@ pub enum DocError {
    #[error(transparent)]
    Threshold(#[from] ThresholdError),
    #[error("git: {0}")]
-
    GitExt(#[from] git::Error),
-
    #[error("git: {0}")]
-
    Git(#[from] git2::Error),
+
    Git(#[from] crate::git2::Error),
    #[error("missing identity document")]
    Missing,
}
@@ -71,9 +70,7 @@ impl DocError {
    /// Whether this error is caused by the document not being found.
    pub fn is_not_found(&self) -> bool {
        match self {
-
            Self::GitExt(git::Error::NotFound(_)) => true,
-
            Self::GitExt(git::Error::Git(e)) if git::is_not_found_err(e) => true,
-
            Self::Git(err) if git::is_not_found_err(err) => true,
+
            Self::Git(err) if err.is_not_found() => true,
            _ => false,
        }
    }
@@ -687,7 +684,7 @@ impl Doc {
    }

    /// Construct a [`Doc`] contained in the provided Git blob.
-
    pub fn from_blob(blob: &git2::Blob) -> Result<Self, DocError> {
+
    pub fn from_blob(blob: &crate::git2::Blob) -> Result<Self, DocError> {
        RawDoc::from_json(blob.content())?.verified()
    }

@@ -810,7 +807,7 @@ impl Doc {
        if !self.is_delegate(&key.into()) {
            return Err(*key);
        }
-
        if key.verify(blob.as_bytes(), signature).is_err() {
+
        if key.verify(blob.as_ref(), signature).is_err() {
            return Err(*key);
        }
        Ok(())
@@ -830,32 +827,32 @@ impl Doc {
    pub(crate) fn blob_at<R: ReadRepository>(
        commit: Oid,
        repo: &R,
-
    ) -> Result<git2::Blob, DocError> {
+
    ) -> Result<crate::git2::Blob, DocError> {
        let path = Path::new("embeds").join(*PATH);
        repo.blob_at(commit, path.as_path()).map_err(DocError::from)
    }

    /// Encode the [`Doc`] as canonical JSON, returning the set of bytes and its
    /// corresponding Git [`Oid`].
-
    pub fn encode(&self) -> Result<(git::Oid, Vec<u8>), DocError> {
+
    pub fn encode(&self) -> Result<(crate::Oid, Vec<u8>), DocError> {
        let mut buf = Vec::new();
        let mut serializer =
            serde_json::Serializer::with_formatter(&mut buf, CanonicalFormatter::new());

        self.serialize(&mut serializer)?;
-
        let oid = git2::Oid::hash_object(git2::ObjectType::Blob, &buf)?;
+
        let oid = crate::git2::Oid::hash_object(crate::git2::ObjectType::Blob, &buf)?;

        Ok((oid.into(), buf))
    }

    /// [`Doc::encode`] and sign the [`Doc`], returning the set of bytes, its
    /// corresponding Git [`Oid`] and the [`Signature`] over the [`Oid`].
-
    pub fn sign<G>(&self, signer: &G) -> Result<(git::Oid, Vec<u8>, Signature), DocError>
+
    pub fn sign<G>(&self, signer: &G) -> Result<(crate::Oid, Vec<u8>, Signature), DocError>
    where
        G: crypto::signature::Signer<crypto::Signature>,
    {
        let (oid, bytes) = self.encode()?;
-
        let sig = signer.sign(oid.as_bytes());
+
        let sig = signer.sign(oid.as_ref());

        Ok((oid, bytes, sig))
    }
@@ -889,7 +886,7 @@ impl Doc {
        &self,
        repo: &storage::git::Repository,
        signer: &Device<G>,
-
    ) -> Result<git::Oid, RepositoryError>
+
    ) -> Result<crate::Oid, RepositoryError>
    where
        G: crypto::signature::Signer<crypto::Signature>,
    {
@@ -1132,7 +1129,7 @@ mod test {
            &repo,
            "heartwood".try_into().unwrap(),
            "Radicle Heartwood Protocol & Stack",
-
            git::refname!("master"),
+
            git::fmt::refname!("master"),
            Visibility::default(),
            &delegate,
            &storage,
@@ -1157,10 +1154,13 @@ mod test {
        let remote = arbitrary::gen::<RemoteId>(1);
        let proj = arbitrary::gen::<RepoId>(1);
        let repo = storage.create(proj).unwrap();
-
        let oid = git2::Oid::from_str("2d52a53ce5e4f141148a5f770cfd3ead2d6a45b8").unwrap();
+
        let oid = crate::git2::Oid::from_str("2d52a53ce5e4f141148a5f770cfd3ead2d6a45b8").unwrap();

        let err = repo.identity_head_of(&remote).unwrap_err();
-
        matches!(err, git::ext::Error::NotFound(_));
+
        {
+
            use crate::git2::ErrorExt as _;
+
            assert!(err.is_not_found());
+
        }

        let err = Doc::load_at(oid.into(), &repo).unwrap_err();
        assert!(err.is_not_found());
@@ -1179,7 +1179,7 @@ mod test {
            &working,
            "heartwood".try_into().unwrap(),
            "Radicle Heartwood Protocol & Stack",
-
            git::refname!("master"),
+
            git::fmt::refname!("master"),
            Visibility::default(),
            &delegate,
            &storage,
modified crates/radicle/src/identity/doc/id.rs
@@ -1,10 +1,9 @@
use std::ops::Deref;
use std::{ffi::OsString, fmt, str::FromStr};

-
use git_ext::ref_format::{Component, RefString};
+
use crate::git::fmt::{Component, RefString};
use thiserror::Error;

-
use crate::git;
use crate::serde_ext;

/// Radicle identifier prefix.
@@ -12,10 +11,12 @@ pub const RAD_PREFIX: &str = "rad:";

#[derive(Error, Debug)]
pub enum IdError {
-
    #[error("invalid git object id: {0}")]
-
    InvalidOid(#[from] git2::Error),
+
    // #[error("invalid git object id: {0}")]
+
    // InvalidOid(#[from] crate::git2::Error),
    #[error(transparent)]
    Multibase(#[from] multibase::Error),
+
    #[error("invalid length")]
+
    Length(Vec<u8>),
}

/// A repository identifier.
@@ -29,7 +30,7 @@ pub struct RepoId(
        length(min = 5),
        example = &"rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
    ))]
-
    git::Oid,
+
    crate::Oid,
);

impl fmt::Display for RepoId {
@@ -68,14 +69,13 @@ impl RepoId {
    /// Eg. `z3XncAdkZjeK9mQS5Sdc4qhw98BUX`.
    ///
    pub fn canonical(&self) -> String {
-
        multibase::encode(multibase::Base::Base58Btc, self.0.as_bytes())
+
        multibase::encode(multibase::Base::Base58Btc, self.0.as_ref())
    }

    pub fn from_canonical(input: &str) -> Result<Self, IdError> {
        let (_, bytes) = multibase::decode(input)?;
-
        let array: git::Oid = bytes.as_slice().try_into()?;
-

-
        Ok(Self(array))
+
        let bytes: [u8; 20] = bytes.try_into().map_err(IdError::Length)?;
+
        Ok(Self(crate::Oid::from_sha1(bytes)))
    }
}

@@ -96,20 +96,20 @@ impl TryFrom<OsString> for RepoId {
    }
}

-
impl From<git::Oid> for RepoId {
-
    fn from(oid: git::Oid) -> Self {
+
impl From<crate::Oid> for RepoId {
+
    fn from(oid: crate::Oid) -> Self {
        Self(oid)
    }
}

-
impl From<git2::Oid> for RepoId {
-
    fn from(oid: git2::Oid) -> Self {
+
impl From<crate::git2::Oid> for RepoId {
+
    fn from(oid: crate::git2::Oid) -> Self {
        Self(oid.into())
    }
}

impl Deref for RepoId {
-
    type Target = git::Oid;
+
    type Target = crate::Oid;

    fn deref(&self) -> &Self::Target {
        &self.0
modified crates/radicle/src/identity/doc/update.rs
@@ -208,7 +208,8 @@ pub fn verify(raw: RawDoc) -> Result<Doc, error::DocVerification> {
        .map(|rcrefs| rcrefs.and_then(|c| project.map(|p| (c, p))))
    {
        Ok(Some((crefs, project))) => {
-
            let default = git::Qualified::from(git::lit::refs_heads(project.default_branch()));
+
            let default =
+
                git::fmt::Qualified::from(git::fmt::lit::refs_heads(project.default_branch()));
            let matches = crefs
                .raw_rules()
                .matches(&default)
@@ -300,7 +301,7 @@ mod test {
    #[test]
    fn test_cannot_include_default_branch_rule() {
        let raw = arbitrary::gen::<RawDoc>(1);
-
        let branch = git::Qualified::from(git::lit::refs_heads(
+
        let branch = git::fmt::Qualified::from(git::fmt::lit::refs_heads(
            raw.project().unwrap().default_branch(),
        ));
        let raw = super::payload(
@@ -333,7 +334,7 @@ mod test {
    #[test]
    fn test_default_branch_rule_exists_after_verification() {
        let raw = arbitrary::gen::<RawDoc>(1);
-
        let branch = git::Qualified::from(git::lit::refs_heads(
+
        let branch = git::fmt::Qualified::from(git::fmt::lit::refs_heads(
            raw.project().unwrap().default_branch(),
        ));
        let raw = super::payload(
modified crates/radicle/src/identity/doc/update/error.rs
@@ -1,7 +1,7 @@
use thiserror::Error;

use crate::git;
-
use crate::git::RefString;
+
use crate::git::fmt::RefString;
use crate::identity::{doc::PayloadId, Did, DocError};

#[derive(Debug, Error)]
@@ -31,7 +31,7 @@ pub enum DocVerification {
    #[error("incompatible payloads: The rule(s) xyz.radicle.crefs.rules.{matches:?} matches the value of xyz.radicle.project.defaultBranch ('{default}'). Possible resolutions: Change the name of the default branch or remove the rule(s).")]
    DisallowDefault {
        matches: Vec<String>,
-
        default: git::Qualified<'static>,
+
        default: git::fmt::Qualified<'static>,
    },
}

modified crates/radicle/src/lib.rs
@@ -7,7 +7,6 @@ pub extern crate radicle_crypto as crypto;

#[macro_use]
extern crate amplify;
-
extern crate radicle_git_ext as git_ext;

mod canonical;

@@ -16,6 +15,7 @@ pub mod cob;
pub mod collections;
pub mod explorer;
pub mod git;
+
pub mod git2;
pub mod identity;
pub mod io;
#[cfg(feature = "logger")]
@@ -36,6 +36,7 @@ pub mod web;
pub use cob::{external, issue, patch};
pub use node::Node;
pub use profile::Profile;
+
pub use radicle_oid::Oid;
pub use storage::git::Storage;

pub mod prelude {
modified crates/radicle/src/node.rs
@@ -931,7 +931,7 @@ impl FetchResult {
        }
    }

-
    pub fn find_updated(&self, name: &git::RefStr) -> Option<RefUpdate> {
+
    pub fn find_updated(&self, name: &git::fmt::RefStr) -> Option<RefUpdate> {
        let updated = match self {
            Self::Success { updated, .. } => Some(updated),
            _ => None,
modified crates/radicle/src/node/events.rs
@@ -9,10 +9,11 @@ use std::time;

use crossbeam_channel as chan;

-
use crate::git::{Oid, Qualified};
+
use crate::git::fmt::Qualified;
use crate::node;
use crate::prelude::*;
use crate::storage::{refs, RefUpdate};
+
use crate::Oid;

/// Maximum unconsumed events allowed per subscription.
pub const MAX_PENDING_EVENTS: usize = 8192;
modified crates/radicle/src/node/notifications.rs
@@ -7,7 +7,7 @@ use thiserror::Error;

use crate::cob;
use crate::cob::TypedId;
-
use crate::git::{BranchName, Qualified};
+
use crate::git::{fmt::Qualified, BranchName};
use crate::prelude::RepoId;
use crate::storage::{RefUpdate, RemoteId};

@@ -73,7 +73,7 @@ pub enum NotificationKindError {
    TypedId(#[from] cob::ParseIdentifierError),
    /// Invalid Git ref format.
    #[error("invalid ref format: {0}")]
-
    RefFormat(#[from] radicle_git_ext::ref_format::Error),
+
    RefFormat(#[from] crate::git::fmt::Error),
}

impl TryFrom<Qualified<'_>> for NotificationKind {
modified crates/radicle/src/node/notifications/store.rs
@@ -10,10 +10,12 @@ use sqlite as sql;
use thiserror::Error;

use crate::git;
-
use crate::git::{Oid, RefError, RefString};
+
use crate::git::fmt::RefString;
+
use crate::git::RefError;
use crate::prelude::RepoId;
use crate::sql::transaction;
use crate::storage::RefUpdate;
+
use crate::Oid;

use super::{
    Notification, NotificationId, NotificationKind, NotificationKindError, NotificationStatus,
@@ -40,7 +42,7 @@ pub enum Error {
    RefName(#[from] RefError),
    /// Invalid Git ref format.
    #[error("invalid ref format: {0}")]
-
    RefFormat(#[from] git_ext::ref_format::Error),
+
    RefFormat(#[from] crate::git::fmt::Error),
    /// Invalid notification kind.
    #[error("invalid notification kind: {0}")]
    NotificationKind(#[from] NotificationKindError),
@@ -379,7 +381,7 @@ mod parse {
                    })
                })
            })
-
            .unwrap_or(Ok(git::raw::Oid::zero().into()))?;
+
            .unwrap_or(Ok(crate::git2::Oid::zero().into()))?;
        let new = row
            .try_read::<Option<&str>, _>("new")?
            .map(|oid| {
@@ -390,7 +392,7 @@ mod parse {
                    })
                })
            })
-
            .unwrap_or(Ok(git::raw::Oid::zero().into()))?;
+
            .unwrap_or(Ok(crate::git2::Oid::zero().into()))?;
        let update = RefUpdate::from(RefString::try_from(refstr)?, old, new);
        let (namespace, qualified) = git::parse_ref(refstr)?;
        let timestamp = row.try_read::<i64, _>("timestamp")?;
@@ -414,10 +416,10 @@ mod parse {
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod test {
-
    use radicle_git_ext::ref_format::{qualified, refname};
+
    use crate::git::fmt::{qualified, refname};
+
    use crate::{cob, node::NodeId, test::arbitrary};

    use super::*;
-
    use crate::{cob, node::NodeId, test::arbitrary};

    #[test]
    fn test_clear() {
modified crates/radicle/src/node/policy/config.rs
@@ -37,7 +37,7 @@ pub enum NamespacesError {
        err: RepositoryError,
    },
    #[error(transparent)]
-
    Git(#[from] crate::git::raw::Error),
+
    Git(#[from] crate::git2::Error),
    #[error("could not find any followed nodes for {rid}")]
    NoFollowed { rid: RepoId },
}
modified crates/radicle/src/node/refs/store.rs
@@ -6,12 +6,13 @@ use localtime::LocalTime;
use sqlite as sql;
use thiserror::Error;

-
use crate::git::{Oid, Qualified};
+
use crate::git::fmt::Qualified;
use crate::node::Database;
use crate::node::NodeId;
use crate::prelude::RepoId;
use crate::storage;
use crate::storage::{ReadRepository, ReadStorage, RemoteRepository, RepositoryError};
+
use crate::Oid;

#[derive(Error, Debug)]
pub enum Error {
@@ -176,7 +177,7 @@ impl Store for Database {
#[allow(clippy::unwrap_used)]
mod test {
    use super::*;
-
    use crate::git::qualified;
+
    use crate::git::fmt::qualified;
    use crate::test::arbitrary;
    use localtime::{LocalDuration, LocalTime};

modified crates/radicle/src/node/seed.rs
@@ -3,7 +3,6 @@ pub use store::{Error, Store};

use localtime::LocalTime;

-
use crate::git;
use crate::node::KnownAddress;
use crate::prelude::NodeId;
use crate::storage::{refs::RefsAt, ReadRepository, RemoteId};
@@ -14,8 +13,7 @@ use crate::storage::{refs::RefsAt, ReadRepository, RemoteId};
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SyncedAt {
    /// Head of `rad/sigrefs`.
-
    #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
-
    pub oid: git_ext::Oid,
+
    pub oid: crate::Oid,
    /// When these refs were synced.
    #[serde(with = "crate::serde_ext::localtime::time")]
    #[cfg_attr(
@@ -27,7 +25,7 @@ pub struct SyncedAt {

impl SyncedAt {
    /// Load a new [`SyncedAt`] for the given remote.
-
    pub fn load<S: ReadRepository>(repo: &S, remote: RemoteId) -> Result<Self, git::ext::Error> {
+
    pub fn load<S: ReadRepository>(repo: &S, remote: RemoteId) -> Result<Self, crate::git2::Error> {
        let refs = RefsAt::new(repo, remote)?;
        let oid = refs.at;

@@ -35,7 +33,7 @@ impl SyncedAt {
    }

    /// Create a new [`SyncedAt`] given an OID, by looking up the timestamp in the repo.
-
    pub fn new<S: ReadRepository>(oid: git::ext::Oid, repo: &S) -> Result<Self, git::ext::Error> {
+
    pub fn new<S: ReadRepository>(oid: crate::Oid, repo: &S) -> Result<Self, crate::git2::Error> {
        let timestamp = repo.commit(oid)?.time();
        let timestamp = LocalTime::from_secs(timestamp.seconds() as u64);

modified crates/radicle/src/node/seed/store.rs
@@ -5,12 +5,12 @@ use localtime::LocalTime;
use sqlite as sql;
use thiserror::Error;

-
use crate::git::Oid;
use crate::node::address;
use crate::node::address::Store as _;
use crate::node::NodeId;
use crate::node::{seed::SyncedSeed, Database, SyncedAt};
use crate::prelude::{RepoId, Timestamp};
+
use crate::Oid;

#[derive(Error, Debug)]
pub enum Error {
modified crates/radicle/src/profile.rs
@@ -88,7 +88,7 @@ pub mod env {

    /// Get the configured pager program from the environment.
    pub fn pager() -> Option<String> {
-
        if let Ok(cfg) = git2::Config::open_default() {
+
        if let Ok(cfg) = crate::git2::Config::open_default() {
            if let Ok(pager) = cfg.get_string("core.pager") {
                return Some(pager);
            }
modified crates/radicle/src/rad.rs
@@ -22,13 +22,13 @@ use crate::storage::{WriteRepository, WriteStorage};
use crate::{identity, storage};

/// Name of the radicle storage remote.
-
pub static REMOTE_NAME: LazyLock<git::RefString> = LazyLock::new(|| git::refname!("rad"));
+
pub static REMOTE_NAME: LazyLock<git::fmt::RefString> = LazyLock::new(|| git::fmt::refname!("rad"));
/// Name of the radicle storage remote.
-
pub static REMOTE_COMPONENT: LazyLock<git::Component> =
+
pub static REMOTE_COMPONENT: LazyLock<git::fmt::Component> =
    LazyLock::new(|| git::fmt::name::component!("rad"));
/// Refname used for pushing patches.
-
pub static PATCHES_REFNAME: LazyLock<git::RefString> =
-
    LazyLock::new(|| git::refname!("refs/patches"));
+
pub static PATCHES_REFNAME: LazyLock<git::fmt::RefString> =
+
    LazyLock::new(|| git::fmt::refname!("refs/patches"));

#[derive(Error, Debug)]
pub enum InitError {
@@ -39,7 +39,7 @@ pub enum InitError {
    #[error("project payload: {0}")]
    ProjectPayload(String),
    #[error("git: {0}")]
-
    Git(#[from] git2::Error),
+
    Git(#[from] crate::git2::Error),
    #[error("i/o: {0}")]
    Io(#[from] io::Error),
    #[error("storage: {0}")]
@@ -48,7 +48,7 @@ pub enum InitError {

/// Initialize a new radicle project from a git repository.
pub fn init<G, S>(
-
    repo: &git2::Repository,
+
    repo: &crate::git2::Repository,
    name: ProjectName,
    description: &str,
    default_branch: BranchName,
@@ -96,11 +96,11 @@ where
}

fn init_configure<G>(
-
    repo: &git2::Repository,
+
    repo: &crate::git2::Repository,
    stored: &Repository,
    default_branch: &BranchName,
    url: &git::Url,
-
    identity: git::Oid,
+
    identity: crate::Oid,
    signer: &Device<G>,
) -> Result<SignedRefs<Verified>, InitError>
where
@@ -110,16 +110,16 @@ where

    git::configure_repository(repo)?;
    git::configure_remote(repo, &REMOTE_NAME, url, &url.clone().with_namespace(*pk))?;
-
    let branch = git::Qualified::from(git::fmt::lit::refs_heads(default_branch));
+
    let branch = git::fmt::Qualified::from(git::fmt::lit::refs_heads(default_branch));

    {
        // Push branch to storage.
        let stored = dunce::canonicalize(stored.path())?.display().to_string();

        // Pushes to default branch to the namespace of the `signer`.
-
        let pushspec = git::Refspec {
+
        let pushspec = git::fmt::refspec::Refspec {
            src: branch.clone(),
-
            dst: branch.with_namespace(git::Component::from(pk)),
+
            dst: branch.with_namespace(git::fmt::Component::from(pk)),
            force: false,
        }
        .to_string();
@@ -128,8 +128,8 @@ where
    }

    // N.b. we need to create the remote branch for the default branch
-
    let rad_remote =
-
        git::Qualified::from(git::lit::refs_remotes(&*REMOTE_COMPONENT)).join(default_branch);
+
    let rad_remote = git::fmt::Qualified::from(git::fmt::lit::refs_remotes(&*REMOTE_COMPONENT))
+
        .join(default_branch);
    let oid = repo.refname_to_id(branch.as_str())?;
    repo.reference(
        rad_remote.as_str(),
@@ -152,7 +152,7 @@ where
#[derive(Error, Debug)]
pub enum ForkError {
    #[error("git: {0}")]
-
    Git(#[from] git2::Error),
+
    Git(#[from] crate::git2::Error),
    #[error("storage: {0}")]
    Storage(#[from] storage::Error),
    #[error("payload: {0}")]
@@ -215,7 +215,7 @@ where

    raw.reference(
        &canonical_branch.with_namespace(me.into()),
-
        *canonical_head,
+
        canonical_head.into(),
        true,
        &format!("creating default branch for {me}"),
    )?;
@@ -235,7 +235,7 @@ pub enum CheckoutError {
        stdout: String,
    },
    #[error("git: {0}")]
-
    Git(#[from] git2::Error),
+
    Git(#[from] crate::git2::Error),
    #[error("payload: {0}")]
    Payload(#[from] doc::PayloadError),
    #[error("repository `{0}` was not found in storage")]
@@ -252,19 +252,19 @@ pub fn checkout<P: AsRef<Path>, S: storage::ReadStorage>(
    path: P,
    storage: &S,
    bare: bool,
-
) -> Result<git2::Repository, CheckoutError> {
+
) -> Result<crate::git2::Repository, CheckoutError> {
    // TODO: Decide on whether we can use `clone_local`
    // TODO: Look into sharing object databases.
    let doc = storage.get(proj)?.ok_or(CheckoutError::NotFound(proj))?;
    let project = doc.project()?;

-
    let mut opts = git2::RepositoryInitOptions::new();
+
    let mut opts = crate::git2::RepositoryInitOptions::new();
    opts.no_reinit(true)
        .external_template(false)
        .description(project.description())
        .bare(bare);

-
    let repo = git2::Repository::init_opts(path.as_ref(), &opts)?;
+
    let repo = crate::git2::Repository::init_opts(path.as_ref(), &opts)?;
    let url = git::Url::from(proj);

    // Configure repository for radicle.
@@ -280,10 +280,10 @@ pub fn checkout<P: AsRef<Path>, S: storage::ReadStorage>(
    {
        // Fetch remote head to working copy.

-
        let fetchspec = git::Refspec {
-
            src: git::refspec::pattern!("refs/heads/*"),
-
            dst: git::Qualified::from(git::lit::refs_remotes(&*REMOTE_NAME))
-
                .to_pattern(git::refspec::STAR)
+
        let fetchspec = git::fmt::refspec::Refspec {
+
            src: git::fmt::refspec::pattern!("refs/heads/*"),
+
            dst: git::fmt::Qualified::from(git::fmt::lit::refs_remotes(&*REMOTE_NAME))
+
                .to_pattern(git::fmt::refspec::STAR)
                .into_patternstring(),
            force: false,
        }
@@ -334,7 +334,7 @@ pub fn checkout<P: AsRef<Path>, S: storage::ReadStorage>(
#[derive(Error, Debug)]
pub enum RemoteError {
    #[error("git: {0}")]
-
    Git(#[from] git2::Error),
+
    Git(#[from] crate::git2::Error),
    #[error("invalid remote url: {0}")]
    Url(#[from] transport::local::UrlError),
    #[error("invalid utf-8 string")]
@@ -346,9 +346,11 @@ pub enum RemoteError {
}

/// Get the radicle ("rad") remote of a repository, and return the associated project id.
-
pub fn remote(repo: &git2::Repository) -> Result<(git2::Remote<'_>, RepoId), RemoteError> {
+
pub fn remote(
+
    repo: &crate::git2::Repository,
+
) -> Result<(crate::git2::Remote<'_>, RepoId), RemoteError> {
    let remote = repo.find_remote(&REMOTE_NAME).map_err(|e| {
-
        if e.code() == git2::ErrorCode::NotFound {
+
        if e.code() == crate::git2::ErrorCode::NotFound {
            RemoteError::NotFound(REMOTE_NAME.to_string())
        } else {
            RemoteError::from(e)
@@ -361,9 +363,9 @@ pub fn remote(repo: &git2::Repository) -> Result<(git2::Remote<'_>, RepoId), Rem
}

/// Delete the radicle ("rad") remote of a repository.
-
pub fn remove_remote(repo: &git2::Repository) -> Result<(), RemoteError> {
+
pub fn remove_remote(repo: &crate::git2::Repository) -> Result<(), RemoteError> {
    repo.remote_delete(&REMOTE_NAME).map_err(|e| {
-
        if e.code() == git2::ErrorCode::NotFound {
+
        if e.code() == crate::git2::ErrorCode::NotFound {
            RemoteError::NotFound(REMOTE_NAME.to_string())
        } else {
            RemoteError::from(e)
@@ -379,7 +381,7 @@ pub enum CwdError {

    #[error("Detection failed (git: '{git}', jj: '{jj}')")]
    Detection {
-
        git: git2::Error,
+
        git: crate::git2::Error,
        jj: JujutsuGitRootError,
    },
}
@@ -394,7 +396,7 @@ pub enum CwdError {
/// This function should only perform read operations since we do not
/// want to modify the wrong repository in the case that it found a
/// Git repository that is not a Radicle repository.
-
pub fn cwd() -> Result<(git2::Repository, RepoId), CwdError> {
+
pub fn cwd() -> Result<(crate::git2::Repository, RepoId), CwdError> {
    let repo =
        repo().or_else(|git| repo_jj_git_root().map_err(|jj| CwdError::Detection { git, jj }))?;

@@ -403,23 +405,23 @@ pub fn cwd() -> Result<(git2::Repository, RepoId), CwdError> {
}

/// Get the repository of project in specified directory
-
pub fn at(path: impl AsRef<Path>) -> Result<(git2::Repository, RepoId), RemoteError> {
-
    let repo = git2::Repository::open(path)?;
+
pub fn at(path: impl AsRef<Path>) -> Result<(crate::git2::Repository, RepoId), RemoteError> {
+
    let repo = crate::git2::Repository::open(path)?;
    let (_, id) = remote(&repo)?;

    Ok((repo, id))
}

/// Get the current Git repository.
-
pub fn repo() -> Result<git2::Repository, git2::Error> {
-
    let mut flags = git2::RepositoryOpenFlags::empty();
+
pub fn repo() -> Result<crate::git2::Repository, crate::git2::Error> {
+
    let mut flags = crate::git2::RepositoryOpenFlags::empty();
    // Allow to search upwards.
-
    flags.set(git2::RepositoryOpenFlags::NO_SEARCH, false);
+
    flags.set(crate::git2::RepositoryOpenFlags::NO_SEARCH, false);
    // Allow to use `GIT_DIR` env.
-
    flags.set(git2::RepositoryOpenFlags::FROM_ENV, true);
+
    flags.set(crate::git2::RepositoryOpenFlags::FROM_ENV, true);

    let ceilings: &[&str] = &[];
-
    let repo = git2::Repository::open_ext(Path::new("."), flags, ceilings)?;
+
    let repo = crate::git2::Repository::open_ext(Path::new("."), flags, ceilings)?;

    Ok(repo)
}
@@ -427,7 +429,7 @@ pub fn repo() -> Result<git2::Repository, git2::Error> {
#[derive(Error, Debug)]
pub enum JujutsuGitRootError {
    #[error("git: {0}")]
-
    Git(#[from] git2::Error),
+
    Git(#[from] crate::git2::Error),

    #[error("i/o: {0}")]
    Io(#[from] io::Error),
@@ -437,7 +439,7 @@ pub enum JujutsuGitRootError {
}

/// Get the Git repo underlying the current Jujutsu repository.
-
pub fn repo_jj_git_root() -> Result<git2::Repository, JujutsuGitRootError> {
+
pub fn repo_jj_git_root() -> Result<crate::git2::Repository, JujutsuGitRootError> {
    let output = std::process::Command::new("jj")
        .args(["git", "root"])
        .output()?;
@@ -449,21 +451,21 @@ pub fn repo_jj_git_root() -> Result<git2::Repository, JujutsuGitRootError> {
    }

    let path = std::path::PathBuf::from(String::from_utf8_lossy(&output.stdout).to_string().trim());
-
    Ok(git2::Repository::open(path)?)
+
    Ok(crate::git2::Repository::open(path)?)
}

/// Setup patch upstream branch such that `git push` updates the patch.
pub fn setup_patch_upstream<'a>(
    patch: &ObjectId,
-
    patch_head: git::Oid,
-
    working: &'a git::raw::Repository,
-
    remote: &git::RefString,
+
    patch_head: crate::Oid,
+
    working: &'a crate::git2::Repository,
+
    remote: &git::fmt::RefString,
    force: bool,
-
) -> Result<Option<git::raw::Branch<'a>>, git::ext::Error> {
+
) -> Result<Option<crate::git2::Branch<'a>>, crate::git2::Error> {
    let head = working.head()?;

    // Don't do anything in case we're not on the patch branch.
-
    if head.peel_to_commit()?.id() != *patch_head {
+
    if patch_head != head.peel_to_commit()?.id() {
        return Ok(None);
    }
    let Ok(r) = head.resolve() else {
@@ -475,18 +477,18 @@ pub fn setup_patch_upstream<'a>(
        return Ok(None);
    }

-
    let branch = git::raw::Branch::wrap(r);
+
    let branch = crate::git2::Branch::wrap(r);

    // Only set the upstream if it's missing or `force` is `true`
    if branch.upstream().is_ok() && !force {
        return Ok(None);
    }

-
    let name: Option<git::RefString> = branch.name()?.and_then(|b| b.try_into().ok());
+
    let name: Option<git::fmt::RefString> = branch.name()?.and_then(|b| b.try_into().ok());
    let remote_branch = git::refs::workdir::patch_upstream(patch);
    let remote_branch = working.reference(
        &remote_branch,
-
        *patch_head,
+
        patch_head.into(),
        true,
        "Create remote tracking branch for patch",
    )?;
@@ -497,7 +499,7 @@ pub fn setup_patch_upstream<'a>(
            git::set_upstream(working, remote, name.as_str(), git::refs::patch(patch))?;
        }
    }
-
    Ok(Some(git::raw::Branch::wrap(remote_branch)))
+
    Ok(Some(crate::git2::Branch::wrap(remote_branch)))
}

#[cfg(test)]
@@ -507,12 +509,12 @@ mod tests {

    use pretty_assertions::assert_eq;

-
    use crate::git::{name::component, qualified};
    use crate::identity::Did;
    use crate::storage::git::transport;
    use crate::storage::git::Storage;
    use crate::storage::{ReadStorage, RemoteRepository as _};
    use crate::test::fixtures;
+
    use git::fmt::{name::component, qualified};

    use super::*;

@@ -530,7 +532,7 @@ mod tests {
            &repo,
            "acme".try_into().unwrap(),
            "Acme's repo",
-
            git::refname!("master"),
+
            git::fmt::refname!("master"),
            Visibility::default(),
            &signer,
            &storage,
@@ -552,19 +554,19 @@ mod tests {

        // Test canonical refs.
        assert_eq!(refs.head(component!("master")).unwrap(), head);
-
        assert_eq!(project_repo.raw().refname_to_id("HEAD").unwrap(), *head);
+
        assert_eq!(head, project_repo.raw().refname_to_id("HEAD").unwrap());
        assert_eq!(
+
            head,
            project_repo
                .raw()
                .refname_to_id("refs/heads/master")
                .unwrap(),
-
            *head
        );

        assert_eq!(remotes[&public_key].refs, refs);
        assert_eq!(project.name(), "acme");
        assert_eq!(project.description(), "Acme's repo");
-
        assert_eq!(project.default_branch(), &git::refname!("master"));
+
        assert_eq!(project.default_branch(), &git::fmt::refname!("master"));
        assert_eq!(doc.delegates().first(), &Did::from(public_key));
    }

@@ -585,7 +587,7 @@ mod tests {
            &original,
            "acme".try_into().unwrap(),
            "Acme's repo",
-
            git::refname!("master"),
+
            git::fmt::refname!("master"),
            Visibility::default(),
            &alice,
            &storage,
@@ -621,7 +623,7 @@ mod tests {
            &original,
            "acme".try_into().unwrap(),
            "Acme's repo",
-
            git::refname!("master"),
+
            git::fmt::refname!("master"),
            Visibility::default(),
            &signer,
            &storage,
modified crates/radicle/src/schemars_ext.rs
@@ -90,23 +90,9 @@ pub(crate) mod localtime {
}

pub(crate) mod git {
-
    use super::*;
-

-
    /// See [`crate::git::Oid`]
-
    /// See [`::git_ext::Oid`]
-
    /// See [`::git2::Oid`]
-
    ///
-
    /// A Git Object Identifier in hexadecimal encoding.
-
    #[derive(JsonSchema)]
-
    #[schemars(
-
        remote = "git2::Oid",
-
        description = "A Git Object Identifier (SHA-1 or SHA-256 hash) in hexadecimal encoding."
-
    )]
-
    pub(crate) struct Oid(
-
        #[schemars(regex(pattern = r"^([0-9a-fA-F]{64}|[0-9a-fA-F]{40})$"))] String,
-
    );
-

-
    /// See [`crate::git::RefString`]
-
    #[derive(JsonSchema)]
-
    pub(crate) struct RefString(String);
+
    pub(crate) mod fmt {
+
        /// See [`crate::git::fmt::RefString`]
+
        #[derive(schemars::JsonSchema)]
+
        pub(crate) struct RefString(String);
+
    }
}
modified crates/radicle/src/storage.rs
@@ -10,14 +10,16 @@ use nonempty::NonEmpty;
use serde::{Deserialize, Serialize};
use thiserror::Error;

+
pub use crate::Oid;
use crypto::{PublicKey, Unverified, Verified};
pub use git::{Validation, Validations};
-
pub use radicle_git_ext::Oid;

use crate::cob;
use crate::collections::RandomMap;
-
use crate::git::{canonical, ext as git_ext};
-
use crate::git::{refspec::Refspec, PatternString, Qualified, RefError, RefStr, RefString};
+
use crate::git::canonical;
+
use crate::git::fmt::{refspec::PatternString, refspec::Refspec, Qualified, RefStr, RefString};
+
use crate::git::RefError;
+
use crate::git2::ErrorExt as _;
use crate::identity::{doc, Did, PayloadError};
use crate::identity::{Doc, DocAt, DocError};
use crate::identity::{Identity, RepoId};
@@ -26,10 +28,10 @@ use crate::node::SyncedAt;
use crate::storage::git::NAMESPACES_GLOB;
use crate::storage::refs::Refs;

-
use self::git::UserInfo;
use self::refs::{RefsAt, SignedRefs};
+
use crate::git::UserInfo;

-
pub type BranchName = git::RefString;
+
pub type BranchName = crate::git::fmt::RefString;

/// Basic repository information.
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -68,7 +70,9 @@ impl Namespaces {
            Namespaces::Followed(pks) => pks
                .iter()
                .map(|pk| {
-
                    let ns = pk.to_namespace().with_pattern(git::refspec::STAR);
+
                    let ns = pk
+
                        .to_namespace()
+
                        .with_pattern(crate::git::fmt::refspec::STAR);
                    Refspec {
                        src: ns.clone(),
                        dst: ns,
@@ -113,9 +117,7 @@ pub enum RepositoryError {
    #[error(transparent)]
    Payload(#[from] PayloadError),
    #[error(transparent)]
-
    Git(#[from] git::raw::Error),
-
    #[error(transparent)]
-
    GitExt(#[from] git_ext::Error),
+
    Git(#[from] crate::git2::Error),
    #[error(transparent)]
    Quorum(#[from] canonical::error::QuorumError),
    #[error(transparent)]
@@ -134,8 +136,7 @@ impl RepositoryError {
    pub fn is_not_found(&self) -> bool {
        match self {
            Self::Storage(e) if e.is_not_found() => true,
-
            Self::Git(e) if git_ext::is_not_found_err(e) => true,
-
            Self::GitExt(git_ext::Error::NotFound(_)) => true,
+
            Self::Git(e) if e.is_not_found() => true,
            _ => false,
        }
    }
@@ -153,9 +154,7 @@ pub enum Error {
    #[error(transparent)]
    Refs(#[from] refs::Error),
    #[error("git: {0}")]
-
    Git(#[from] git2::Error),
-
    #[error("git: {0}")]
-
    Ext(#[from] git::ext::Error),
+
    Git(#[from] crate::git2::Error),
    #[error("invalid repository identifier {0:?}")]
    InvalidId(std::ffi::OsString),
    #[error("i/o: {0}")]
@@ -167,7 +166,7 @@ impl Error {
    pub fn is_not_found(&self) -> bool {
        match self {
            Self::Io(e) if e.kind() == io::ErrorKind::NotFound => true,
-
            Self::Git(e) if git::ext::is_not_found_err(e) => true,
+
            Self::Git(e) if e.is_not_found() => true,
            Self::Doc(e) if e.is_not_found() => true,
            _ => false,
        }
@@ -179,7 +178,7 @@ impl Error {
#[allow(clippy::large_enum_variant)]
pub enum FetchError {
    #[error("git: {0}")]
-
    Git(#[from] git2::Error),
+
    Git(#[from] crate::git2::Error),
    #[error("i/o: {0}")]
    Io(#[from] io::Error),
    #[error(transparent)]
@@ -204,36 +203,31 @@ pub enum RefUpdate {
    Updated {
        #[cfg_attr(
            feature = "schemars",
-
            schemars(with = "crate::schemars_ext::git::RefString")
+
            schemars(with = "crate::schemars_ext::git::fmt::RefString")
        )]
        name: RefString,
-
        #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
        old: Oid,
-
        #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
        new: Oid,
    },
    Created {
        #[cfg_attr(
            feature = "schemars",
-
            schemars(with = "crate::schemars_ext::git::RefString")
+
            schemars(with = "crate::schemars_ext::git::fmt::RefString")
        )]
        name: RefString,
-
        #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
        oid: Oid,
    },
    Deleted {
        #[cfg_attr(
            feature = "schemars",
-
            schemars(with = "crate::schemars_ext::git::RefString")
+
            schemars(with = "crate::schemars_ext::git::fmt::RefString")
        )]
        name: RefString,
-
        #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
        oid: Oid,
    },
    Skipped {
        #[cfg_attr(feature = "schemars", schemars(with = "String"))]
        name: RefString,
-
        #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
        oid: Oid,
    },
}
@@ -503,16 +497,20 @@ pub trait ReadRepository: Sized + ValidateRepository {
    fn id(&self) -> RepoId;

    /// Returns `true` if there are no references in the repository.
-
    fn is_empty(&self) -> Result<bool, git2::Error>;
+
    fn is_empty(&self) -> Result<bool, crate::git2::Error>;

    /// The [`Path`] to the git repository.
    fn path(&self) -> &Path;

    /// Get a blob in this repository at the given commit and path.
-
    fn blob_at<P: AsRef<Path>>(&self, commit: Oid, path: P) -> Result<git2::Blob, git_ext::Error>;
+
    fn blob_at<P: AsRef<Path>>(
+
        &self,
+
        commit: Oid,
+
        path: P,
+
    ) -> Result<crate::git2::Blob, crate::git2::Error>;

    /// Get a blob in this repository, given its id.
-
    fn blob(&self, oid: Oid) -> Result<git2::Blob, git_ext::Error>;
+
    fn blob(&self, oid: Oid) -> Result<crate::git2::Blob, crate::git2::Error>;

    /// Get the head of this repository.
    ///
@@ -536,7 +534,7 @@ pub trait ReadRepository: Sized + ValidateRepository {
    fn identity_head(&self) -> Result<Oid, RepositoryError>;

    /// Get the identity head of a specific remote.
-
    fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, git::ext::Error>;
+
    fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, crate::git2::Error>;

    /// Get the root commit of the canonical identity branch.
    fn identity_root(&self) -> Result<Oid, RepositoryError>;
@@ -572,28 +570,28 @@ pub trait ReadRepository: Sized + ValidateRepository {
        &self,
        remote: &RemoteId,
        reference: &Qualified,
-
    ) -> Result<git2::Reference, git_ext::Error>;
+
    ) -> Result<crate::git2::Reference, crate::git2::Error>;

-
    /// Get the [`git2::Commit`] found using its `oid`.
+
    /// Get the [`crate::git2::Commit`] found using its `oid`.
    ///
    /// Returns `Err` if the commit did not exist.
-
    fn commit(&self, oid: Oid) -> Result<git2::Commit, git::ext::Error>;
+
    fn commit(&self, oid: Oid) -> Result<crate::git2::Commit, crate::git2::Error>;

    /// Perform a revision walk of a commit history starting from the given head.
-
    fn revwalk(&self, head: Oid) -> Result<git2::Revwalk, git2::Error>;
+
    fn revwalk(&self, head: Oid) -> Result<crate::git2::Revwalk, crate::git2::Error>;

    /// Check if the underlying ODB contains the given `oid`.
-
    fn contains(&self, oid: Oid) -> Result<bool, git2::Error>;
+
    fn contains(&self, oid: Oid) -> Result<bool, crate::git2::Error>;

    /// Check whether the given commit is an ancestor of another commit.
-
    fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, git::ext::Error>;
+
    fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, crate::git2::Error>;

    /// Get the object id of a reference under the given remote.
    fn reference_oid(
        &self,
        remote: &RemoteId,
        reference: &Qualified,
-
    ) -> Result<Oid, git::raw::Error>;
+
    ) -> Result<Oid, crate::git2::Error>;

    /// Get all references of the given remote.
    fn references_of(&self, remote: &RemoteId) -> Result<Refs, Error>;
@@ -605,8 +603,8 @@ pub trait ReadRepository: Sized + ValidateRepository {
    /// commit pointed to by the tag is returned, and not the [`Oid`] of the tag itsself.
    fn references_glob(
        &self,
-
        pattern: &git::PatternStr,
-
    ) -> Result<Vec<(Qualified, Oid)>, git::ext::Error>;
+
        pattern: &crate::git::fmt::refspec::PatternStr,
+
    ) -> Result<Vec<(Qualified, Oid)>, crate::git2::Error>;

    /// Get repository delegates.
    fn delegates(&self) -> Result<NonEmpty<Did>, RepositoryError> {
@@ -627,7 +625,7 @@ pub trait ReadRepository: Sized + ValidateRepository {
    fn identity_doc_at(&self, head: Oid) -> Result<DocAt, DocError>;

    /// Get the merge base of two commits.
-
    fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, git::ext::Error>;
+
    fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, crate::git2::Error>;
}

/// Access the remotes of a repository.
@@ -692,7 +690,7 @@ pub trait WriteRepository: ReadRepository + SignRepository {
    /// Set the user info of the Git repository.
    fn set_user(&self, info: &UserInfo) -> Result<(), Error>;
    /// Get the underlying git repository.
-
    fn raw(&self) -> &git2::Repository;
+
    fn raw(&self) -> &crate::git2::Repository;
}

/// Allows signing refs.
modified crates/radicle/src/storage/git.rs
@@ -12,6 +12,7 @@ use crypto::Verified;
use tempfile::TempDir;

use crate::git::canonical::Quorum;
+
use crate::git2::ErrorExt as _;
use crate::identity::crefs::GetCanonicalRefs as _;
use crate::identity::doc::DocError;
use crate::identity::{CanonicalRefs, Doc, DocAt, RepoId};
@@ -24,24 +25,26 @@ use crate::storage::{
    ReadRepository, ReadStorage, Remote, Remotes, RepositoryInfo, SetHead, SignRepository,
    WriteRepository, WriteStorage,
};
-
use crate::{git, node};
+
use crate::{git, node, Oid};

-
pub use crate::git::{
-
    ext, raw, refname, refspec, Oid, PatternStr, Qualified, RefError, RefString, UserInfo,
+
use crate::git::fmt::{
+
    refname, refspec, refspec::PatternStr, refspec::PatternString, Qualified, RefString,
};
+
use crate::git::RefError;
+
use crate::git::UserInfo;
pub use crate::storage::{Error, RepositoryError};

use super::refs::RefsAt;
use super::{RemoteId, RemoteRepository, ValidateRepository};

-
pub static NAMESPACES_GLOB: LazyLock<git::refspec::PatternString> =
-
    LazyLock::new(|| git::refspec::pattern!("refs/namespaces/*"));
+
pub static NAMESPACES_GLOB: LazyLock<PatternString> =
+
    LazyLock::new(|| git::fmt::refspec::pattern!("refs/namespaces/*"));
pub static SIGREFS_GLOB: LazyLock<refspec::PatternString> =
-
    LazyLock::new(|| git::refspec::pattern!("refs/namespaces/*/rad/sigrefs"));
-
pub static CANONICAL_IDENTITY: LazyLock<git::Qualified> = LazyLock::new(|| {
-
    git::Qualified::from_components(
-
        git::name::component!("rad"),
-
        git::name::component!("id"),
+
    LazyLock::new(|| git::fmt::refspec::pattern!("refs/namespaces/*/rad/sigrefs"));
+
pub static CANONICAL_IDENTITY: LazyLock<git::fmt::Qualified> = LazyLock::new(|| {
+
    git::fmt::Qualified::from_components(
+
        git::fmt::name::component!("rad"),
+
        git::fmt::name::component!("id"),
        None,
    )
});
@@ -49,15 +52,15 @@ pub static CANONICAL_IDENTITY: LazyLock<git::Qualified> = LazyLock::new(|| {
/// A parsed Git reference.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ref {
-
    pub oid: git::Oid,
+
    pub oid: crate::Oid,
    pub name: RefString,
    pub namespace: Option<RemoteId>,
}

-
impl TryFrom<git2::Reference<'_>> for Ref {
+
impl TryFrom<crate::git2::Reference<'_>> for Ref {
    type Error = RefError;

-
    fn try_from(r: git2::Reference) -> Result<Self, Self::Error> {
+
    fn try_from(r: crate::git2::Reference) -> Result<Self, Self::Error> {
        let name = r.name().ok_or(RefError::InvalidName)?;
        let (namespace, name) = match git::parse_ref_namespaced::<RemoteId>(name) {
            Ok((namespace, refname)) => (Some(namespace), refname.to_ref_string()),
@@ -282,7 +285,7 @@ pub struct Repository {
    /// The repository identifier (RID).
    pub id: RepoId,
    /// The backing Git repository.
-
    pub backend: git2::Repository,
+
    pub backend: crate::git2::Repository,
}

impl git::canonical::effects::Ancestry for Repository {
@@ -377,12 +380,12 @@ pub enum Validation {
impl Repository {
    /// Open an existing repository.
    pub fn open<P: AsRef<Path>>(path: P, id: RepoId) -> Result<Self, RepositoryError> {
-
        let backend = git2::Repository::open_ext(
+
        let backend = crate::git2::Repository::open_ext(
            path.as_ref(),
-
            git2::RepositoryOpenFlags::empty()
-
                | git2::RepositoryOpenFlags::BARE
-
                | git2::RepositoryOpenFlags::NO_DOTGIT
-
                | git2::RepositoryOpenFlags::NO_SEARCH,
+
            crate::git2::RepositoryOpenFlags::empty()
+
                | crate::git2::RepositoryOpenFlags::BARE
+
                | crate::git2::RepositoryOpenFlags::NO_DOTGIT
+
                | crate::git2::RepositoryOpenFlags::NO_SEARCH,
            &[] as &[&std::ffi::OsStr],
        )?;

@@ -391,9 +394,9 @@ impl Repository {

    /// Create a new repository.
    pub fn create<P: AsRef<Path>>(path: P, id: RepoId, info: &UserInfo) -> Result<Self, Error> {
-
        let backend = git2::Repository::init_opts(
+
        let backend = crate::git2::Repository::init_opts(
            &path,
-
            git2::RepositoryInitOptions::new()
+
            crate::git2::RepositoryInitOptions::new()
                .bare(true)
                .no_reinit(true)
                .external_template(false),
@@ -442,9 +445,9 @@ impl Repository {
                continue;
            }

-
            let glob = git::refname!("refs/namespaces")
-
                .join(git::Component::from(&id))
-
                .with_pattern(git::refspec::STAR);
+
            let glob = git::fmt::refname!("refs/namespaces")
+
                .join(git::fmt::Component::from(&id))
+
                .with_pattern(git::fmt::refspec::STAR);
            let refs = match self.references_glob(&glob) {
                Ok(refs) => refs,
                Err(e) => {
@@ -472,7 +475,7 @@ impl Repository {
        doc: &Doc,
        storage: &S,
        signer: &Device<G>,
-
    ) -> Result<(Self, git::Oid), RepositoryError>
+
    ) -> Result<(Self, crate::Oid), RepositoryError>
    where
        G: crypto::signature::Signer<crypto::Signature>,
        S: WriteStorage,
@@ -482,7 +485,7 @@ impl Repository {
        let repo = Self::create(paths::repository(storage, &id), id, storage.info())?;
        let oid = repo.backend.blob(&doc_bytes)?; // Store document blob in repository.

-
        debug_assert_eq!(oid, *doc_oid);
+
        debug_assert_eq!(doc_oid, oid);

        let commit = doc.init(&repo, signer)?;

@@ -503,7 +506,7 @@ impl Repository {
    /// Iterate over all references.
    pub fn references(
        &self,
-
    ) -> Result<impl Iterator<Item = Result<Ref, refs::Error>> + '_, git2::Error> {
+
    ) -> Result<impl Iterator<Item = Result<Ref, refs::Error>> + '_, crate::git2::Error> {
        let refs = self
            .backend
            .references()?
@@ -536,7 +539,7 @@ impl Repository {

    pub fn remote_ids(
        &self,
-
    ) -> Result<impl Iterator<Item = Result<RemoteId, refs::Error>> + '_, git2::Error> {
+
    ) -> Result<impl Iterator<Item = Result<RemoteId, refs::Error>> + '_, crate::git2::Error> {
        let iter = self.backend.references_glob(SIGREFS_GLOB.as_str())?.map(
            |reference| -> Result<RemoteId, refs::Error> {
                let r = reference?;
@@ -553,7 +556,7 @@ impl Repository {
        &self,
    ) -> Result<
        impl Iterator<Item = Result<(RemoteId, Remote<Verified>), refs::Error>> + '_,
-
        git2::Error,
+
        crate::git2::Error,
    > {
        let remotes =
            self.backend
@@ -649,7 +652,7 @@ impl ReadRepository for Repository {
        self.id
    }

-
    fn is_empty(&self) -> Result<bool, git2::Error> {
+
    fn is_empty(&self) -> Result<bool, crate::git2::Error> {
        Ok(self.remotes()?.next().is_none())
    }

@@ -657,65 +660,68 @@ impl ReadRepository for Repository {
        self.backend.path()
    }

-
    fn blob_at<P: AsRef<Path>>(&self, commit: Oid, path: P) -> Result<git2::Blob, git::Error> {
-
        let commit = self.backend.find_commit(*commit)?;
+
    fn blob_at<P: AsRef<Path>>(
+
        &self,
+
        commit_id: Oid,
+
        path: P,
+
    ) -> Result<crate::git2::Blob, crate::git2::Error> {
+
        let commit = self.backend.find_commit(commit_id.into())?;
        let tree = commit.tree()?;
        let entry = tree.get_path(path.as_ref())?;
        let obj = entry.to_object(&self.backend)?;
-
        let blob = obj.into_blob().map_err(|_| {
-
            git::Error::NotFound(git::NotFound::NoSuchBlob(
-
                path.as_ref().display().to_string(),
-
            ))
-
        })?;
+
        let blob = obj.into_blob().map_err(|_|
+
            crate::git2::Error::new(
+
                crate::git2::ErrorCode::NotFound,
+
                crate::git2::ErrorClass::None,
+
                format!("Path '{}' in tree of commit {commit_id} was expected to be a blob, but is not.", path.as_ref().display()),
+
            )
+
        )?;

        Ok(blob)
    }

-
    fn blob(&self, oid: Oid) -> Result<git2::Blob, git::Error> {
-
        self.backend.find_blob(oid.into()).map_err(git::Error::from)
+
    fn blob(&self, oid: Oid) -> Result<crate::git2::Blob, crate::git2::Error> {
+
        self.backend.find_blob(oid.into())
    }

    fn reference(
        &self,
        remote: &RemoteId,
-
        name: &git::Qualified,
-
    ) -> Result<git2::Reference, git::Error> {
+
        name: &git::fmt::Qualified,
+
    ) -> Result<crate::git2::Reference, crate::git2::Error> {
        let name = name.with_namespace(remote.into());
-
        self.backend.find_reference(&name).map_err(git::Error::from)
+
        self.backend.find_reference(&name)
    }

    fn reference_oid(
        &self,
        remote: &RemoteId,
-
        reference: &git::Qualified,
-
    ) -> Result<Oid, git::raw::Error> {
+
        reference: &git::fmt::Qualified,
+
    ) -> Result<Oid, crate::git2::Error> {
        let name = reference.with_namespace(remote.into());
        let oid = self.backend.refname_to_id(&name)?;

        Ok(oid.into())
    }

-
    fn commit(&self, oid: Oid) -> Result<git2::Commit, git::Error> {
-
        self.backend
-
            .find_commit(oid.into())
-
            .map_err(git::Error::from)
+
    fn commit(&self, oid: Oid) -> Result<crate::git2::Commit, crate::git2::Error> {
+
        self.backend.find_commit(oid.into())
    }

-
    fn revwalk(&self, head: Oid) -> Result<git2::Revwalk, git2::Error> {
+
    fn revwalk(&self, head: Oid) -> Result<crate::git2::Revwalk, crate::git2::Error> {
        let mut revwalk = self.backend.revwalk()?;
        revwalk.push(head.into())?;

        Ok(revwalk)
    }

-
    fn contains(&self, oid: Oid) -> Result<bool, raw::Error> {
+
    fn contains(&self, oid: Oid) -> Result<bool, crate::git2::Error> {
        self.backend.odb().map(|odb| odb.exists(oid.into()))
    }

-
    fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, git::Error> {
+
    fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, crate::git2::Error> {
        self.backend
            .graph_descendant_of(head.into(), ancestor.into())
-
            .map_err(git::Error::from)
    }

    fn references_of(&self, remote: &RemoteId) -> Result<Refs, Error> {
@@ -731,12 +737,14 @@ impl ReadRepository for Repository {
            let oid = e.resolve()?.target().ok_or(Error::InvalidRef)?;
            let (_, category, _, _) = refname.non_empty_components();

+
            use git::fmt::name;
+

            if [
-
                git::name::HEADS,
-
                git::name::TAGS,
-
                git::name::NOTES,
-
                &git::name::component!("rad"),
-
                &git::name::component!("cobs"),
+
                name::HEADS,
+
                name::TAGS,
+
                name::NOTES,
+
                &name::component!("rad"),
+
                &name::component!("cobs"),
            ]
            .contains(&category.as_ref())
            {
@@ -749,7 +757,7 @@ impl ReadRepository for Repository {
    fn references_glob(
        &self,
        pattern: &PatternStr,
-
    ) -> Result<Vec<(Qualified, Oid)>, git::ext::Error> {
+
    ) -> Result<Vec<(Qualified, Oid)>, crate::git2::Error> {
        let mut refs = Vec::new();

        for r in self.backend.references_glob(pattern)? {
@@ -761,8 +769,8 @@ impl ReadRepository for Repository {

            if let Some(name) = r
                .name()
-
                .and_then(|n| git::RefStr::try_from_str(n).ok())
-
                .and_then(git::Qualified::from_refstr)
+
                .and_then(|n| git::fmt::RefStr::try_from_str(n).ok())
+
                .and_then(git::fmt::Qualified::from_refstr)
            {
                refs.push((name.to_owned(), oid.into()));
            }
@@ -814,14 +822,13 @@ impl ReadRepository for Repository {

        match result {
            Ok(oid) => Ok(oid),
-
            Err(err) if git::ext::is_not_found_err(&err) => self.canonical_identity_head(),
+
            Err(err) if err.is_not_found() => self.canonical_identity_head(),
            Err(err) => Err(err.into()),
        }
    }

-
    fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, git::ext::Error> {
+
    fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, crate::git2::Error> {
        self.reference_oid(remote, &git::refs::storage::IDENTITY_BRANCH)
-
            .map_err(git::ext::Error::from)
    }

    fn identity_root(&self) -> Result<Oid, RepositoryError> {
@@ -860,7 +867,7 @@ impl ReadRepository for Repository {
            let blob = Doc::blob_at(root, self)?;

            // We've got an identity that goes back to the correct root.
-
            if blob.id() == **self.id {
+
            if *self.id == blob.id() {
                let identity = Identity::get(&root.into(), self)?;

                return Ok(identity.head());
@@ -869,11 +876,10 @@ impl ReadRepository for Repository {
        Err(DocError::Missing.into())
    }

-
    fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, git::ext::Error> {
+
    fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, crate::git2::Error> {
        self.backend
-
            .merge_base(**left, **right)
+
            .merge_base(left.into(), right.into())
            .map(Oid::from)
-
            .map_err(git::ext::Error::from)
    }
}

@@ -893,7 +899,7 @@ impl WriteRepository for Repository {
        }
        log::debug!(target: "storage", "Setting ref: {} -> {}", &branch_ref, new);
        self.raw()
-
            .reference(&branch_ref, *new, true, "set-local-branch (radicle)")?;
+
            .reference(&branch_ref, new.into(), true, "set-local-branch (radicle)")?;

        log::debug!(target: "storage", "Setting ref: {head_ref} -> {branch_ref}");
        self.raw()
@@ -906,7 +912,7 @@ impl WriteRepository for Repository {
        log::debug!(target: "storage", "Setting ref: {} -> {}", *CANONICAL_IDENTITY, commit);
        self.raw().reference(
            CANONICAL_IDENTITY.as_str(),
-
            *commit,
+
            commit.into(),
            true,
            "set-local-branch (radicle)",
        )?;
@@ -921,7 +927,7 @@ impl WriteRepository for Repository {
        let refname = git::refs::storage::id_root(remote);

        self.raw()
-
            .reference(refname.as_str(), *root, true, "set-id-root (radicle)")?;
+
            .reference(refname.as_str(), root.into(), true, "set-id-root (radicle)")?;

        Ok(())
    }
@@ -933,7 +939,7 @@ impl WriteRepository for Repository {
        Ok(())
    }

-
    fn raw(&self) -> &git2::Repository {
+
    fn raw(&self) -> &crate::git2::Repository {
        &self.backend
    }
}
@@ -983,7 +989,7 @@ pub mod trailers {

    pub fn parse_signatures(msg: &str) -> Result<HashMap<PublicKey, Signature>, Error> {
        let trailers =
-
            git2::message_trailers_strs(msg).map_err(|_| Error::SignatureTrailerFormat)?;
+
            crate::git2::message_trailers_strs(msg).map_err(|_| Error::SignatureTrailerFormat)?;
        let mut signatures = HashMap::with_capacity(trailers.len());

        for (key, val) in trailers.iter() {
@@ -1093,14 +1099,14 @@ mod tests {
        let (rid, _, working, _) =
            fixtures::project(tmp.path().join("project"), &storage, &signer).unwrap();
        let stored = storage.repository(rid).unwrap();
-
        let sig =
-
            git2::Signature::now(&alice.to_string(), "anonymous@radicle.example.com").unwrap();
+
        let sig = crate::git2::Signature::now(&alice.to_string(), "anonymous@radicle.example.com")
+
            .unwrap();
        let head = working.head().unwrap().peel_to_commit().unwrap();

        git::commit(
            &working,
            &head,
-
            &git::RefString::try_from(format!("refs/remotes/{alice}/heads/master")).unwrap(),
+
            &git::fmt::RefString::try_from(format!("refs/remotes/{alice}/heads/master")).unwrap(),
            "Second commit",
            &sig,
            &head.tree().unwrap(),
modified crates/radicle/src/storage/git/cob.rs
@@ -12,6 +12,7 @@ use storage::SignRepository;
use storage::ValidateRepository;

use crate::git;
+
use crate::git::fmt::*;
use crate::git::*;
use crate::identity;
use crate::identity::doc::DocError;
@@ -33,9 +34,7 @@ pub enum ObjectsError {
    #[error(transparent)]
    Convert(#[from] cob::object::storage::convert::Error),
    #[error(transparent)]
-
    Git(#[from] git2::Error),
-
    #[error(transparent)]
-
    GitExt(#[from] git_ext::Error),
+
    Git(#[from] crate::git2::Error),
}

#[derive(Error, Debug)]
@@ -43,7 +42,7 @@ pub enum TypesError {
    #[error(transparent)]
    Convert(#[from] cob::object::storage::convert::Error),
    #[error(transparent)]
-
    Git(#[from] git2::Error),
+
    Git(#[from] crate::git2::Error),
    #[error(transparent)]
    ParseKey(#[from] crypto::Error),
    #[error(transparent)]
@@ -55,12 +54,12 @@ pub enum TypesError {
impl cob::Store for Repository {}

impl change::Storage for Repository {
-
    type StoreError = <git2::Repository as change::Storage>::StoreError;
-
    type LoadError = <git2::Repository as change::Storage>::LoadError;
+
    type StoreError = <crate::git2::Repository as change::Storage>::StoreError;
+
    type LoadError = <crate::git2::Repository as change::Storage>::LoadError;

-
    type ObjectId = <git2::Repository as change::Storage>::ObjectId;
-
    type Parent = <git2::Repository as change::Storage>::Parent;
-
    type Signatures = <git2::Repository as change::Storage>::Signatures;
+
    type ObjectId = <crate::git2::Repository as change::Storage>::ObjectId;
+
    type Parent = <crate::git2::Repository as change::Storage>::Parent;
+
    type Signatures = <crate::git2::Repository as change::Storage>::Signatures;

    fn store<Signer>(
        &self,
@@ -91,8 +90,8 @@ impl change::Storage for Repository {
impl cob::object::Storage for Repository {
    type ObjectsError = ObjectsError;
    type TypesError = TypesError;
-
    type UpdateError = git2::Error;
-
    type RemoveError = git2::Error;
+
    type UpdateError = crate::git2::Error;
+
    type RemoveError = crate::git2::Error;

    type Namespace = NodeId;

@@ -200,12 +199,12 @@ impl<'a, R> DraftStore<'a, R> {
impl<R: storage::WriteRepository> cob::Store for DraftStore<'_, R> {}

impl<R: storage::WriteRepository> change::Storage for DraftStore<'_, R> {
-
    type StoreError = <git2::Repository as change::Storage>::StoreError;
-
    type LoadError = <git2::Repository as change::Storage>::LoadError;
+
    type StoreError = <crate::git2::Repository as change::Storage>::StoreError;
+
    type LoadError = <crate::git2::Repository as change::Storage>::LoadError;

-
    type ObjectId = <git2::Repository as change::Storage>::ObjectId;
-
    type Parent = <git2::Repository as change::Storage>::Parent;
-
    type Signatures = <git2::Repository as change::Storage>::Signatures;
+
    type ObjectId = <crate::git2::Repository as change::Storage>::ObjectId;
+
    type Parent = <crate::git2::Repository as change::Storage>::Parent;
+
    type Signatures = <crate::git2::Repository as change::Storage>::Signatures;

    fn store<Signer>(
        &self,
@@ -274,7 +273,7 @@ impl<R: storage::ReadRepository> ReadRepository for DraftStore<'_, R> {
        self.repo.id()
    }

-
    fn is_empty(&self) -> Result<bool, git2::Error> {
+
    fn is_empty(&self) -> Result<bool, crate::git2::Error> {
        self.repo.is_empty()
    }

@@ -290,47 +289,47 @@ impl<R: storage::ReadRepository> ReadRepository for DraftStore<'_, R> {
        self.repo.path()
    }

-
    fn commit(&self, oid: Oid) -> Result<git2::Commit, git_ext::Error> {
+
    fn commit(&self, oid: Oid) -> Result<crate::git2::Commit, crate::git2::Error> {
        self.repo.commit(oid)
    }

-
    fn revwalk(&self, head: Oid) -> Result<git2::Revwalk, git2::Error> {
+
    fn revwalk(&self, head: Oid) -> Result<crate::git2::Revwalk, crate::git2::Error> {
        self.repo.revwalk(head)
    }

-
    fn contains(&self, oid: Oid) -> Result<bool, raw::Error> {
+
    fn contains(&self, oid: Oid) -> Result<bool, crate::git2::Error> {
        self.repo.contains(oid)
    }

-
    fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, git_ext::Error> {
+
    fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, crate::git2::Error> {
        self.repo.is_ancestor_of(ancestor, head)
    }

    fn blob_at<P: AsRef<Path>>(
        &self,
-
        oid: git_ext::Oid,
+
        oid: Oid,
        path: P,
-
    ) -> Result<git2::Blob, git_ext::Error> {
+
    ) -> Result<crate::git2::Blob, crate::git2::Error> {
        self.repo.blob_at(oid, path)
    }

-
    fn blob(&self, oid: git_ext::Oid) -> Result<raw::Blob, ext::Error> {
+
    fn blob(&self, oid: Oid) -> Result<crate::git2::Blob, crate::git2::Error> {
        self.repo.blob(oid)
    }

    fn reference(
        &self,
        remote: &RemoteId,
-
        reference: &git::Qualified,
-
    ) -> Result<git2::Reference, git_ext::Error> {
+
        reference: &git::fmt::Qualified,
+
    ) -> Result<crate::git2::Reference, crate::git2::Error> {
        self.repo.reference(remote, reference)
    }

    fn reference_oid(
        &self,
        remote: &RemoteId,
-
        reference: &git::Qualified,
-
    ) -> Result<git_ext::Oid, git::raw::Error> {
+
        reference: &git::fmt::Qualified,
+
    ) -> Result<Oid, crate::git2::Error> {
        self.repo.reference_oid(remote, reference)
    }

@@ -340,8 +339,8 @@ impl<R: storage::ReadRepository> ReadRepository for DraftStore<'_, R> {

    fn references_glob(
        &self,
-
        pattern: &git::PatternStr,
-
    ) -> Result<Vec<(fmt::Qualified, Oid)>, git::ext::Error> {
+
        pattern: &git::fmt::refspec::PatternStr,
+
    ) -> Result<Vec<(fmt::Qualified, Oid)>, crate::git2::Error> {
        self.repo.references_glob(pattern)
    }

@@ -357,7 +356,7 @@ impl<R: storage::ReadRepository> ReadRepository for DraftStore<'_, R> {
        self.repo.identity_head()
    }

-
    fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, super::ext::Error> {
+
    fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, crate::git2::Error> {
        self.repo.identity_head_of(remote)
    }

@@ -373,16 +372,16 @@ impl<R: storage::ReadRepository> ReadRepository for DraftStore<'_, R> {
        self.repo.canonical_identity_head()
    }

-
    fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, git::ext::Error> {
+
    fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, crate::git2::Error> {
        self.repo.merge_base(left, right)
    }
}

impl<R: storage::WriteRepository> cob::object::Storage for DraftStore<'_, R> {
    type ObjectsError = ObjectsError;
-
    type TypesError = git::ext::Error;
-
    type UpdateError = git2::Error;
-
    type RemoveError = git2::Error;
+
    type TypesError = crate::git2::Error;
+
    type UpdateError = crate::git2::Error;
+
    type RemoveError = crate::git2::Error;

    type Namespace = NodeId;

modified crates/radicle/src/storage/git/transport/local.rs
@@ -25,20 +25,19 @@ struct Local {
    child: RefCell<Option<process::Child>>,
}

-
impl git2::transport::SmartSubtransport for Local {
+
impl crate::git2::transport::SmartSubtransport for Local {
    fn action(
        &self,
        url: &str,
-
        service: git2::transport::Service,
-
    ) -> Result<Box<dyn git2::transport::SmartSubtransportStream>, git2::Error> {
-
        let url = Url::from_str(url).map_err(|e| git2::Error::from_str(e.to_string().as_str()))?;
+
        service: crate::git2::transport::Service,
+
    ) -> Result<Box<dyn crate::git2::transport::SmartSubtransportStream>, crate::git2::Error> {
+
        let url =
+
            Url::from_str(url).map_err(|e| crate::git2::Error::from_str(e.to_string().as_str()))?;
        let service: &str = match service {
-
            git2::transport::Service::UploadPack | git2::transport::Service::UploadPackLs => {
-
                "upload-pack"
-
            }
-
            git2::transport::Service::ReceivePack | git2::transport::Service::ReceivePackLs => {
-
                "receive-pack"
-
            }
+
            crate::git2::transport::Service::UploadPack
+
            | crate::git2::transport::Service::UploadPackLs => "upload-pack",
+
            crate::git2::transport::Service::ReceivePack
+
            | crate::git2::transport::Service::ReceivePackLs => "receive-pack",
        };
        let git_dir = THREAD_STORAGE
            .with(|t| {
@@ -46,7 +45,9 @@ impl git2::transport::SmartSubtransport for Local {
                    .as_ref()
                    .map(|s| storage::git::paths::repository(&s, &url.repo))
            })
-
            .ok_or_else(|| git2::Error::from_str("local transport storage was not registered"))?;
+
            .ok_or_else(|| {
+
                crate::git2::Error::from_str("local transport storage was not registered")
+
            })?;

        let mut cmd = process::Command::new("git");

@@ -61,7 +62,7 @@ impl git2::transport::SmartSubtransport for Local {
            .stdout(process::Stdio::piped())
            .stderr(process::Stdio::inherit())
            .spawn()
-
            .map_err(|e| git2::Error::from_str(e.to_string().as_str()))?;
+
            .map_err(|e| crate::git2::Error::from_str(e.to_string().as_str()))?;

        let stdin = child.stdin.take().expect("taking stdin is safe");
        let stdout = child.stdout.take().expect("taking stdout is safe");
@@ -71,19 +72,19 @@ impl git2::transport::SmartSubtransport for Local {
        Ok(Box::new(ChildStream { stdout, stdin }))
    }

-
    fn close(&self) -> Result<(), git2::Error> {
+
    fn close(&self) -> Result<(), crate::git2::Error> {
        if let Some(mut child) = self.child.take() {
            let result = child
                .wait()
-
                .map_err(|e| git2::Error::from_str(e.to_string().as_str()))?;
+
                .map_err(|e| crate::git2::Error::from_str(e.to_string().as_str()))?;

            if !result.success() {
                return if let Some(code) = result.code() {
-
                    Err(git2::Error::from_str(
+
                    Err(crate::git2::Error::from_str(
                        format!("transport: child process exited with error code {code}").as_str(),
                    ))
                } else {
-
                    Err(git2::Error::from_str(
+
                    Err(crate::git2::Error::from_str(
                        "transport: child process exited with unknown error",
                    ))
                };
@@ -103,8 +104,8 @@ pub fn register(storage: Storage) {
    });

    REGISTER.call_once(|| unsafe {
-
        git2::transport::register(Url::SCHEME, move |remote| {
-
            git2::transport::Transport::smart(remote, false, Local::default())
+
        crate::git2::transport::register(Url::SCHEME, move |remote| {
+
            crate::git2::transport::Transport::smart(remote, false, Local::default())
        })
        .expect("local transport registration");
    });
modified crates/radicle/src/storage/git/transport/remote/mock.rs
@@ -19,19 +19,21 @@ static NODES: LazyLock<Mutex<HashMap<(ThreadId, RemoteId), PathBuf>>> =
#[derive(Default)]
struct MockTransport;

-
impl git2::transport::SmartSubtransport for MockTransport {
+
impl crate::git2::transport::SmartSubtransport for MockTransport {
    fn action(
        &self,
        url: &str,
-
        service: git2::transport::Service,
-
    ) -> Result<Box<dyn git2::transport::SmartSubtransportStream>, git2::Error> {
-
        let url = Url::from_str(url).map_err(|e| git2::Error::from_str(e.to_string().as_str()))?;
+
        service: crate::git2::transport::Service,
+
    ) -> Result<Box<dyn crate::git2::transport::SmartSubtransportStream>, crate::git2::Error> {
+
        let url =
+
            Url::from_str(url).map_err(|e| crate::git2::Error::from_str(e.to_string().as_str()))?;
        let id = thread::current().id();
        let nodes = NODES.lock().expect("lock cannot be poisoned");
        let storage = if let Some(storage) = nodes.get(&(id, url.node)) {
            match service {
-
                git2::transport::Service::ReceivePack | git2::transport::Service::ReceivePackLs => {
-
                    return Err(git2::Error::from_str(
+
                crate::git2::transport::Service::ReceivePack
+
                | crate::git2::transport::Service::ReceivePackLs => {
+
                    return Err(crate::git2::Error::from_str(
                        "git-receive-pack is not supported with the mock transport",
                    ));
                }
@@ -39,7 +41,7 @@ impl git2::transport::SmartSubtransport for MockTransport {
            }
            storage
        } else {
-
            return Err(git2::Error::from_str(&format!(
+
            return Err(crate::git2::Error::from_str(&format!(
                "node {} was not registered with the mock transport",
                url.node
            )));
@@ -76,7 +78,7 @@ impl git2::transport::SmartSubtransport for MockTransport {
        Ok(Box::new(ChildStream { stdout, stdin }))
    }

-
    fn close(&self) -> Result<(), git2::Error> {
+
    fn close(&self) -> Result<(), crate::git2::Error> {
        Ok(())
    }
}
@@ -86,8 +88,8 @@ pub fn register(node: &RemoteId, path: &Path) {
    static REGISTER: Once = Once::new();

    REGISTER.call_once(|| unsafe {
-
        git2::transport::register(Url::SCHEME, move |remote| {
-
            git2::transport::Transport::smart(remote, false, MockTransport)
+
        crate::git2::transport::register(Url::SCHEME, move |remote| {
+
            crate::git2::transport::Transport::smart(remote, false, MockTransport)
        })
        .expect("transport registration is successful");
    });
modified crates/radicle/src/storage/refs.rs
@@ -13,12 +13,12 @@ use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::git;
-
use crate::git::ext as git_ext;
-
use crate::git::Oid;
+
use crate::git2::ErrorExt as _;
use crate::node::device::Device;
use crate::profile::env;
use crate::storage;
use crate::storage::{ReadRepository, RemoteId, RepoId, WriteRepository};
+
use crate::Oid;

pub use crate::git::refs::storage::*;

@@ -47,7 +47,7 @@ pub enum Error {
    #[error("invalid reference")]
    InvalidRef,
    #[error("missing identity root reference '{0}'")]
-
    MissingIdentityRoot(git::RefString),
+
    MissingIdentityRoot(git::fmt::RefString),
    #[error("missing identity object '{0}'")]
    MissingIdentity(Oid),
    #[error("mismatched identity: local {local}, remote {remote}")]
@@ -55,28 +55,24 @@ pub enum Error {
    #[error("invalid reference: {0}")]
    Ref(#[from] git::RefError),
    #[error(transparent)]
-
    Git(#[from] git2::Error),
-
    #[error(transparent)]
-
    GitExt(#[from] git_ext::Error),
+
    Git(#[from] crate::git2::Error),
}

impl Error {
    /// Whether this error is caused by a reference not being found.
    pub fn is_not_found(&self) -> bool {
        match self {
-
            Self::GitExt(git::Error::NotFound(_)) => true,
-
            Self::GitExt(git::Error::Git(e)) if git::is_not_found_err(e) => true,
-
            Self::Git(e) if git::is_not_found_err(e) => true,
+
            Self::Git(e) if e.is_not_found() => true,
            _ => false,
        }
    }
}

-
// TODO(finto): we should turn `git::RefString` to `git::Qualified`,
+
// TODO(finto): we should turn `git::fmt::RefString` to `git::fmt::Qualified`,
// since all these refs SHOULD be `Qualified`.
/// The published state of a local repository.
#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize)]
-
pub struct Refs(BTreeMap<git::RefString, Oid>);
+
pub struct Refs(BTreeMap<git::fmt::RefString, Oid>);

impl Refs {
    /// Verify the given signature on these refs, and return [`SignedRefs`] on success.
@@ -102,13 +98,13 @@ impl Refs {
    }

    /// Get a particular ref.
-
    pub fn get(&self, name: &git::Qualified) -> Option<Oid> {
+
    pub fn get(&self, name: &git::fmt::Qualified) -> Option<Oid> {
        self.0.get(name.to_ref_string().as_refstr()).copied()
    }

    /// Get a particular head ref.
-
    pub fn head(&self, name: impl AsRef<git::RefStr>) -> Option<Oid> {
-
        let branch = git::refname!("refs/heads").join(name);
+
    pub fn head(&self, name: impl AsRef<git::fmt::RefStr>) -> Option<Oid> {
+
        let branch = git::fmt::refname!("refs/heads").join(name);
        self.0.get(&branch).copied()
    }

@@ -123,8 +119,8 @@ impl Refs {
                .split_once(' ')
                .ok_or(canonical::Error::InvalidFormat)?;

-
            let name = git::RefString::try_from(name)?;
-
            let oid = Oid::from_str(oid)?;
+
            let name = git::fmt::RefString::try_from(name)?;
+
            let oid = Oid::from_str(oid).map_err(|_| canonical::Error::InvalidFormat)?;

            if oid.is_zero() {
                continue;
@@ -148,15 +144,15 @@ impl Refs {
}

impl IntoIterator for Refs {
-
    type Item = (git::RefString, Oid);
-
    type IntoIter = std::collections::btree_map::IntoIter<git::RefString, Oid>;
+
    type Item = (git::fmt::RefString, Oid);
+
    type IntoIter = std::collections::btree_map::IntoIter<git::fmt::RefString, Oid>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

-
impl From<Refs> for BTreeMap<git::RefString, Oid> {
+
impl From<Refs> for BTreeMap<git::fmt::RefString, Oid> {
    fn from(refs: Refs) -> Self {
        refs.0
    }
@@ -168,14 +164,14 @@ impl<V> From<SignedRefs<V>> for Refs {
    }
}

-
impl From<BTreeMap<git::RefString, Oid>> for Refs {
-
    fn from(refs: BTreeMap<git::RefString, Oid>) -> Self {
+
impl From<BTreeMap<git::fmt::RefString, Oid>> for Refs {
+
    fn from(refs: BTreeMap<git::fmt::RefString, Oid>) -> Self {
        Self(refs)
    }
}

impl Deref for Refs {
-
    type Target = BTreeMap<git::RefString, Oid>;
+
    type Target = BTreeMap<git::fmt::RefString, Oid>;

    fn deref(&self) -> &Self::Target {
        &self.0
@@ -297,7 +293,7 @@ impl SignedRefs<Verified> {
            Some(SignedRefsAt { sigrefs, at }) if sigrefs.signature == self.signature => {
                return Ok(Updated::Unchanged { oid: at });
            }
-
            Some(SignedRefsAt { at, .. }) => Some(raw.find_commit(*at)?),
+
            Some(SignedRefsAt { at, .. }) => Some(raw.find_commit(at.into())?),
            None => None,
        };

@@ -322,8 +318,8 @@ impl SignedRefs<Verified> {
                    env::GIT_COMMITTER_DATE
                );
            };
-
            let time = git2::Time::new(timestamp, 0);
-
            git2::Signature::new("radicle", remote.to_string().as_str(), &time)?
+
            let time = crate::git2::Time::new(timestamp, 0);
+
            crate::git2::Signature::new("radicle", remote.to_string().as_str(), &time)?
        } else {
            raw.signature()?
        };
@@ -334,13 +330,13 @@ impl SignedRefs<Verified> {
            &author,
            "Update signed refs\n",
            &tree,
-
            &parent.iter().collect::<Vec<&git2::Commit>>(),
+
            &parent.iter().collect::<Vec<&crate::git2::Commit>>(),
        );

        match commit {
            Ok(oid) => Ok(Updated::Updated { oid: oid.into() }),
            Err(e) => match (e.class(), e.code()) {
-
                (git2::ErrorClass::Object, git2::ErrorCode::Modified) => {
+
                (crate::git2::ErrorClass::Object, crate::git2::ErrorCode::Modified) => {
                    log::warn!("Concurrent modification of refs: {e:?}");

                    Err(Error::Git(e))
@@ -386,12 +382,11 @@ pub struct RefsAt {
    )]
    pub remote: RemoteId,
    /// The commit SHA that `rad/sigrefs` points to.
-
    #[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
    pub at: Oid,
}

impl RefsAt {
-
    pub fn new<S: ReadRepository>(repo: &S, remote: RemoteId) -> Result<Self, git::raw::Error> {
+
    pub fn new<S: ReadRepository>(repo: &S, remote: RemoteId) -> Result<Self, crate::git2::Error> {
        let at = repo.reference_oid(&remote, &storage::refs::SIGREFS_BRANCH)?;
        Ok(RefsAt { remote, at })
    }
@@ -400,7 +395,7 @@ impl RefsAt {
        SignedRefsAt::load_at(self.at, self.remote, repo)
    }

-
    pub fn path(&self) -> &git::Qualified {
+
    pub fn path(&self) -> &git::fmt::Qualified {
        &SIGREFS_BRANCH
    }
}
@@ -424,7 +419,7 @@ impl SignedRefsAt {
    {
        let at = match RefsAt::new(repo, remote) {
            Ok(RefsAt { at, .. }) => at,
-
            Err(e) if git::is_not_found_err(&e) => return Ok(None),
+
            Err(e) if e.is_not_found() => return Ok(None),
            Err(e) => return Err(e.into()),
        };
        Self::load_at(at, remote, repo).map(Some)
@@ -440,7 +435,7 @@ impl SignedRefsAt {
        })
    }

-
    pub fn iter(&self) -> impl Iterator<Item = (&git::RefString, &Oid)> {
+
    pub fn iter(&self) -> impl Iterator<Item = (&git::fmt::RefString, &Oid)> {
        self.sigrefs.refs.iter()
    }
}
@@ -465,7 +460,7 @@ pub mod canonical {
        #[error(transparent)]
        Io(#[from] io::Error),
        #[error(transparent)]
-
        Git(#[from] git2::Error),
+
        Git(#[from] crate::git2::Error),
    }
}

@@ -509,7 +504,7 @@ mod tests {
            &paris_repo,
            "paris".try_into().unwrap(),
            "Paris repository",
-
            git::refname!("master"),
+
            git::fmt::refname!("master"),
            Default::default(),
            &alice,
            storage,
@@ -522,7 +517,7 @@ mod tests {
            &london_repo,
            "london".try_into().unwrap(),
            "London repository",
-
            git::refname!("master"),
+
            git::fmt::refname!("master"),
            Default::default(),
            &alice,
            storage,
@@ -574,12 +569,12 @@ mod tests {
            )
            .unwrap();

-
            let paris_head = bob_working.find_commit(paris_head).unwrap();
-
            let bob_sig = git2::Signature::now("bob", "bob@example.com").unwrap();
+
            let paris_head = bob_working.find_commit(paris_head.into()).unwrap();
+
            let bob_sig = crate::git2::Signature::now("bob", "bob@example.com").unwrap();
            let bob_head = git::empty_commit(
                &bob_working,
                &paris_head,
-
                git::refname!("refs/heads/master").as_refstr(),
+
                git::fmt::refname!("refs/heads/master").as_refstr(),
                "Bob's commit",
                &bob_sig,
            )
@@ -596,9 +591,9 @@ mod tests {

            assert_eq!(
                sigrefs
-
                    .get(&git_ext::ref_format::qualified!("refs/heads/master"))
+
                    .get(&crate::git::fmt::qualified!("refs/heads/master"))
                    .unwrap(),
-
                bob_head.id().into()
+
                bob_head.id()
            );
            (sigrefs, bob_head.id())
        };
@@ -610,10 +605,10 @@ mod tests {
                .unwrap();
            assert_ne!(
                alice_paris_sigrefs
-
                    .get(&git_ext::ref_format::qualified!("refs/heads/master"))
+
                    .get(&crate::git::fmt::qualified!("refs/heads/master"))
                    .unwrap(),
                bob_paris_sigrefs
-
                    .get(&git_ext::ref_format::qualified!("refs/heads/master"))
+
                    .get(&crate::git::fmt::qualified!("refs/heads/master"))
                    .unwrap()
            );
        }
@@ -645,7 +640,8 @@ mod tests {
        london
            .raw()
            .reference(
-
                git::refs::storage::branch_of(bob.public_key(), &git::refname!("master")).as_str(),
+
                git::refs::storage::branch_of(bob.public_key(), &git::fmt::refname!("master"))
+
                    .as_str(),
                bob_head,
                false,
                "",
modified crates/radicle/src/test.rs
@@ -6,6 +6,7 @@ pub mod storage;

use super::storage::{Namespaces, RefUpdate};

+
use crate::git;
use crate::prelude::NodeId;
use crate::storage::WriteRepository;

@@ -22,18 +23,18 @@ pub fn fetch<W: WriteRepository>(
        Namespaces::Followed(followed) => followed.into_iter().next(),
    };
    let mut updates = Vec::new();
-
    let mut callbacks = git2::RemoteCallbacks::new();
-
    let mut opts = git2::FetchOptions::default();
+
    let mut callbacks = crate::git2::RemoteCallbacks::new();
+
    let mut opts = crate::git2::FetchOptions::default();
    let refspec = if let Some(namespace) = namespace {
-
        opts.prune(git2::FetchPrune::On);
+
        opts.prune(crate::git2::FetchPrune::On);
        format!("refs/namespaces/{namespace}/refs/*:refs/namespaces/{namespace}/refs/*")
    } else {
-
        opts.prune(git2::FetchPrune::Off);
+
        opts.prune(crate::git2::FetchPrune::Off);
        "refs/namespaces/*:refs/namespaces/*".to_owned()
    };

    callbacks.update_tips(|name, old, new| {
-
        if let Ok(name) = crate::git::RefString::try_from(name) {
+
        if let Ok(name) = git::fmt::RefString::try_from(name) {
            if name.to_namespaced().is_some() {
                updates.push(RefUpdate::from(name, old, new));
                // Returning `true` ensures the process is not aborted.
@@ -179,7 +180,7 @@ pub mod setup {

    /// A repository checkout.
    pub struct NodeRepoCheckout {
-
        checkout: git::raw::Repository,
+
        checkout: crate::git2::Repository,
    }

    impl NodeRepoCheckout {
@@ -187,7 +188,8 @@ pub mod setup {
            &self,
            blobs: impl IntoIterator<Item = (S, T)>,
        ) -> BranchWith {
-
            let refname = git::Qualified::from(git::lit::refs_heads(git::refname!("master")));
+
            let refname =
+
                git::fmt::Qualified::from(git::fmt::lit::refs_heads(git::fmt::refname!("master")));
            let base = self.checkout.refname_to_id(refname.as_str()).unwrap();
            let parent = self.checkout.find_commit(base).unwrap();
            let oid = commit(&self.checkout, &refname, blobs, &[&parent]);
@@ -202,7 +204,7 @@ pub mod setup {
    }

    impl std::ops::Deref for NodeRepoCheckout {
-
        type Target = git::raw::Repository;
+
        type Target = crate::git2::Repository;

        fn deref(&self) -> &Self::Target {
            &self.checkout
@@ -290,27 +292,27 @@ pub mod setup {

    #[derive(Debug)]
    pub struct BranchWith {
-
        pub base: git::Oid,
-
        pub oid: git::Oid,
+
        pub base: crate::Oid,
+
        pub oid: crate::Oid,
    }

    pub fn commit<S: AsRef<Path>, T: AsRef<[u8]>>(
-
        repo: &git2::Repository,
-
        refname: &git::Qualified,
+
        repo: &crate::git2::Repository,
+
        refname: &git::fmt::Qualified,
        blobs: impl IntoIterator<Item = (S, T)>,
-
        parents: &[&git2::Commit<'_>],
-
    ) -> git::Oid {
+
        parents: &[&crate::git2::Commit<'_>],
+
    ) -> crate::Oid {
        let tree = {
            let mut tb = repo.treebuilder(None).unwrap();
            for (name, blob) in blobs.into_iter() {
                let oid = repo.blob(blob.as_ref()).unwrap();
-
                tb.insert(name.as_ref(), oid, git2::FileMode::Blob.into())
+
                tb.insert(name.as_ref(), oid, crate::git2::FileMode::Blob.into())
                    .unwrap();
            }
            tb.write().unwrap()
        };
        let tree = repo.find_tree(tree).unwrap();
-
        let author = git2::Signature::now("anonymous", "anonymous@example.com").unwrap();
+
        let author = crate::git2::Signature::now("anonymous", "anonymous@example.com").unwrap();

        repo.commit(
            Some(refname.as_str()),
modified crates/radicle/src/test/arbitrary.rs
@@ -27,14 +27,14 @@ use crate::{cob, git};

pub fn oid() -> storage::Oid {
    let oid_bytes: [u8; 20] = gen(1);
-
    storage::Oid::try_from(oid_bytes.as_slice()).unwrap()
+
    storage::Oid::from_sha1(oid_bytes)
}

pub fn entry_id() -> cob::EntryId {
    self::oid()
}

-
pub fn refstring(len: usize) -> git::RefString {
+
pub fn refstring(len: usize) -> git::fmt::RefString {
    let mut buf = Vec::<u8>::new();
    for _ in 0..len {
        buf.push(fastrand::u8(0x61..0x7a));
@@ -135,7 +135,7 @@ impl Arbitrary for Project {
        let description = iter::repeat_with(|| rng.alphanumeric())
            .take(length * 2)
            .collect();
-
        let default_branch: git::RefString = iter::repeat_with(|| rng.alphanumeric())
+
        let default_branch: git::fmt::RefString = iter::repeat_with(|| rng.alphanumeric())
            .take(length)
            .collect::<String>()
            .try_into()
@@ -207,7 +207,7 @@ impl Arbitrary for SignedRefs<Unverified> {

impl Arbitrary for Refs {
    fn arbitrary(g: &mut qcheck::Gen) -> Self {
-
        let mut refs: BTreeMap<git::RefString, storage::Oid> = BTreeMap::new();
+
        let mut refs: BTreeMap<git::fmt::RefString, storage::Oid> = BTreeMap::new();
        let mut bytes: [u8; 20] = [0; 20];
        let names = &[
            "heads/master",
@@ -225,8 +225,8 @@ impl Arbitrary for Refs {
                for byte in &mut bytes {
                    *byte = u8::arbitrary(g);
                }
-
                let oid = storage::Oid::try_from(&bytes[..]).unwrap();
-
                let name = git::RefString::try_from(*name).unwrap();
+
                let oid = storage::Oid::from_sha1(bytes);
+
                let name = git::fmt::RefString::try_from(*name).unwrap();

                refs.insert(name, oid);
            }
@@ -273,7 +273,7 @@ impl Arbitrary for storage::Remote<crypto::Unverified> {
impl Arbitrary for RepoId {
    fn arbitrary(g: &mut qcheck::Gen) -> Self {
        let bytes = <[u8; 20]>::arbitrary(g);
-
        let oid = git::Oid::try_from(bytes.as_slice()).unwrap();
+
        let oid = crate::Oid::from_sha1(bytes);

        RepoId::from(oid)
    }
modified crates/radicle/src/test/fixtures.rs
@@ -57,7 +57,7 @@ where
            &repo,
            name.try_into().unwrap(),
            desc,
-
            git::refname!("master"),
+
            git::fmt::refname!("master"),
            Visibility::default(),
            signer,
            &storage,
@@ -72,7 +72,15 @@ pub fn project<P, G>(
    path: P,
    storage: &Storage,
    signer: &Device<G>,
-
) -> Result<(RepoId, SignedRefs<Verified>, git2::Repository, git2::Oid), rad::InitError>
+
) -> Result<
+
    (
+
        RepoId,
+
        SignedRefs<Verified>,
+
        crate::git2::Repository,
+
        crate::Oid,
+
    ),
+
    rad::InitError,
+
>
where
    P: AsRef<Path>,
    G: crypto::signature::Signer<crypto::Signature>,
@@ -84,7 +92,7 @@ where
        &working,
        "acme".try_into().unwrap(),
        "Acme's repository",
-
        git::refname!("master"),
+
        git::fmt::refname!("master"),
        Visibility::default(),
        signer,
        storage,
@@ -94,19 +102,19 @@ where
}

/// Creates a regular repository at the given path with a couple of commits.
-
pub fn repository<P: AsRef<Path>>(path: P) -> (git2::Repository, git2::Oid) {
+
pub fn repository<P: AsRef<Path>>(path: P) -> (crate::git2::Repository, crate::Oid) {
    let (repo, oid) = repository_with(
        path,
-
        git2::RepositoryInitOptions::new().external_template(false),
+
        crate::git2::RepositoryInitOptions::new().external_template(false),
    );
    repo.checkout_head(None).unwrap();
    (repo, oid)
}

-
pub fn bare_repository<P: AsRef<Path>>(path: P) -> (git2::Repository, git2::Oid) {
+
pub fn bare_repository<P: AsRef<Path>>(path: P) -> (crate::git2::Repository, crate::Oid) {
    repository_with(
        path,
-
        git2::RepositoryInitOptions::new()
+
        crate::git2::RepositoryInitOptions::new()
            .external_template(false)
            .bare(true),
    )
@@ -114,24 +122,28 @@ pub fn bare_repository<P: AsRef<Path>>(path: P) -> (git2::Repository, git2::Oid)

fn repository_with<P: AsRef<Path>>(
    path: P,
-
    opts: &mut git2::RepositoryInitOptions,
-
) -> (git2::Repository, git2::Oid) {
-
    let repo = git2::Repository::init_opts(path, opts).unwrap();
+
    opts: &mut crate::git2::RepositoryInitOptions,
+
) -> (crate::git2::Repository, crate::Oid) {
+
    let repo = crate::git2::Repository::init_opts(path, opts).unwrap();

    {
        let mut config = repo.config().unwrap();
        config.set_str("user.name", USER_NAME).unwrap();
        config.set_str("user.email", USER_EMAIL).unwrap();
    }
-
    let sig =
-
        git2::Signature::new(USER_NAME, USER_EMAIL, &git2::Time::new(RADICLE_EPOCH, 0)).unwrap();
+
    let sig = crate::git2::Signature::new(
+
        USER_NAME,
+
        USER_EMAIL,
+
        &crate::git2::Time::new(RADICLE_EPOCH, 0),
+
    )
+
    .unwrap();
    let head = git::initial_commit(&repo, &sig).unwrap();
    let tree = git::write_tree(Path::new("README"), "Hello World!\n".as_bytes(), &repo).unwrap();
    let oid = {
        let commit = git::commit(
            &repo,
            &head,
-
            git::refname!("refs/heads/master").as_refstr(),
+
            git::fmt::refname!("refs/heads/master").as_refstr(),
            "Second commit",
            &sig,
            &tree,
@@ -145,18 +157,22 @@ fn repository_with<P: AsRef<Path>>(
    drop(tree);
    drop(head);

-
    (repo, oid)
+
    (repo, oid.into())
}

/// Create an empty commit on the current branch.
-
pub fn commit(msg: &str, parents: &[git2::Oid], repo: &git2::Repository) -> git::Oid {
+
pub fn commit(msg: &str, parents: &[crate::Oid], repo: &crate::git2::Repository) -> crate::Oid {
    let head = repo.head().unwrap();
-
    let sig =
-
        git2::Signature::new(USER_NAME, USER_EMAIL, &git2::Time::new(RADICLE_EPOCH, 0)).unwrap();
+
    let sig = crate::git2::Signature::new(
+
        USER_NAME,
+
        USER_EMAIL,
+
        &crate::git2::Time::new(RADICLE_EPOCH, 0),
+
    )
+
    .unwrap();
    let tree = head.peel_to_commit().unwrap().tree().unwrap();
    let parents = parents
        .iter()
-
        .map(|p| repo.find_commit(*p).unwrap())
+
        .map(|p| repo.find_commit(p.into()).unwrap())
        .collect::<Vec<_>>();
    let parents = parents.iter().collect::<Vec<_>>(); // Get references.

@@ -166,19 +182,28 @@ pub fn commit(msg: &str, parents: &[git2::Oid], repo: &git2::Repository) -> git:
}

/// Create an (annotated) tag of the given commit.
-
pub fn tag(name: &str, message: &str, commit: git2::Oid, repo: &git2::Repository) -> git::Oid {
+
pub fn tag(
+
    name: &str,
+
    message: &str,
+
    commit: crate::Oid,
+
    repo: &crate::git2::Repository,
+
) -> crate::Oid {
    let target = repo
-
        .find_object(commit, Some(git2::ObjectType::Commit))
+
        .find_object(commit.into(), Some(crate::git2::ObjectType::Commit))
        .unwrap();
-
    let tagger =
-
        git2::Signature::new(USER_NAME, USER_EMAIL, &git2::Time::new(RADICLE_EPOCH, 0)).unwrap();
+
    let tagger = crate::git2::Signature::new(
+
        USER_NAME,
+
        USER_EMAIL,
+
        &crate::git2::Time::new(RADICLE_EPOCH, 0),
+
    )
+
    .unwrap();
    repo.tag(name, &target, &tagger, message, false)
        .unwrap()
        .into()
}

/// Populate a repository with commits, branches and blobs.
-
pub fn populate(repo: &git2::Repository, scale: usize) -> Vec<git::Qualified> {
+
pub fn populate(repo: &crate::git2::Repository, scale: usize) -> Vec<git::fmt::Qualified> {
    assert!(
        scale <= 8,
        "Scale parameter must be less than or equal to 8"
@@ -196,9 +221,9 @@ pub fn populate(repo: &git2::Repository, scale: usize) -> Vec<git::Qualified> {
            .take(7)
            .collect::<String>()
            .to_lowercase();
-
        let name =
-
            git::refname!("feature").join(git::RefString::try_from(random.as_str()).unwrap());
-
        let signature = git2::Signature::now("Radicle", "radicle@radicle.xyz").unwrap();
+
        let name = git::fmt::refname!("feature")
+
            .join(git::fmt::RefString::try_from(random.as_str()).unwrap());
+
        let signature = crate::git2::Signature::now("Radicle", "radicle@radicle.xyz").unwrap();

        rng.fill(&mut buffer);

@@ -219,7 +244,7 @@ pub fn populate(repo: &git2::Repository, scale: usize) -> Vec<git::Qualified> {
        )
        .unwrap();

-
        refs.push(git::Qualified::from_refstr(refstr).unwrap());
+
        refs.push(git::fmt::Qualified::from_refstr(refstr).unwrap());
    }
    refs
}
@@ -241,20 +266,20 @@ pub mod gen {
    }

    /// Creates a regular repository at the given path with a couple of commits.
-
    pub fn repository<P: AsRef<Path>>(path: P) -> (git2::Repository, git2::Oid) {
-
        let repo = git2::Repository::init_opts(
+
    pub fn repository<P: AsRef<Path>>(path: P) -> (crate::git2::Repository, crate::git2::Oid) {
+
        let repo = crate::git2::Repository::init_opts(
            path,
-
            git2::RepositoryInitOptions::new().external_template(false),
+
            crate::git2::RepositoryInitOptions::new().external_template(false),
        )
        .unwrap();
-
        let sig = git2::Signature::now(string(6).as_str(), email().as_str()).unwrap();
+
        let sig = crate::git2::Signature::now(string(6).as_str(), email().as_str()).unwrap();
        let head = git::initial_commit(&repo, &sig).unwrap();
        let tree =
            git::write_tree(Path::new("README"), "Hello World!\n".as_bytes(), &repo).unwrap();
        let oid = git::commit(
            &repo,
            &head,
-
            git::refname!("refs/heads/master").as_refstr(),
+
            git::fmt::refname!("refs/heads/master").as_refstr(),
            string(16).as_str(),
            &sig,
            &tree,
modified crates/radicle/src/test/storage.rs
@@ -4,7 +4,7 @@ use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;

-
use git_ext::ref_format as fmt;
+
use crate::git::fmt;

use crate::crypto::Verified;
use crate::identity::doc::{Doc, DocAt, DocError, RawDoc, RepoId};
@@ -18,7 +18,7 @@ use super::{arbitrary, fixtures};
#[derive(Clone, Debug)]
pub struct MockStorage {
    pub path: PathBuf,
-
    pub info: git::UserInfo,
+
    pub info: crate::git::UserInfo,

    /// All refs keyed by RID.
    /// Each value is a map of refs keyed by node Id (public key).
@@ -67,7 +67,7 @@ impl MockStorage {
impl ReadStorage for MockStorage {
    type Repository = MockRepository;

-
    fn info(&self) -> &git::UserInfo {
+
    fn info(&self) -> &crate::git::UserInfo {
        &self.info
    }

@@ -199,7 +199,7 @@ impl ReadRepository for MockRepository {
        self.id
    }

-
    fn is_empty(&self) -> Result<bool, git2::Error> {
+
    fn is_empty(&self) -> Result<bool, crate::git2::Error> {
        Ok(self.remotes.is_empty())
    }

@@ -215,56 +215,58 @@ impl ReadRepository for MockRepository {
        todo!()
    }

-
    fn commit(&self, oid: Oid) -> Result<git2::Commit, git_ext::Error> {
-
        Err(git_ext::Error::NotFound(git_ext::NotFound::NoSuchObject(
-
            *oid,
-
        )))
+
    fn commit(&self, oid: Oid) -> Result<crate::git2::Commit, crate::git2::Error> {
+
        Err(crate::git2::Error::new(
+
            crate::git2::ErrorCode::NotFound,
+
            crate::git2::ErrorClass::None,
+
            format!("commit {oid} not found"),
+
        ))
    }

-
    fn revwalk(&self, _head: Oid) -> Result<git2::Revwalk, git2::Error> {
+
    fn revwalk(&self, _head: Oid) -> Result<crate::git2::Revwalk, crate::git2::Error> {
        todo!()
    }

-
    fn contains(&self, oid: Oid) -> Result<bool, git2::Error> {
+
    fn contains(&self, oid: Oid) -> Result<bool, crate::git2::Error> {
        Ok(self
            .remotes
            .values()
            .any(|sigrefs| sigrefs.at == oid || sigrefs.refs.values().any(|oid_| *oid_ == oid)))
    }

-
    fn is_ancestor_of(&self, _ancestor: Oid, _head: Oid) -> Result<bool, git_ext::Error> {
+
    fn is_ancestor_of(&self, _ancestor: Oid, _head: Oid) -> Result<bool, crate::git2::Error> {
        Ok(true)
    }

-
    fn blob(&self, _oid: Oid) -> Result<git2::Blob, git_ext::Error> {
+
    fn blob(&self, _oid: Oid) -> Result<crate::git2::Blob, crate::git2::Error> {
        todo!()
    }

    fn blob_at<P: AsRef<std::path::Path>>(
        &self,
-
        _oid: git_ext::Oid,
+
        _oid: Oid,
        _path: P,
-
    ) -> Result<git2::Blob, git_ext::Error> {
+
    ) -> Result<crate::git2::Blob, crate::git2::Error> {
        todo!()
    }

    fn reference(
        &self,
        _remote: &RemoteId,
-
        _reference: &git::Qualified,
-
    ) -> Result<git2::Reference, git_ext::Error> {
+
        _reference: &crate::git::fmt::Qualified,
+
    ) -> Result<crate::git2::Reference, crate::git2::Error> {
        todo!()
    }

    fn reference_oid(
        &self,
        remote: &RemoteId,
-
        reference: &git::Qualified,
-
    ) -> Result<git_ext::Oid, git::raw::Error> {
+
        reference: &crate::git::fmt::Qualified,
+
    ) -> Result<Oid, crate::git2::Error> {
        let not_found = || {
-
            git::raw::Error::new(
-
                git::raw::ErrorCode::NotFound,
-
                git::raw::ErrorClass::Reference,
+
            crate::git2::Error::new(
+
                crate::git2::ErrorCode::NotFound,
+
                crate::git2::ErrorClass::Reference,
                format!("could not find {reference} for {remote}"),
            )
        };
@@ -283,8 +285,8 @@ impl ReadRepository for MockRepository {

    fn references_glob(
        &self,
-
        _pattern: &git::PatternStr,
-
    ) -> Result<Vec<(fmt::Qualified, Oid)>, git::ext::Error> {
+
        _pattern: &crate::git::fmt::refspec::PatternStr,
+
    ) -> Result<Vec<(fmt::Qualified, Oid)>, crate::git2::Error> {
        todo!()
    }

@@ -300,7 +302,7 @@ impl ReadRepository for MockRepository {
        self.canonical_identity_head()
    }

-
    fn identity_head_of(&self, _remote: &RemoteId) -> Result<Oid, git::ext::Error> {
+
    fn identity_head_of(&self, _remote: &RemoteId) -> Result<Oid, crate::git2::Error> {
        Ok(self.doc.commit)
    }

@@ -316,13 +318,13 @@ impl ReadRepository for MockRepository {
        Ok(self.doc.commit)
    }

-
    fn merge_base(&self, _left: &Oid, _right: &Oid) -> Result<Oid, git::ext::Error> {
+
    fn merge_base(&self, _left: &Oid, _right: &Oid) -> Result<Oid, crate::git2::Error> {
        todo!()
    }
}

impl WriteRepository for MockRepository {
-
    fn raw(&self) -> &git2::Repository {
+
    fn raw(&self) -> &crate::git2::Repository {
        todo!()
    }

@@ -342,7 +344,7 @@ impl WriteRepository for MockRepository {
        todo!()
    }

-
    fn set_user(&self, _info: &git::UserInfo) -> Result<(), Error> {
+
    fn set_user(&self, _info: &crate::git::UserInfo) -> Result<(), Error> {
        todo!()
    }
}