Radish alpha
r
rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt
Git libraries for Radicle
Radicle
Git
git-storage: remove crate
Fintan Halpenny committed 7 months ago
commit 3e3c5e0f0e76cc86ed1930fce5649bd1aa0d6b59
parent b3af979
25 files changed +0 -2095
modified Cargo.lock
@@ -299,32 +299,6 @@ dependencies = [
]

[[package]]
-
name = "git-storage"
-
version = "0.1.0"
-
dependencies = [
-
 "either",
-
 "git2",
-
 "globset",
-
 "libc",
-
 "libgit2-sys",
-
 "parking_lot",
-
 "radicle-git-ext",
-
 "radicle-std-ext",
-
 "thiserror",
-
]
-

-
[[package]]
-
name = "git-storage-test"
-
version = "0.1.0"
-
dependencies = [
-
 "git-storage",
-
 "git2",
-
 "proptest",
-
 "radicle-git-ext",
-
 "test-helpers",
-
]
-

-
[[package]]
name = "git2"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
modified Cargo.toml
@@ -5,8 +5,5 @@ members = [
  "radicle-surf",
  "test",
]
-
exclude = [
-
  "git-storage",
-
]
resolver = "2"
package.version = "0.18.0"

\ No newline at end of file
deleted git-storage/Cargo.toml
@@ -1,29 +0,0 @@
-
[package]
-
name = "git-storage"
-
version = "0.1.0"
-
authors = ["Kim Altintop <kim@eagain.st>", "Fintan Halpenny <fintan.halpenny@gmail.com>"]
-
edition = "2021"
-
license = "GPL-3.0-or-later"
-

-
[dependencies]
-
globset = "0.4"
-
libc = "0.2"
-
parking_lot = "0.12"
-
thiserror = "1"
-
either = "1.8.0"
-

-
[dependencies.git2]
-
version = "0.19"
-
default-features = false
-
features = ["vendored-libgit2"]
-

-
[dependencies.libgit2-sys]
-
version = ">= 0.14.2"
-
default-features = false
-
features = ["vendored"]
-

-
[dependencies.radicle-git-ext]
-
path = "../radicle-git-ext"
-

-
[dependencies.radicle-std-ext]
-
path = "../radicle-std-ext"
deleted git-storage/src/backend.rs
@@ -1,5 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
pub mod read;
-
pub mod write;
deleted git-storage/src/backend/read.rs
@@ -1,166 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{fmt, path::Path};
-

-
use git_ext::{error::is_not_found_err, ref_format, Oid};
-
use std_ext::result::ResultExt as _;
-

-
use crate::{
-
    glob,
-
    odb::{self, Blob, Commit, Object, Tag, Tree},
-
    refdb::{self, Reference, References, ReferencesGlob},
-
};
-

-
/// A read-only storage backend for accessing git's odb and refdb.
-
///
-
/// For access to the odb see [`odb::Read`].
-
/// For access to the refdb see [`refdb::Read`].
-
///
-
/// To construct the `Read` storage use [`Read::open`].
-
pub struct Read {
-
    pub(super) raw: git2::Repository,
-
}
-

-
impl Read {
-
    /// Open the [`Read`] storage using the `path` provided.
-
    ///
-
    /// The storage **must** exist already. To initialise the storage for the
-
    /// first time see [`crate::Write::open`].
-
    ///
-
    /// # Concurrency
-
    ///
-
    /// [`Read`] can be sent between threads, but it can't be shared between
-
    /// threads. _Some_ operations are safe to perform concurrently in much
-
    /// the same way two `git` processes can access the same repository.
-
    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, error::Init> {
-
        crate::init();
-

-
        let raw = git2::Repository::open(path)?;
-

-
        Ok(Self { raw })
-
    }
-
}
-

-
impl fmt::Debug for Read {
-
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-
        f.debug_struct("Read")
-
            .field("raw", &self.raw.path())
-
            .finish()
-
    }
-
}
-

-
pub mod error {
-
    use thiserror::Error;
-

-
    use crate::refdb::error::ParseReference;
-

-
    #[derive(Debug, Error)]
-
    pub enum Init {
-
        #[error(transparent)]
-
        Git(#[from] git2::Error),
-
    }
-

-
    #[derive(Debug, Error)]
-
    pub enum Identifier {
-
        #[error(transparent)]
-
        Git(#[from] git2::Error),
-
    }
-

-
    #[derive(Debug, Error)]
-
    pub enum FindRef {
-
        #[error(transparent)]
-
        Git(#[from] git2::Error),
-
        #[error(transparent)]
-
        Parse(#[from] ParseReference),
-
    }
-
}
-

-
// impls
-

-
impl<'a> refdb::Read for &'a Read {
-
    type FindRef = error::FindRef;
-
    type FindRefs = refdb::error::Iter;
-
    type FindRefOid = git2::Error;
-

-
    type References = References<'a>;
-

-
    fn find_reference<Ref>(&self, reference: Ref) -> Result<Option<Reference>, Self::FindRef>
-
    where
-
        Ref: AsRef<ref_format::RefStr>,
-
    {
-
        let reference = self
-
            .raw
-
            .find_reference(reference.as_ref().as_str())
-
            .map(Some)
-
            .or_matches::<git2::Error, _, _>(is_not_found_err, || Ok(None))?;
-
        Ok(reference.map(Reference::try_from).transpose()?)
-
    }
-

-
    fn find_references<Pat>(&self, reference: Pat) -> Result<Self::References, Self::FindRefs>
-
    where
-
        Pat: AsRef<ref_format::refspec::PatternStr>,
-
    {
-
        Ok(References {
-
            inner: ReferencesGlob {
-
                iter: self.raw.references()?,
-
                glob: glob::RefspecMatcher::from(reference.as_ref().to_owned()),
-
            },
-
        })
-
    }
-

-
    fn find_reference_oid<Ref>(&self, reference: Ref) -> Result<Option<Oid>, Self::FindRefOid>
-
    where
-
        Ref: AsRef<ref_format::RefStr>,
-
    {
-
        self.raw
-
            .refname_to_id(reference.as_ref().as_str())
-
            .map(|oid| Some(Oid::from(oid)))
-
            .or_matches(is_not_found_err, || Ok(None))
-
    }
-
}
-

-
impl odb::Read for Read {
-
    type FindObj = git2::Error;
-
    type FindBlob = git2::Error;
-
    type FindCommit = git2::Error;
-
    type FindTag = git2::Error;
-
    type FindTree = git2::Error;
-

-
    fn find_object(&self, oid: Oid) -> Result<Option<Object>, Self::FindObj> {
-
        self.raw
-
            .find_object(oid.into(), None)
-
            .map(Some)
-
            .or_matches::<git2::Error, _, _>(is_not_found_err, || Ok(None))
-
    }
-

-
    fn find_blob(&self, oid: Oid) -> Result<Option<Blob>, Self::FindBlob> {
-
        self.raw
-
            .find_blob(oid.into())
-
            .map(Some)
-
            .or_matches(is_not_found_err, || Ok(None))
-
    }
-

-
    fn find_commit(&self, oid: Oid) -> Result<Option<Commit>, Self::FindCommit> {
-
        self.raw
-
            .find_commit(oid.into())
-
            .map(Some)
-
            .or_matches(is_not_found_err, || Ok(None))
-
    }
-

-
    fn find_tag(&self, oid: Oid) -> Result<Option<Tag>, Self::FindTag> {
-
        self.raw
-
            .find_tag(oid.into())
-
            .map(Some)
-
            .or_matches(is_not_found_err, || Ok(None))
-
    }
-

-
    fn find_tree(&self, oid: Oid) -> Result<Option<Tree>, Self::FindTree> {
-
        self.raw
-
            .find_tree(oid.into())
-
            .map(Some)
-
            .or_matches(is_not_found_err, || Ok(None))
-
    }
-
}
deleted git-storage/src/backend/write.rs
@@ -1,585 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::path::Path;
-

-
use either::Either;
-

-
use git_ext::{
-
    error::is_not_found_err,
-
    ref_format::{self, Qualified, RefStr, RefString},
-
    Oid,
-
};
-

-
use crate::{
-
    odb,
-
    refdb::{
-
        self, resolve,
-
        write::{previous, Applied, Policy, SymrefTarget, Update, Updated},
-
        Read as _, Reference, Target,
-
    },
-
    signature::UserInfo,
-
};
-

-
use super::read::{self, Read};
-

-
pub mod error;
-

-
/// A read-write storage backend for accessing git's odb and refdb.
-
///
-
/// For read-only access to the odb see [`odb::Read`].
-
/// For write access to the odb see [`odb::Write`].
-
///
-
/// For read-only access to the refdb see [`refdb::Read`].
-
/// For write access to the refdb see [`refdb::Write`].
-
///
-
/// To construct the `Write` storage use [`Read::open`].
-
#[derive(Debug)]
-
pub struct Write {
-
    inner: Read,
-
    info: UserInfo,
-
}
-

-
impl Write {
-
    /// Open the [`Write`] storage, initialising it if it doesn't exist.
-
    ///
-
    /// # Concurrency
-
    ///
-
    /// [`Write`] can be sent between threads, but it can't be shared between
-
    /// threads. _Some_ operations are safe to perform concurrently in much
-
    /// the same way two `git` processes can access the same repository.
-
    pub fn open<P: AsRef<Path>>(path: P, info: UserInfo) -> Result<Self, error::Init> {
-
        crate::init();
-

-
        let path = path.as_ref();
-
        let raw = match git2::Repository::open_bare(path) {
-
            Err(e) if is_not_found_err(&e) => {
-
                let backend = git2::Repository::init_opts(
-
                    path,
-
                    git2::RepositoryInitOptions::new()
-
                        .bare(true)
-
                        .no_reinit(true)
-
                        .external_template(false),
-
                )?;
-
                Ok(backend)
-
            }
-
            Ok(repo) => Ok(repo),
-
            Err(e) => Err(e),
-
        }?;
-

-
        Ok(Self {
-
            inner: Read { raw },
-
            info,
-
        })
-
    }
-
}
-

-
impl Write {
-
    /// Return a read-only handle of the storage.
-
    pub fn read_only(&self) -> &Read {
-
        &self.inner
-
    }
-

-
    /// Return the [`UserInfo`] of the storage.
-
    pub fn info(&self) -> &UserInfo {
-
        &self.info
-
    }
-

-
    fn as_raw(&self) -> &git2::Repository {
-
        &self.inner.raw
-
    }
-
}
-

-
// refdb impls
-

-
impl<'a> refdb::Read for &'a Write {
-
    type FindRef = <&'a Read as refdb::Read>::FindRef;
-
    type FindRefs = <&'a Read as refdb::Read>::FindRefs;
-
    type FindRefOid = <&'a Read as refdb::Read>::FindRefOid;
-

-
    type References = <&'a Read as refdb::Read>::References;
-

-
    fn find_reference<Ref>(&self, reference: Ref) -> Result<Option<Reference>, Self::FindRef>
-
    where
-
        Ref: AsRef<ref_format::RefStr>,
-
    {
-
        self.read_only().find_reference(reference)
-
    }
-

-
    fn find_references<Pat>(&self, reference: Pat) -> Result<Self::References, Self::FindRefs>
-
    where
-
        Pat: AsRef<ref_format::refspec::PatternStr>,
-
    {
-
        self.read_only().find_references(reference)
-
    }
-

-
    fn find_reference_oid<Ref>(&self, reference: Ref) -> Result<Option<Oid>, Self::FindRefOid>
-
    where
-
        Ref: AsRef<ref_format::RefStr>,
-
    {
-
        self.read_only().find_reference_oid(reference)
-
    }
-
}
-

-
impl<'a> refdb::Write for &'a Write {
-
    type UpdateError = error::Update;
-

-
    fn update<'b, U>(&mut self, updates: U) -> Result<refdb::write::Applied<'b>, Self::UpdateError>
-
    where
-
        U: IntoIterator<Item = Update<'b>>,
-
    {
-
        let mut refdb = Transaction::new(self)?;
-
        let mut applied = Applied::default();
-
        for up in updates.into_iter() {
-
            match up {
-
                Update::Direct {
-
                    name,
-
                    target,
-
                    no_ff,
-
                    previous,
-
                    reflog,
-
                } => match refdb.direct(name, target, no_ff, previous, reflog)? {
-
                    Either::Left(update) => applied.rejected.push(update),
-
                    Either::Right(updated) => applied.updated.push(updated),
-
                },
-
                Update::Symbolic {
-
                    name,
-
                    target,
-
                    type_change,
-
                    previous,
-
                    reflog,
-
                } => match refdb.symbolic(name, target, type_change, previous, reflog)? {
-
                    Either::Left(update) => applied.rejected.push(update),
-
                    Either::Right(updated) => applied.updated.extend(updated),
-
                },
-
                Update::Remove { name, previous } => match refdb.remove(name, previous)? {
-
                    Either::Left(update) => applied.rejected.push(update),
-
                    Either::Right(updated) => applied.updated.push(updated),
-
                },
-
            }
-
        }
-
        refdb.commit()?;
-

-
        Ok(applied)
-
    }
-
}
-

-
/// An internal struct combining a [`Write`] and [`git2::Transaction`].
-
// TODO: include optional namespace
-
struct Transaction<'a> {
-
    refdb: &'a Write,
-
    txn: git2::Transaction<'a>,
-
}
-

-
impl<'a> Transaction<'a> {
-
    pub fn new(refdb: &'a Write) -> Result<Self, git2::Error> {
-
        let txn = refdb.inner.raw.transaction()?;
-
        Ok(Self { refdb, txn })
-
    }
-

-
    /// Perform an [`Update::Direct`] within the [`Transaction`].
-
    ///
-
    /// Steps:
-
    /// 1. Get the state of the reference
-
    ///   a. if it did not exist create the source and destination references,
-
    ///      skip the next steps.
-
    ///   b. if it did, then follow the next steps.
-
    ///
-
    /// 2. Guard against the [`previous::Edit`] value, if this fails then reject
-
    /// the [`Update`].
-
    ///
-
    /// 3. Check the fast-forward policy, either aborting,
-
    /// rejecting, or accepting the update
-
    pub fn direct<'b>(
-
        &mut self,
-
        name: Qualified<'b>,
-
        target: Oid,
-
        no_ff: Policy,
-
        previous: previous::Edit,
-
        reflog: String,
-
    ) -> Result<Either<Update<'b>, Updated>, error::Update> {
-
        let prev = self.refdb.find_reference(&name)?;
-
        let given = prev
-
            .as_ref()
-
            .map(|prev| resolve(self.refdb.as_raw(), prev))
-
            .transpose()?;
-
        if let Err(_err) = previous.guard(given) {
-
            return Ok(Either::Left(Update::Direct {
-
                name,
-
                target,
-
                no_ff,
-
                previous,
-
                reflog,
-
            }));
-
        }
-

-
        let not_ff = match given {
-
            Some(prev) => {
-
                if !self.is_ff(&name, target, prev)? {
-
                    Some(prev)
-
                } else {
-
                    None
-
                }
-
            }
-
            None => None,
-
        };
-

-
        match not_ff {
-
            // It wasn't an fast-forward so we check our policy
-
            Some(cur) => match no_ff {
-
                Policy::Abort => Err(error::Update::NonFF {
-
                    name: name.into(),
-
                    new: target,
-
                    cur,
-
                }),
-
                Policy::Reject => Ok(Either::Left(Update::Direct {
-
                    name,
-
                    target,
-
                    no_ff,
-
                    previous,
-
                    reflog,
-
                })),
-
                Policy::Allow => Ok(Either::Right(
-
                    self.direct_edit(&name, target, given, &reflog)?,
-
                )),
-
            },
-
            // It was an fast-forward so we go ahead and make the edit
-
            None => Ok(Either::Right(
-
                self.direct_edit(&name, target, given, &reflog)?,
-
            )),
-
        }
-
    }
-

-
    /// Perform an [`Update::Symbolic`] within the [`Transaction`].
-
    ///
-
    /// Steps:
-
    /// 1. Get the state of the source reference
-
    ///   a. if it did not exist create the source and destination references,
-
    ///      skip the next steps.
-
    ///   b. if it did, then follow the next steps.
-
    ///
-
    /// 2. Guard against the `type_change`, aborting or rejecting depending on
-
    /// the [`Policy`].
-
    ///
-
    /// 3. Get the state of the destination reference.
-
    ///
-
    /// 4. Check the target of the desitination:
-
    ///   a. if it's direct then make the edit depending on the fast-forward
-
    ///      status.
-
    ///   b. if it's symbolic then this is an error.
-
    pub fn symbolic<'b: 'a>(
-
        &mut self,
-
        name: Qualified<'b>,
-
        target: SymrefTarget<'b>,
-
        type_change: Policy,
-
        previous: previous::Edit,
-
        reflog: String,
-
    ) -> Result<Either<Update<'b>, Vec<Updated>>, error::Update> {
-
        let src = self.refdb.find_reference(&name)?;
-
        let prev = src
-
            .as_ref()
-
            .map(|src| refdb::resolve(self.refdb.as_raw(), src))
-
            .transpose()?;
-
        match src {
-
            Some(src) => match src.target {
-
                Target::Direct { .. } if matches!(type_change, Policy::Abort) => {
-
                    Err(error::Update::TypeChange(name.into()))
-
                }
-
                Target::Direct { .. } if matches!(type_change, Policy::Reject) => {
-
                    Ok(Either::Left(Update::Symbolic {
-
                        name,
-
                        target,
-
                        type_change,
-
                        previous,
-
                        reflog,
-
                    }))
-
                }
-
                _ => {
-
                    let dst = self.refdb.find_reference(&target.name)?;
-
                    match dst {
-
                        Some(dst) => match dst.target {
-
                            Target::Direct { oid: dst } => {
-
                                let is_ff = target.target != dst
-
                                    && self.is_ff(&target.name, target.target, dst)?;
-
                                Ok(Either::Right(
-
                                    self.symbolic_edit(name, target, prev, &reflog, is_ff)?,
-
                                ))
-
                            }
-
                            Target::Symbolic { .. } => Err(error::Update::TargetSymbolic(dst.name)),
-
                        },
-
                        None => Ok(Either::Right(
-
                            self.symbolic_edit(name, target, prev, &reflog, true)?,
-
                        )),
-
                    }
-
                }
-
            },
-
            None => Ok(Either::Right(
-
                self.symbolic_edit(name, target, prev, &reflog, true)?,
-
            )),
-
        }
-
    }
-

-
    /// Perform an [`Update::Remove`] within the [`Transaction`].
-
    ///
-
    /// Steps:
-
    /// 1. Get the state of the reference
-
    ///
-
    /// 2. Guard against the [`previous::Remove`] value, if this fails then
-
    /// reject the [`Update`].
-
    ///
-
    /// 3. Remove the reference
-
    ///
-
    /// # Panics
-
    ///
-
    /// The `previous` SHOULD guard against the reference not existing, so this
-
    /// will panic if the previous reference was missing AND passed the
-
    /// `previous::guard`.
-
    pub fn remove<'b>(
-
        &mut self,
-
        name: Qualified<'b>,
-
        previous: previous::Remove,
-
    ) -> Result<Either<Update<'b>, Updated>, error::Update> {
-
        let prev = self.refdb.find_reference(&name)?;
-
        let given = prev
-
            .as_ref()
-
            .map(|prev| resolve(self.refdb.as_raw(), prev))
-
            .transpose()?;
-
        if let Err(_err) = previous.guard(given) {
-
            Ok(Either::Left(Update::Remove { name, previous }))
-
        } else {
-
            match given {
-
                None => {
-
                    panic!("BUG: the previous value for a reference to be removed was not given, but its existence SHOULD be guarded")
-
                }
-
                Some(previous) => Ok(Either::Right(self.remove_edit(name, previous)?)),
-
            }
-
        }
-
    }
-

-
    pub fn lock<R>(&mut self, reference: R) -> Result<(), error::Transaction>
-
    where
-
        R: AsRef<RefStr>,
-
    {
-
        let reference = reference.as_ref();
-
        self.txn
-
            .lock_ref(reference.as_str())
-
            .map_err(|err| error::Transaction::Lock {
-
                reference: reference.to_owned(),
-
                source: err,
-
            })
-
    }
-

-
    pub fn commit(self) -> Result<(), error::Transaction> {
-
        self.txn
-
            .commit()
-
            .map_err(|err| error::Transaction::Commit { source: err })
-
    }
-

-
    pub fn direct_edit<R>(
-
        &mut self,
-
        reference: R,
-
        target: Oid,
-
        prev: Option<Oid>,
-
        reflog: &str,
-
    ) -> Result<Updated, error::Transaction>
-
    where
-
        R: AsRef<RefStr>,
-
    {
-
        let reference = reference.as_ref();
-
        self.lock(reference)?;
-
        let info = self.refdb.info();
-
        let sig = info
-
            .signature()
-
            .map_err(|err| error::Transaction::Signature {
-
                name: info.name.to_owned(),
-
                email: info.email.to_owned(),
-
                source: err,
-
            })?;
-
        self.txn
-
            .set_target(reference.as_str(), target.into(), Some(&sig), reflog)
-
            .map_err(|err| error::Transaction::SetDirect {
-
                reference: reference.to_owned(),
-
                target,
-
                source: err,
-
            })?;
-

-
        Ok(Updated::Direct {
-
            name: reference.to_owned(),
-
            target,
-
            previous: prev,
-
        })
-
    }
-

-
    pub fn symbolic_edit<R>(
-
        &mut self,
-
        reference: R,
-
        target: SymrefTarget<'a>,
-
        prev: Option<Oid>,
-
        reflog: &str,
-
        is_ff: bool,
-
    ) -> Result<Vec<Updated>, error::Transaction>
-
    where
-
        R: AsRef<RefStr>,
-
    {
-
        let reference = reference.as_ref();
-
        self.lock(reference)?;
-
        self.lock(&target.name)?;
-

-
        let SymrefTarget {
-
            name: dst,
-
            target: dst_target,
-
        } = target;
-

-
        let mut edits = Vec::with_capacity(2);
-
        let info = self.refdb.info();
-
        let sig = info
-
            .signature()
-
            .map_err(|err| error::Transaction::Signature {
-
                name: info.name.to_owned(),
-
                email: info.email.to_owned(),
-
                source: err,
-
            })?;
-
        if is_ff {
-
            let direct = self.direct_edit(&dst, dst_target, prev, reflog)?;
-
            edits.push(direct);
-
        }
-

-
        self.txn
-
            .set_symbolic_target(reference.as_str(), dst.as_str(), Some(&sig), reflog)
-
            .map_err(|err| error::Transaction::SetSymbolic {
-
                reference: reference.to_owned(),
-
                target: dst.clone().into(),
-
                source: err,
-
            })?;
-
        edits.push(Updated::Symbolic {
-
            name: reference.to_owned(),
-
            target: dst.into(),
-
            previous: prev,
-
        });
-
        Ok(edits)
-
    }
-

-
    pub fn remove_edit<R>(
-
        &mut self,
-
        reference: R,
-
        previous: Oid,
-
    ) -> Result<Updated, error::Transaction>
-
    where
-
        R: AsRef<RefStr>,
-
    {
-
        let reference = reference.as_ref();
-
        self.lock(reference)?;
-
        self.txn
-
            .remove(reference.as_str())
-
            .map_err(|err| error::Transaction::Remove {
-
                reference: reference.to_owned(),
-
                source: err,
-
            })?;
-
        Ok(Updated::Removed {
-
            name: reference.to_owned(),
-
            previous,
-
        })
-
    }
-

-
    fn is_ff<R>(&self, name: R, target: Oid, prev: Oid) -> Result<bool, error::Transaction>
-
    where
-
        R: AsRef<RefStr>,
-
    {
-
        self.refdb
-
            .inner
-
            .raw
-
            .graph_descendant_of(target.into(), prev.into())
-
            .map_err(|err| error::Transaction::Ancestry {
-
                name: name.as_ref().to_owned(),
-
                new: target,
-
                old: prev,
-
                source: err,
-
            })
-
    }
-
}
-

-
// odb impls
-

-
impl odb::Read for Write {
-
    type FindObj = <Read as odb::Read>::FindObj;
-
    type FindBlob = <Read as odb::Read>::FindBlob;
-
    type FindCommit = <Read as odb::Read>::FindCommit;
-
    type FindTag = <Read as odb::Read>::FindTag;
-
    type FindTree = <Read as odb::Read>::FindTree;
-

-
    fn find_object(&self, oid: Oid) -> Result<Option<crate::Object>, Self::FindObj> {
-
        self.read_only().find_object(oid)
-
    }
-

-
    fn find_blob(&self, oid: Oid) -> Result<Option<git2::Blob>, Self::FindBlob> {
-
        self.read_only().find_blob(oid)
-
    }
-

-
    fn find_commit(&self, oid: Oid) -> Result<Option<git2::Commit>, Self::FindCommit> {
-
        self.read_only().find_commit(oid)
-
    }
-

-
    fn find_tag(&self, oid: Oid) -> Result<Option<git2::Tag>, Self::FindTag> {
-
        self.read_only().find_tag(oid)
-
    }
-

-
    fn find_tree(&self, oid: Oid) -> Result<Option<git2::Tree>, Self::FindTree> {
-
        self.read_only().find_tree(oid)
-
    }
-
}
-

-
impl odb::Write for Write {
-
    type WriteBlob = git2::Error;
-
    type WriteCommit = git2::Error;
-
    type WriteTag = git2::Error;
-
    type WriteTree = git2::Error;
-

-
    fn write_blob(&self, data: &[u8]) -> Result<Oid, Self::WriteBlob> {
-
        self.as_raw().blob(data).map(Oid::from)
-
    }
-

-
    fn write_commit(
-
        &self,
-
        tree: &odb::Tree,
-
        parents: &[&odb::Commit<'_>],
-
        message: &str,
-
    ) -> Result<Oid, Self::WriteCommit> {
-
        let author = self.info.signature()?;
-
        self.as_raw()
-
            .commit(None, &author, &author, message, tree, parents)
-
            .map(Oid::from)
-
    }
-

-
    fn write_tag<R>(
-
        &self,
-
        name: R,
-
        target: &odb::Object,
-
        message: &str,
-
    ) -> Result<Oid, Self::WriteTag>
-
    where
-
        R: AsRef<RefStr>,
-
    {
-
        let tagger = self.info.signature()?;
-
        self.as_raw()
-
            .tag_annotation_create(name.as_ref().as_str(), target, &tagger, message)
-
            .map(Oid::from)
-
    }
-

-
    fn write_tree(&self, builder: odb::TreeBuilder) -> Result<Oid, Self::WriteTree> {
-
        let repo = self.as_raw();
-
        let mut tree = repo.treebuilder(None)?;
-
        for entry in builder.iter() {
-
            match entry {
-
                odb::TreeEntry::Insert {
-
                    name,
-
                    oid,
-
                    filemode,
-
                } => tree.insert(name, (*oid).into(), *filemode).map(|_| ())?,
-
                odb::TreeEntry::Remove { name } => tree.remove(name)?,
-
            }
-
        }
-
        tree.write().map(Oid::from)
-
    }
-
}
deleted git-storage/src/backend/write/error.rs
@@ -1,80 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use thiserror::Error;
-

-
use super::*;
-

-
#[derive(Debug, Error)]
-
pub enum Init {
-
    #[error(transparent)]
-
    Git(#[from] git2::Error),
-
}
-

-
#[derive(Debug, Error)]
-
pub enum Transaction {
-
    #[error("error determining if {old} is an ancestor of {new} in within {name}")]
-
    Ancestry {
-
        name: RefString,
-
        new: Oid,
-
        old: Oid,
-
        #[source]
-
        source: git2::Error,
-
    },
-
    #[error("error committing update for storage")]
-
    Commit {
-
        #[source]
-
        source: git2::Error,
-
    },
-
    #[error("error locking reference '{reference}' when attempting to update storage")]
-
    Lock {
-
        reference: RefString,
-
        #[source]
-
        source: git2::Error,
-
    },
-
    #[error("error obtaining signature for '{name}' '{email}'")]
-
    Signature {
-
        name: String,
-
        email: String,
-
        #[source]
-
        source: git2::Error,
-
    },
-
    #[error("error setting the direct reference '{reference}' to the target '{target}'")]
-
    SetDirect {
-
        reference: RefString,
-
        target: Oid,
-
        #[source]
-
        source: git2::Error,
-
    },
-
    #[error("error setting the symbolic reference '{reference}' to the target '{target}'")]
-
    SetSymbolic {
-
        reference: RefString,
-
        target: RefString,
-
        #[source]
-
        source: git2::Error,
-
    },
-
    #[error("error removing the reference '{reference}'")]
-
    Remove {
-
        reference: RefString,
-
        #[source]
-
        source: git2::Error,
-
    },
-
}
-

-
#[derive(Debug, Error)]
-
pub enum Update {
-
    #[error("non-fast-forward update of {name} (current: {cur}, new: {new})")]
-
    NonFF { name: RefString, new: Oid, cur: Oid },
-
    #[error(transparent)]
-
    FindRef(#[from] read::error::FindRef),
-
    #[error(transparent)]
-
    Git(#[from] git2::Error),
-
    #[error("the symref target {0} is itself a symref")]
-
    TargetSymbolic(RefString),
-
    #[error(transparent)]
-
    Transaction(#[from] Transaction),
-
    #[error("rejected changing type of reference '{0}' from symbolic to direct")]
-
    TypeChange(RefString),
-
}
deleted git-storage/src/glob.rs
@@ -1,39 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::path::Path;
-

-
use git_ext::ref_format::refspec::PatternString;
-

-
pub trait Pattern {
-
    fn matches<P: AsRef<Path>>(&self, path: P) -> bool;
-
}
-

-
impl Pattern for globset::GlobMatcher {
-
    fn matches<P: AsRef<Path>>(&self, path: P) -> bool {
-
        self.is_match(path)
-
    }
-
}
-

-
impl Pattern for globset::GlobSet {
-
    fn matches<P: AsRef<Path>>(&self, path: P) -> bool {
-
        self.is_match(path)
-
    }
-
}
-

-
#[derive(Clone, Debug)]
-
pub struct RefspecMatcher(globset::GlobMatcher);
-

-
impl From<PatternString> for RefspecMatcher {
-
    fn from(pat: PatternString) -> Self {
-
        Self(globset::Glob::new(pat.as_str()).unwrap().compile_matcher())
-
    }
-
}
-

-
impl Pattern for RefspecMatcher {
-
    fn matches<P: AsRef<Path>>(&self, path: P) -> bool {
-
        self.0.is_match(path)
-
    }
-
}
deleted git-storage/src/lib.rs
@@ -1,77 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
//! # `git-storage`
-
//!
-
//! This crate provides access to git [references][refs] and [objects][objs].
-
//!
-
//! To first initialise the storage use [`Write::open`].
-
//!
-
//! After the storage is initialised, use [`Write::open`] or [`Read::open`] for
-
//! read-write or read-only access to the underlying storage. These structs will
-
//! implement the traits below depending on their access levels.
-
//!
-
//! ## Read-only access
-
//!
-
//! * [`refdb::Read`]
-
//! * [`odb::Read`]
-
//!
-
//! ## Read-write access
-
//!
-
//! * [`refdb::Read`]
-
//! * [`refdb::Write`]
-
//! * [`odb::Read`]
-
//! * [`odb::Write`]
-
//!
-
//! ## Concurrency
-
//!
-
//!
-
//! [`Read`] and [`Write`] can be sent between threads, but it can't be shared
-
//! between threads. _Some_ operations are safe to perform concurrently in much
-
//! the same way two `git` processes can access the same repository.
-
//!
-
//! [refs]: https://git-scm.com/book/en/v2/Git-Internals-Git-References
-
//! [objs]: https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
-

-
extern crate radicle_git_ext as git_ext;
-
extern crate radicle_std_ext as std_ext;
-

-
pub mod glob;
-

-
pub mod refdb;
-
pub use refdb::{Applied, Reference, SymrefTarget, Target, Update, Updated};
-

-
pub mod odb;
-
pub use odb::{Blob, Commit, Object, Tag, Tree};
-

-
mod backend;
-
pub use backend::{
-
    read::{self, Read},
-
    write::{self, Write},
-
};
-

-
pub mod signature;
-

-
/// Initialise the git backend.
-
///
-
/// **SHOULD** be called before all accesses to git functionality.
-
pub fn init() {
-
    use libc::c_int;
-
    use libgit2_sys as raw_git;
-
    use std::sync::Once;
-

-
    static INIT: Once = Once::new();
-

-
    unsafe {
-
        INIT.call_once(|| {
-
            let ret =
-
                raw_git::git_libgit2_opts(raw_git::GIT_OPT_SET_MWINDOW_FILE_LIMIT as c_int, 256);
-
            if ret < 0 {
-
                panic!(
-
                    "error setting libgit2 option: {}",
-
                    git2::Error::last_error(ret).unwrap()
-
                )
-
            }
-
        })
-
    }
-
}
deleted git-storage/src/odb.rs
@@ -1,165 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
//! The `odb` is separated into two traits: [`Read`] and [`Write`], providing
-
//! access to [git objects][objs].
-
//!
-
//! The [`Read`] trait provides functions for read-only access to the odb.
-
//! The [`Write`] trait provides functions for read and write access to the
-
//! odb, thus it implies the [`Read`] trait.
-
//!
-
//! The reason for separating these types of actions out is that one can infer
-
//! what kind of access a function has to the odb by looking at which trait it
-
//! is using.
-
//!
-
//! For implementations of these traits, this crate provides [`crate::Read`] and
-
//! [`crate::Write`] structs.
-
//!
-
//! [objs]: https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
-

-
// TODO: this doesn't abstract over the git2 types very well, but it's too much
-
// hassle to massage that right now.
-

-
use std::{
-
    error::Error,
-
    path::{Path, PathBuf},
-
};
-

-
pub use git2::{Blob, Commit, Object, Tag, Tree};
-

-
use git_ext::Oid;
-

-
pub mod read;
-
pub use read::Read;
-

-
pub mod write;
-
pub use write::Write;
-

-
/// A `TreeBuilder` represents a series of operations to build a git tree, see
-
/// *Tree Objects* [here][git-objects].
-
///
-
/// The [`TreeEntry`] variants represent a limited set of the actions one can
-
/// perform [git-objects]: <https://git-scm.com/book/en/v2/Git-Internals-Git-Objects>
-
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
-
pub struct TreeBuilder {
-
    entries: Vec<TreeEntry>,
-
}
-

-
impl TreeBuilder {
-
    pub fn new() -> Self {
-
        Self {
-
            entries: Vec::new(),
-
        }
-
    }
-

-
    /// [`TreeBuilder::insert`] creates a [`TreeEntry::Insert`] entry in the
-
    /// `TreeBuilder`.
-
    pub fn insert<P, M>(mut self, name: P, oid: Oid, mode: M) -> Self
-
    where
-
        P: AsRef<Path>,
-
        M: Into<i32>,
-
    {
-
        let name = name.as_ref().to_path_buf();
-
        let filemode = mode.into();
-
        self.entries.push(TreeEntry::Insert {
-
            name,
-
            oid,
-
            filemode,
-
        });
-
        self
-
    }
-

-
    /// [`TreeBuilder::remove`] creates a [`TreeEntry::Remove`] entry in the
-
    /// `TreeBuilder`.
-
    pub fn remove<P>(mut self, name: P) -> Self
-
    where
-
        P: AsRef<Path>,
-
    {
-
        let name = name.as_ref().to_path_buf();
-
        self.entries.push(TreeEntry::Remove { name });
-
        self
-
    }
-

-
    /// Iterate over the [`TreeEntry`]'s found in the `TreeBuilder`.
-
    pub fn iter(&self) -> impl Iterator<Item = &TreeEntry> {
-
        self.entries.iter()
-
    }
-
}
-

-
/// A `TreeEntry` represents a single operation within a [`TreeBuilder`].
-
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-
pub enum TreeEntry {
-
    /// Insert the `oid` under the `name` path in the resulting tree.
-
    /// The `filemode` determines the file mode for the file found at `name`.
-
    Insert {
-
        name: PathBuf,
-
        oid: Oid,
-
        filemode: i32,
-
    },
-
    /// Remove the `name` path from the resulting tree.
-
    Remove { name: PathBuf },
-
}
-

-
/// Find the [`Object`] corresponding to the given `oid`.
-
///
-
/// Will fail if the object does not exist.
-
pub fn object<S>(storage: &S, oid: Oid) -> Result<Object, S::FindObj>
-
where
-
    S: Read<FindObj = git2::Error>,
-
{
-
    storage
-
        .find_object(oid)?
-
        .ok_or_else(|| not_found("object", oid))
-
}
-

-
/// Find the [`Blob`] corresponding to the given `oid`.
-
///
-
/// Will fail if the blob does not exist.
-
pub fn blob<S>(storage: &S, oid: Oid) -> Result<Blob, S::FindBlob>
-
where
-
    S: Read<FindBlob = git2::Error>,
-
{
-
    storage
-
        .find_blob(oid)?
-
        .ok_or_else(|| not_found("blob", oid))
-
}
-

-
/// Find the [`Commit`] corresponding to the given `oid`.
-
///
-
/// Will fail if the commit does not exist.
-
pub fn commit<S>(storage: &S, oid: Oid) -> Result<Commit, S::FindCommit>
-
where
-
    S: Read<FindCommit = git2::Error>,
-
{
-
    storage
-
        .find_commit(oid)?
-
        .ok_or_else(|| not_found("commit", oid))
-
}
-

-
/// Find the [`Tag`] corresponding to the given `oid`.
-
///
-
/// Will fail if the tag does not exist.
-
pub fn tag<S>(storage: &S, oid: Oid) -> Result<Tag, S::FindTag>
-
where
-
    S: Read<FindTag = git2::Error>,
-
{
-
    storage.find_tag(oid)?.ok_or_else(|| not_found("tag", oid))
-
}
-

-
/// Find the [`Tree`] corresponding to the given `oid`.
-
///
-
/// Will fail if the tree does not exist.
-
pub fn tree<S>(storage: &S, oid: Oid) -> Result<Tree, S::FindTree>
-
where
-
    S: Read<FindTree = git2::Error>,
-
{
-
    storage
-
        .find_tree(oid)?
-
        .ok_or_else(|| not_found("tree", oid))
-
}
-

-
fn not_found(kind: &str, oid: Oid) -> git2::Error {
-
    use git2::{ErrorClass::*, ErrorCode::*};
-

-
    git2::Error::new(NotFound, Object, format!("could not find {kind} {oid}"))
-
}
deleted git-storage/src/odb/read.rs
@@ -1,49 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
use super::*;
-

-
/// Read-only access to a git refdb.
-
///
-
/// See [`crate::Read`] for an implementation of this trait.
-
pub trait Read {
-
    /// The error type for finding an object in the refdb.
-
    type FindObj: Error + Send + Sync + 'static;
-

-
    /// The error type for finding a blob in the refdb.
-
    type FindBlob: Error + Send + Sync + 'static;
-

-
    /// The error type for finding a commit in the refdb.
-
    type FindCommit: Error + Send + Sync + 'static;
-

-
    /// The error type for finding a tag in the refdb.
-
    type FindTag: Error + Send + Sync + 'static;
-

-
    /// The error type for finding a tree in the refdb.
-
    type FindTree: Error + Send + Sync + 'static;
-

-
    /// Find the [`Object`] corresponding to the given `oid`.
-
    ///
-
    /// Returns `None` if the [`Object`] did not exist.
-
    fn find_object(&self, oid: Oid) -> Result<Option<Object>, Self::FindObj>;
-

-
    /// Find the [`Blob`] corresponding to the given `oid`.
-
    ///
-
    /// Returns `None` if the [`Blob`] did not exist.
-
    fn find_blob(&self, oid: Oid) -> Result<Option<Blob>, Self::FindBlob>;
-

-
    /// Find the [`Commit`] corresponding to the given `oid`.
-
    ///
-
    /// Returns `None` if the [`Commit`] did not exist.
-
    fn find_commit(&self, oid: Oid) -> Result<Option<Commit>, Self::FindCommit>;
-

-
    /// Find the [`Tag`] corresponding to the given `oid`.
-
    ///
-
    /// Returns `None` if the [`Tag`] did not exist.
-
    fn find_tag(&self, oid: Oid) -> Result<Option<Tag>, Self::FindTag>;
-

-
    /// Find the [`Object`] corresponding to the given `oid`.
-
    ///
-
    /// Returns `None` if the [`Tag`] did not exist.
-
    fn find_tree(&self, oid: Oid) -> Result<Option<Tree>, Self::FindTree>;
-
}
deleted git-storage/src/odb/write.rs
@@ -1,61 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
use std::error::Error;
-

-
use git_ext::{ref_format::RefStr, Oid};
-

-
use super::{Commit, Object, Read, Tree, TreeBuilder};
-

-
/// Read-write access to a git odb.
-
///
-
/// See [`crate::Write`] for an implementation of this trait.
-
pub trait Write: Read {
-
    /// The error type for writing a blob to the odb.
-
    type WriteBlob: Error + Send + Sync + 'static;
-

-
    /// The error type for writing a commit to the odb.
-
    type WriteCommit: Error + Send + Sync + 'static;
-

-
    /// The error type for writing a tag to the odb.
-
    type WriteTag: Error + Send + Sync + 'static;
-

-
    /// The error type for writing a tree to the odb.
-
    type WriteTree: Error + Send + Sync + 'static;
-

-
    /// Write a [`super::Blob`] containing the `data` provided.
-
    fn write_blob(&self, data: &[u8]) -> Result<Oid, Self::WriteBlob>;
-

-
    /// Write a [`Commit`] that points to the given `tree` and has the provided
-
    /// `parents`.
-
    ///
-
    /// The signature of the [`Commit`] is expected to be provided by the
-
    /// implementor of the trait.
-
    ///
-
    /// The commit will not be associated with any reference. If this is
-
    /// required then you can use the [`Oid`] as the target for a
-
    /// [`crate::refdb::Update`].
-
    fn write_commit(
-
        &self,
-
        tree: &Tree,
-
        parents: &[&Commit<'_>],
-
        message: &str,
-
    ) -> Result<Oid, Self::WriteCommit>;
-

-
    /// Write a [`super::Tag`] that points to the given `target`.
-
    ///
-
    /// The signature of the [`super::Tag`] is expected to be provided by the
-
    /// implementor of the trait.
-
    ///
-
    /// No reference is created, however, the `name` is used for naming the
-
    /// [`super::Tag`] object.
-
    ///
-
    /// If a reference is required then you can use the [`Oid`] as the target
-
    /// for a [`crate::refdb::Update`].
-
    fn write_tag<R>(&self, name: R, target: &Object, message: &str) -> Result<Oid, Self::WriteTag>
-
    where
-
        R: AsRef<RefStr>;
-

-
    /// Write a [`super::Tree`] using the provided `builder`.
-
    fn write_tree(&self, builder: TreeBuilder) -> Result<Oid, Self::WriteTree>;
-
}
deleted git-storage/src/refdb.rs
@@ -1,124 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
//! The `refdb` is separated into two traits: [`Read`] and [`Write`], providing
-
//! access to [git references][refs].
-
//!
-
//! The [`Read`] trait provides functions for read-only access to the refdb.
-
//! The [`Write`] trait provides functions for read and write access to the
-
//! refdb, thus it implies the [`Read`] trait.
-
//!
-
//! The reason for separating these types of actions out is that one can infer
-
//! what kind of access a function has to the refdb by looking at which trait it
-
//! is using.
-
//!
-
//! For implementations of these traits, this crate provides [`crate::Read`] and
-
//! [`crate::Write`] structs.
-
//!
-
//! [refs]: https://git-scm.com/book/en/v2/Git-Internals-Git-References
-

-
use std::fmt::Debug;
-

-
use git_ext::{ref_format::RefString, Oid};
-

-
pub mod iter;
-
pub use iter::{References, ReferencesGlob};
-

-
pub mod read;
-
pub use read::Read;
-

-
pub mod write;
-
pub use write::{previous, Applied, Policy, SymrefTarget, Update, Updated, Write};
-

-
/// A read-only structure representing a git reference.
-
///
-
/// References can be constructed by using the [`Read`] functions:
-
///   * [`Read::find_reference`]
-
///   * [`Read::find_references`]
-
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-
pub struct Reference {
-
    /// The name of the reference.
-
    pub name: RefString,
-
    /// The target of the reference.
-
    pub target: Target,
-
}
-

-
impl<'a> TryFrom<git2::Reference<'a>> for Reference {
-
    type Error = error::ParseReference;
-

-
    fn try_from(value: git2::Reference) -> Result<Self, Self::Error> {
-
        use error::ParseReference;
-

-
        let name = value
-
            .name()
-
            .ok_or(ParseReference::InvalidUtf8)
-
            .and_then(|name| RefString::try_from(name).map_err(ParseReference::from))?;
-
        let target = match value.target() {
-
            None => {
-
                let name = value
-
                    .symbolic_target()
-
                    .ok_or(ParseReference::InvalidUtf8)
-
                    .and_then(|name| RefString::try_from(name).map_err(ParseReference::from))?;
-
                name.into()
-
            }
-
            Some(oid) => oid.into(),
-
        };
-

-
        Ok(Reference { name, target })
-
    }
-
}
-

-
/// The target a reference points to.
-
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-
pub enum Target {
-
    /// The reference points directly at an `oid`.
-
    Direct { oid: Oid },
-
    /// The reference is symbolic and points to another reference.
-
    Symbolic { name: RefString },
-
}
-

-
impl From<Oid> for Target {
-
    fn from(oid: Oid) -> Self {
-
        Self::Direct { oid }
-
    }
-
}
-

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

-
impl From<RefString> for Target {
-
    fn from(name: RefString) -> Self {
-
        Self::Symbolic { name }
-
    }
-
}
-

-
pub mod error {
-
    use thiserror::Error;
-

-
    #[derive(Debug, Error)]
-
    pub enum ParseReference {
-
        #[error("reference name did contain valid UTF-8 bytes")]
-
        InvalidUtf8,
-
        #[error(transparent)]
-
        RefString(#[from] radicle_git_ext::ref_format::Error),
-
    }
-

-
    #[derive(Debug, Error)]
-
    pub enum Iter {
-
        #[error(transparent)]
-
        Git(#[from] git2::Error),
-
        #[error(transparent)]
-
        Parse(#[from] ParseReference),
-
    }
-
}
-

-
/// Utility function for resolving a [`Reference`] to its [`Oid`]
-
pub(crate) fn resolve(repo: &git2::Repository, r: &Reference) -> Result<Oid, git2::Error> {
-
    match &r.target {
-
        Target::Direct { oid } => Ok(*oid),
-
        Target::Symbolic { name } => repo.refname_to_id(name.as_str()).map(Oid::from),
-
    }
-
}
deleted git-storage/src/refdb/iter.rs
@@ -1,49 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
//! Iterator adaptors over [`git2::References`].
-

-
use std::fmt::Debug;
-

-
use crate::glob;
-

-
use super::{error, Reference};
-

-
/// Iterator for [`Reference`]s where the inner [`glob::Pattern`] supplied
-
/// filters out any non-matching reference names.
-
pub struct References<'a> {
-
    pub(crate) inner: ReferencesGlob<'a, glob::RefspecMatcher>,
-
}
-

-
impl<'a> Iterator for References<'a> {
-
    type Item = Result<Reference, error::Iter>;
-

-
    fn next(&mut self) -> Option<Self::Item> {
-
        self.inner.next()
-
    }
-
}
-

-
pub struct ReferencesGlob<'a, G: glob::Pattern + Debug> {
-
    pub(crate) iter: git2::References<'a>,
-
    pub(crate) glob: G,
-
}
-

-
impl<'a, G: glob::Pattern + Debug> Iterator for ReferencesGlob<'a, G> {
-
    type Item = Result<Reference, error::Iter>;
-

-
    fn next(&mut self) -> Option<Self::Item> {
-
        for reference in &mut self.iter {
-
            match reference {
-
                Ok(reference) => match reference.name() {
-
                    Some(name) if self.glob.matches(name) => {
-
                        return Some(Reference::try_from(reference).map_err(error::Iter::from))
-
                    }
-
                    _ => continue,
-
                },
-

-
                Err(e) => return Some(Err(e.into())),
-
            }
-
        }
-
        None
-
    }
-
}
deleted git-storage/src/refdb/read.rs
@@ -1,45 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
use std::error::Error;
-

-
use git_ext::{
-
    ref_format::{refspec, RefStr},
-
    Oid,
-
};
-

-
use super::Reference;
-

-
/// Read-only access to a git refdb.
-
///
-
/// See [`crate::Read`] for an implementation of this trait.
-
pub trait Read {
-
    /// The error type for finding a reference in the refdb.
-
    type FindRef: Error + Send + Sync + 'static;
-

-
    /// The error type for finding references in the refdb.
-
    type FindRefs: Error + Send + Sync + 'static;
-

-
    /// The error type for finding reference Oid in the refdb.
-
    type FindRefOid: Error + Send + Sync + 'static;
-

-
    /// Iterator for references returned by `find_references`.
-
    type References: Iterator<Item = Result<Reference, Self::FindRefs>>;
-

-
    /// Find the reference that corresponds to `name`. If the reference does not
-
    /// exist, then `None` is returned.
-
    fn find_reference<Ref>(&self, name: Ref) -> Result<Option<Reference>, Self::FindRef>
-
    where
-
        Ref: AsRef<RefStr>;
-

-
    /// Find the references that match `pattern`.
-
    fn find_references<Pat>(&self, pattern: Pat) -> Result<Self::References, Self::FindRefs>
-
    where
-
        Pat: AsRef<refspec::PatternStr>;
-

-
    /// Find the [`Oid`] for the reference that corresponds to `name`. If the
-
    /// reference does not exist, then `None` is returned.
-
    fn find_reference_oid<Ref>(&self, name: Ref) -> Result<Option<Oid>, Self::FindRefOid>
-
    where
-
        Ref: AsRef<RefStr>;
-
}
deleted git-storage/src/refdb/write.rs
@@ -1,154 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
use std::error::Error;
-

-
use git_ext::{
-
    ref_format::{Qualified, RefString},
-
    Oid,
-
};
-

-
use super::read::Read;
-

-
pub mod previous;
-

-
/// Read-write access to a git refdb.
-
///
-
/// See [`crate::Write`] for an implementation of this trait.
-
pub trait Write: Read {
-
    type UpdateError: Error + Send + Sync + 'static;
-

-
    /// Apply the provided `updates` to the refdb.
-
    ///
-
    /// The implementation of `update` is expected to be transactional. All
-
    /// successful updates are returned in [`Applied::updated`] while rejected
-
    /// updates should be returned in [`Applied::rejected`].
-
    fn update<'a, U>(&mut self, updates: U) -> Result<Applied<'a>, Self::UpdateError>
-
    where
-
        U: IntoIterator<Item = Update<'a>>;
-
}
-

-
/// Perform an update to a reference in the refdb.
-
#[derive(Debug, Clone)]
-
pub enum Update<'a> {
-
    /// Update a direct reference, i.e. one that points directly to an [`Oid`].
-
    Direct {
-
        /// The `name` of the reference.
-
        name: Qualified<'a>,
-
        /// The `target` to point the reference at.
-
        target: Oid,
-
        /// Policy to apply when an [`Update`] would not apply as a
-
        /// fast-forward.
-
        ///
-
        /// An update is a fast-forward iff:
-
        ///
-
        /// 1. A ref with the same name already exists
-
        /// 2. The ref is a direct ref, and the update is a [`Update::Direct`]
-
        /// 3. Both the existing and the update [`Oid`] point to a commit object
-
        ///    without peeling
-
        /// 4. The existing commit is an ancestor of the update commit
-
        ///
-
        /// or:
-
        ///
-
        /// 1. A ref with the same name does not already exist
-
        no_ff: Policy,
-
        /// The expectation of the previous value for the reference before
-
        /// making the update. This allows the update to be rejected in
-
        /// the case of a concurrent modification the reference.
-
        previous: previous::Edit,
-
        /// The [`reflog`][reflog] entry for the update.
-
        ///
-
        /// [reflog]: https://git-scm.com/docs/git-reflog
-
        // TODO: we may want to force the creation of a reflog entry for specific references.
-
        reflog: String,
-
    },
-
    Symbolic {
-
        /// The `name` of the reference.
-
        name: Qualified<'a>,
-
        /// The `target` to point the reference at. Currently, only supports
-
        /// one-level deep.
-
        target: SymrefTarget<'a>,
-
        /// Policy to apply when the ref already exists, but is a direct ref
-
        /// before the update.
-
        type_change: Policy,
-
        /// The expectation of the previous value for the reference before
-
        /// making the update. This allows the update to be rejected in
-
        /// the case of a concurrent modification the reference.
-
        previous: previous::Edit,
-
        /// The [`reflog`][reflog] entry for the update.
-
        ///
-
        /// [reflog]: https://git-scm.com/docs/git-reflog
-
        reflog: String,
-
    },
-
    Remove {
-
        /// The `name` of the reference.
-
        name: Qualified<'a>,
-
        /// The expectation of the previous value for the reference before
-
        /// making the update. This allows the update to be rejected in
-
        /// the case of a concurrent modification the reference.
-
        previous: previous::Remove,
-
    },
-
}
-

-
/// A target of a [symbolic reference][symref].
-
///
-
/// [symref]: https://git-scm.com/docs/git-symbolic-ref
-
#[derive(Debug, Clone)]
-
pub struct SymrefTarget<'a> {
-
    /// The `name` of the symbolic reference.
-
    pub name: Qualified<'a>,
-
    /// The underlying `target` of the symbolic reference.
-
    pub target: Oid,
-
}
-

-
/// The successful result of an [`Update`] applied to the refdb.
-
#[derive(Debug, Clone)]
-
pub enum Updated {
-
    /// The [`Update::Direct`] was succesful.
-
    Direct {
-
        /// The `name` of the reference that was updated.
-
        name: RefString,
-
        /// The new `target` of the reference that was updated.
-
        target: Oid,
-
        /// The previous target of the reference, if it existed.
-
        previous: Option<Oid>,
-
    },
-
    /// The [`Update::Symbolic`] was succesful.
-
    Symbolic {
-
        /// The `name` of the reference that was updated.
-
        name: RefString,
-
        /// The new `target` of the reference that was updated.
-
        target: RefString,
-
        /// The previous peeled target of the reference, if it existed.
-
        previous: Option<Oid>,
-
    },
-
    /// The [`Update::Remove`] was succesful.
-
    Removed {
-
        /// The `name` of the reference that was removed.
-
        name: RefString,
-
        /// The old `target` of the reference that was removed.
-
        previous: Oid,
-
    },
-
}
-

-
/// The outcome of running a [`Write::update`].
-
#[derive(Clone, Default)]
-
pub struct Applied<'a> {
-
    /// Any [`Update`]s that were rejected due to their [`previous::Edit`],
-
    /// [`previous::Remove`], or [`Policy`].
-
    pub rejected: Vec<Update<'a>>,
-
    /// The successful [`Update`]s applied to the refdb.
-
    pub updated: Vec<Updated>,
-
}
-

-
/// The policy to use when guarding against fast-forwards in the case of
-
/// [`Update::Direct`] and type changes in the case of [`Update::Symbolic`].
-
#[derive(Clone, Copy, Debug)]
-
pub enum Policy {
-
    /// Abort the entire transaction.
-
    Abort,
-
    /// Reject this update, but continue the transaction.
-
    Reject,
-
    /// Allow the update.
-
    Allow,
-
}
deleted git-storage/src/refdb/write/previous.rs
@@ -1,132 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
//! The [`Edit`] and [`Remove`] enums provide a mechanism for protecting updates
-
//! during concurrent writes to the refdb. The value is constructed with the
-
//! policy, sometimes providing the expected previous value of the [`Oid`]
-
//! target. If this policy is not satisfied then the update should be rejected.
-

-
use git_ext::Oid;
-

-
use thiserror::Error;
-

-
/// The expectation of the reference's state.
-
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-
pub enum Edit {
-
    /// No requirements are made towards the current value, and the new value is
-
    /// set unconditionally.
-
    Any,
-
    /// The reference must exist and may have any value.
-
    MustExist,
-
    /// Create the ref only, hence the reference must not exist.
-
    MustNotExist,
-
    /// The ref _must_ exist and have the given value.
-
    MustExistAndMatch(Oid),
-
    /// The ref _may_ exist and have the given value, or may not exist at all.
-
    MayExistAndMatch(Oid),
-
}
-

-
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Error)]
-
pub enum EditError {
-
    #[error("the reference does not exist when it was expected to exist")]
-
    DoesNotExist,
-
    #[error("the reference does exist when it was expected not to exist")]
-
    DoesExist,
-
    #[error("the reference does not match - given: '{given}' expected: '{expected}'")]
-
    DoesNotMatch { given: Oid, expected: Oid },
-
}
-

-
impl Edit {
-
    /// Guard against the `given` value using this [`Edit`].
-
    ///
-
    /// The function will return [`Err`] if the `given` value does not pass the
-
    /// policy the policy of the [`Edit`].
-
    ///
-
    /// The `given` value is presumed non-existing if set to `None`, otherwise
-
    /// it is presumed to be existing.
-
    pub fn guard(&self, given: Option<Oid>) -> Result<(), EditError> {
-
        use EditError::*;
-

-
        match self {
-
            Self::Any => Ok(()),
-
            Self::MustExist => {
-
                if given.is_none() {
-
                    Err(DoesNotExist)
-
                } else {
-
                    Ok(())
-
                }
-
            }
-
            Self::MustNotExist => {
-
                if given.is_some() {
-
                    Err(DoesExist)
-
                } else {
-
                    Ok(())
-
                }
-
            }
-
            Self::MustExistAndMatch(expected) => match given {
-
                Some(given) if &given == expected => Ok(()),
-
                Some(given) => Err(DoesNotMatch {
-
                    given,
-
                    expected: *expected,
-
                }),
-
                None => Err(DoesNotExist),
-
            },
-
            Self::MayExistAndMatch(expected) => match given {
-
                Some(given) if &given == expected => Ok(()),
-
                Some(given) => Err(DoesNotMatch {
-
                    given,
-
                    expected: *expected,
-
                }),
-
                None => Ok(()),
-
            },
-
        }
-
    }
-
}
-

-
/// The expectation of the existing reference's state.
-
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-
pub enum Remove {
-
    /// The reference must exist and may have any value.
-
    MustExist,
-
    /// The ref _must_ exist and have the given value.
-
    MustExistAndMatch(Oid),
-
}
-

-
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Error)]
-
pub enum RemoveError {
-
    #[error("the reference does not exist when it was expected to exist")]
-
    DoesNotExist,
-
    #[error("the reference does not match - given: '{given}' expected: '{expected}'")]
-
    DoesNotMatch { given: Oid, expected: Oid },
-
}
-

-
impl Remove {
-
    /// Guard against the `given` value using this [`Remove`].
-
    ///
-
    /// The function will return [`Err`] if the `given` value does not pass the
-
    /// policy the policy of the [`Remove`].
-
    ///
-
    /// The `given` value is presumed non-existing if set to `None`, otherwise
-
    /// it is presumed to be existing.
-
    pub fn guard(&self, given: Option<Oid>) -> Result<(), RemoveError> {
-
        use RemoveError::*;
-

-
        match self {
-
            Self::MustExist => {
-
                if given.is_none() {
-
                    Err(DoesNotExist)
-
                } else {
-
                    Ok(())
-
                }
-
            }
-
            Self::MustExistAndMatch(expected) => match given {
-
                Some(given) if &given == expected => Ok(()),
-
                Some(given) => Err(DoesNotMatch {
-
                    given,
-
                    expected: *expected,
-
                }),
-
                None => Err(DoesNotExist),
-
            },
-
        }
-
    }
-
}
deleted git-storage/src/signature.rs
@@ -1,19 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
-
pub struct UserInfo {
-
    /// Provided `name` of the user.
-
    pub name: String,
-
    /// Proivded `email` of the user. Note that this does not
-
    /// necessarily have to be an email, but will be used as the email
-
    /// field in the [`git2::Signature`].
-
    pub email: String,
-
}
-

-
impl UserInfo {
-
    /// Obtain the [`git2::Signature`] for this `UserInfo`.
-
    pub fn signature(&self) -> Result<git2::Signature, git2::Error> {
-
        git2::Signature::now(&self.name, &self.email)
-
    }
-
}
deleted git-storage/t/Cargo.toml
@@ -1,40 +0,0 @@
-
[package]
-
name = "git-storage-test"
-
version = "0.1.0"
-
edition = "2021"
-
license = "GPL-3.0-or-later"
-

-
publish = false
-

-
[lib]
-
doctest = false
-
test = true
-
doc = false
-

-
[features]
-
test = []
-

-
[dependencies]
-
proptest = "1"
-

-
[dependencies.git-storage]
-
path = ".."
-

-
[dependencies.git2]
-
version = "0.19"
-
default-features = false
-
features = ["vendored-libgit2"]
-

-
[dependencies.radicle-git-ext]
-
path = "../../radicle-git-ext/"
-

-
[dependencies.test-helpers]
-
path = "../../test/test-helpers"
-

-
[dev-dependencies.uuid]
-
version = "1"
-
features = ["v4"]
-

-
[dev-dependencies.radicle-git-ext-test]
-
path = "../../radicle-git-ext/t"
-
features = ["test"]
deleted git-storage/t/src/gen.rs
@@ -1,108 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
use std::path::PathBuf;
-

-
use git_storage::{
-
    odb::{self, write::Write as _},
-
    signature::UserInfo,
-
    Write,
-
};
-
use proptest::prelude::*;
-
use radicle_git_ext::Oid;
-

-
use git2::FileMode;
-

-
/// Represents a file in the git tree but without linking it to the repo yet
-
#[derive(Clone, Debug)]
-
pub struct File {
-
    pub path: PathBuf,
-
    pub inner: Vec<u8>,
-
    pub mode: git2::FileMode,
-
    pub oid: git2::Oid,
-
}
-

-
/// Represents a Tree to be written with the ODB writer.
-
///
-
/// This Tree does not have an explicit link to a repository, linking is
-
/// performed by writing it to the repo using the ODB writer.
-
///
-
/// Used as a replacement of git2::TreeBuilder, which is more complicated to
-
/// build since it requires a repository from the beginning.
-
#[derive(Clone, Debug)]
-
pub struct Tree {
-
    builder: odb::TreeBuilder,
-
    files: Vec<File>,
-
}
-

-
impl Tree {
-
    /// Write the files to the filesystem and the repository `storage`
-
    pub fn write(self, storage: &Write) -> Result<Oid, git2::Error> {
-
        self.write_files(storage)?;
-
        storage.write_tree(self.builder)
-
    }
-

-
    /// Write the files to the filesystem
-
    pub fn write_files(&self, storage: &Write) -> Result<(), git2::Error> {
-
        for file in &self.files {
-
            storage.write_blob(&file.inner)?;
-
        }
-
        Ok(())
-
    }
-
}
-

-
/// Any valid filename
-
pub fn trivial() -> impl Strategy<Value = String> {
-
    "[a-zA-Z0-9]+"
-
}
-

-
pub fn gen_signature() -> impl Strategy<Value = UserInfo> {
-
    trivial().prop_map(move |name| {
-
        UserInfo {
-
            name: name.clone(),
-
            // TODO: is it worth to make this more realistic?
-
            email: format!("{}@{}.com", &name, &name),
-
        }
-
    })
-
}
-

-
pub fn gen_bytes() -> impl Strategy<Value = Vec<u8>> {
-
    any::<Vec<u8>>()
-
}
-

-
pub fn gen_mode() -> impl Strategy<Value = FileMode> {
-
    prop_oneof![Just(FileMode::Blob), Just(FileMode::BlobExecutable)]
-
}
-

-
prop_compose! {
-
    pub fn gen_file()
-
                    (path in trivial(),
-
                     blob in gen_bytes(),
-
                     mode in gen_mode())
-
                    -> File {
-
        let oid = git2::Oid::hash_object(git2::ObjectType::Blob, &blob).unwrap();
-
        File {
-
            path: PathBuf::from(path),
-
            inner: blob,
-
            mode,
-
            oid,
-
        }
-
    }
-
}
-

-
pub fn gen_file_set(max_size: u8) -> impl Strategy<Value = Vec<File>> {
-
    prop::collection::vec(gen_file(), 0..(max_size as usize))
-
}
-

-
/// Generates a [`odb::TreeBuilder`] and a set of [`File`]s.
-
///
-
/// To write the resulting `Tree`, you MUST write the [`File::oid`] to the
-
/// repository first.
-
pub fn gen_tree(max_size: u8) -> impl Strategy<Value = Tree> {
-
    gen_file_set(max_size).prop_map(move |files| {
-
        let builder = files.iter().fold(odb::TreeBuilder::new(), |tree, file| {
-
            tree.insert(file.path.clone(), file.oid.into(), file.mode)
-
        });
-
        Tree { builder, files }
-
    })
-
}
deleted git-storage/t/src/lib.rs
@@ -1,9 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
#[cfg(any(test, feature = "test"))]
-
pub mod gen;
-
#[cfg(test)]
-
mod properties;
-
#[cfg(any(test, feature = "test"))]
-
pub mod tmp;
deleted git-storage/t/src/properties.rs
@@ -1,4 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
mod odb;
deleted git-storage/t/src/properties/odb.rs
@@ -1,108 +0,0 @@
-
// Copyright © 2022 The Radicle Link Contributors
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
use git2::ObjectType;
-

-
use git_storage::{
-
    odb::{Read as _, Write as _},
-
    signature::UserInfo,
-
};
-
use radicle_git_ext::ref_format::RefString;
-
use radicle_git_ext_test::git_ref_format::gen::valid;
-

-
use proptest::prelude::*;
-

-
use crate::{gen, tmp};
-

-
// NOTE: It's enough to check the `Oid`s. If the contents were different the
-
// hash would be different.
-
pub mod prop {
-
    use super::*;
-

-
    pub fn roundtrip_blob(user: UserInfo, bytes: &[u8]) {
-
        let writer = tmp::writer(user);
-

-
        let oid = writer.write_blob(bytes).unwrap();
-

-
        let readback = writer.find_blob(oid).unwrap().unwrap();
-
        assert_eq!(oid, readback.id().into());
-

-
        let readback = writer.find_object(oid).unwrap().unwrap();
-
        assert_eq!(readback.kind(), Some(ObjectType::Blob));
-
    }
-

-
    pub fn roundtrip_tree(user: UserInfo, tree: gen::Tree) {
-
        let writer = tmp::writer(user);
-
        let oid = tree.write(&writer).unwrap();
-
        let readback = writer.find_tree(oid).unwrap().unwrap();
-
        assert_eq!(oid, readback.id().into());
-

-
        let readback = writer.find_object(oid).unwrap().unwrap();
-
        assert_eq!(readback.kind(), Some(ObjectType::Tree));
-
    }
-

-
    pub fn roundtrip_commit(user: UserInfo, tree: gen::Tree, message: &str) {
-
        let writer = tmp::writer(user);
-
        let tree_oid = tree.write(&writer).unwrap();
-
        let tree = writer.find_tree(tree_oid).unwrap().unwrap();
-
        let commit_oid = writer.write_commit(&tree, &[], message).unwrap();
-

-
        let readback = writer.find_commit(commit_oid).unwrap().unwrap();
-
        assert_eq!(readback.id(), commit_oid.into());
-

-
        let readback = writer.find_object(commit_oid).unwrap().unwrap();
-
        assert_eq!(readback.kind(), Some(ObjectType::Commit));
-
    }
-

-
    pub fn roundtrip_tag(user: UserInfo, tree: gen::Tree, tag_name: RefString, message: &str) {
-
        let writer = tmp::writer(user);
-
        let tree_oid = tree.write(&writer).unwrap();
-
        let tree = writer.find_tree(tree_oid).unwrap().unwrap();
-
        let commit_oid = writer.write_commit(&tree, &[], message).unwrap();
-
        let commit_object = writer.find_object(commit_oid).unwrap().unwrap();
-

-
        let tag_oid = writer.write_tag(tag_name, &commit_object, message).unwrap();
-

-
        let readback = writer.find_tag(tag_oid).unwrap().unwrap();
-
        assert_eq!(tag_oid, readback.id().into());
-

-
        let readback = writer.find_object(tag_oid).unwrap().unwrap();
-
        assert_eq!(readback.kind(), Some(ObjectType::Tag));
-
    }
-
}
-

-
proptest! {
-
    #[test]
-
    fn roundtrip_blob(user in gen::gen_signature(), bytes in gen::gen_bytes()) {
-
        prop::roundtrip_blob(user, &bytes)
-
    }
-

-

-
    #[test]
-
    fn roundtrip_tree(
-
        user in gen::gen_signature(),
-
        tree in gen::gen_tree(10),
-
    ) {
-
        prop::roundtrip_tree(user, tree)
-
    }
-

-
    #[test]
-
    fn roundtrip_commit(
-
        user in gen::gen_signature(),
-
        tree in gen::gen_tree(10),
-
        message in gen::trivial()
-
    ) {
-
        prop::roundtrip_commit(user, tree, &message)
-
    }
-

-
    #[test]
-
    fn roundtrip_tag(
-
        user in gen::gen_signature(),
-
        tree in gen::gen_tree(10),
-
        message in gen::trivial(),
-
        tag_name in valid()
-
    ) {
-
        let tag_name = RefString::try_from(tag_name).unwrap();
-
        prop::roundtrip_tag(user, tree, tag_name, &message);
-
    }
-
}
deleted git-storage/t/src/tmp.rs
@@ -1,14 +0,0 @@
-
use git2::Repository;
-
use git_storage::{signature::UserInfo, Write};
-
use test_helpers::tempdir::WithTmpDir;
-

-
pub type TmpWriter = WithTmpDir<Write>;
-
pub type TmpRepo = WithTmpDir<Repository>;
-

-
pub fn writer(user: UserInfo) -> TmpWriter {
-
    WithTmpDir::new(|path| {
-
        Write::open(path, user)
-
            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
-
    })
-
    .unwrap()
-
}
modified test/Cargo.toml
@@ -18,7 +18,3 @@ features = ["test"]
[dev-dependencies.radicle-surf-test]
path = "../radicle-surf/t"
features = ["test"]
-

-
[dev-dependencies.git-storage-test]
-
path = "../git-storage/t"
-
features = ["test"]