diff --git a/Cargo.lock b/Cargo.lock index 8800b9f..2710eac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,32 +298,6 @@ dependencies = [ "syn", ] -[[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" diff --git a/Cargo.toml b/Cargo.toml index e9ef138..62dd0bd 100644 --- a/Cargo.toml +++ b/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 diff --git a/git-storage/Cargo.toml b/git-storage/Cargo.toml deleted file mode 100644 index d530a6d..0000000 --- a/git-storage/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "git-storage" -version = "0.1.0" -authors = ["Kim Altintop ", "Fintan Halpenny "] -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" diff --git a/git-storage/src/backend.rs b/git-storage/src/backend.rs deleted file mode 100644 index 01c7b45..0000000 --- a/git-storage/src/backend.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright © 2022 The Radicle Link Contributors -// SPDX-License-Identifier: GPL-3.0-or-later - -pub mod read; -pub mod write; diff --git a/git-storage/src/backend/read.rs b/git-storage/src/backend/read.rs deleted file mode 100644 index 2148da8..0000000 --- a/git-storage/src/backend/read.rs +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright © 2019-2020 The 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>(path: P) -> Result { - 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(&self, reference: Ref) -> Result, Self::FindRef> - where - Ref: AsRef, - { - let reference = self - .raw - .find_reference(reference.as_ref().as_str()) - .map(Some) - .or_matches::(is_not_found_err, || Ok(None))?; - Ok(reference.map(Reference::try_from).transpose()?) - } - - fn find_references(&self, reference: Pat) -> Result - where - Pat: AsRef, - { - Ok(References { - inner: ReferencesGlob { - iter: self.raw.references()?, - glob: glob::RefspecMatcher::from(reference.as_ref().to_owned()), - }, - }) - } - - fn find_reference_oid(&self, reference: Ref) -> Result, Self::FindRefOid> - where - Ref: AsRef, - { - 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, Self::FindObj> { - self.raw - .find_object(oid.into(), None) - .map(Some) - .or_matches::(is_not_found_err, || Ok(None)) - } - - fn find_blob(&self, oid: Oid) -> Result, 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, 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, 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, Self::FindTree> { - self.raw - .find_tree(oid.into()) - .map(Some) - .or_matches(is_not_found_err, || Ok(None)) - } -} diff --git a/git-storage/src/backend/write.rs b/git-storage/src/backend/write.rs deleted file mode 100644 index bff77ee..0000000 --- a/git-storage/src/backend/write.rs +++ /dev/null @@ -1,585 +0,0 @@ -// Copyright © 2019-2020 The 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>(path: P, info: UserInfo) -> Result { - 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(&self, reference: Ref) -> Result, Self::FindRef> - where - Ref: AsRef, - { - self.read_only().find_reference(reference) - } - - fn find_references(&self, reference: Pat) -> Result - where - Pat: AsRef, - { - self.read_only().find_references(reference) - } - - fn find_reference_oid(&self, reference: Ref) -> Result, Self::FindRefOid> - where - Ref: AsRef, - { - 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, Self::UpdateError> - where - U: IntoIterator>, - { - 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 { - 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, 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, Vec>, 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, 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(&mut self, reference: R) -> Result<(), error::Transaction> - where - R: AsRef, - { - 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( - &mut self, - reference: R, - target: Oid, - prev: Option, - reflog: &str, - ) -> Result - where - R: AsRef, - { - 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( - &mut self, - reference: R, - target: SymrefTarget<'a>, - prev: Option, - reflog: &str, - is_ff: bool, - ) -> Result, error::Transaction> - where - R: AsRef, - { - 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( - &mut self, - reference: R, - previous: Oid, - ) -> Result - where - R: AsRef, - { - 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(&self, name: R, target: Oid, prev: Oid) -> Result - where - R: AsRef, - { - 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 = ::FindObj; - type FindBlob = ::FindBlob; - type FindCommit = ::FindCommit; - type FindTag = ::FindTag; - type FindTree = ::FindTree; - - fn find_object(&self, oid: Oid) -> Result, Self::FindObj> { - self.read_only().find_object(oid) - } - - fn find_blob(&self, oid: Oid) -> Result, Self::FindBlob> { - self.read_only().find_blob(oid) - } - - fn find_commit(&self, oid: Oid) -> Result, Self::FindCommit> { - self.read_only().find_commit(oid) - } - - fn find_tag(&self, oid: Oid) -> Result, Self::FindTag> { - self.read_only().find_tag(oid) - } - - fn find_tree(&self, oid: Oid) -> Result, 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 { - self.as_raw().blob(data).map(Oid::from) - } - - fn write_commit( - &self, - tree: &odb::Tree, - parents: &[&odb::Commit<'_>], - message: &str, - ) -> Result { - let author = self.info.signature()?; - self.as_raw() - .commit(None, &author, &author, message, tree, parents) - .map(Oid::from) - } - - fn write_tag( - &self, - name: R, - target: &odb::Object, - message: &str, - ) -> Result - where - R: AsRef, - { - 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 { - 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) - } -} diff --git a/git-storage/src/backend/write/error.rs b/git-storage/src/backend/write/error.rs deleted file mode 100644 index 514c3b8..0000000 --- a/git-storage/src/backend/write/error.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright © 2019-2020 The 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), -} diff --git a/git-storage/src/glob.rs b/git-storage/src/glob.rs deleted file mode 100644 index afcf66a..0000000 --- a/git-storage/src/glob.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright © 2019-2020 The 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>(&self, path: P) -> bool; -} - -impl Pattern for globset::GlobMatcher { - fn matches>(&self, path: P) -> bool { - self.is_match(path) - } -} - -impl Pattern for globset::GlobSet { - fn matches>(&self, path: P) -> bool { - self.is_match(path) - } -} - -#[derive(Clone, Debug)] -pub struct RefspecMatcher(globset::GlobMatcher); - -impl From for RefspecMatcher { - fn from(pat: PatternString) -> Self { - Self(globset::Glob::new(pat.as_str()).unwrap().compile_matcher()) - } -} - -impl Pattern for RefspecMatcher { - fn matches>(&self, path: P) -> bool { - self.0.is_match(path) - } -} diff --git a/git-storage/src/lib.rs b/git-storage/src/lib.rs deleted file mode 100644 index 5703292..0000000 --- a/git-storage/src/lib.rs +++ /dev/null @@ -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() - ) - } - }) - } -} diff --git a/git-storage/src/odb.rs b/git-storage/src/odb.rs deleted file mode 100644 index c0124c5..0000000 --- a/git-storage/src/odb.rs +++ /dev/null @@ -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]: -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct TreeBuilder { - entries: Vec, -} - -impl TreeBuilder { - pub fn new() -> Self { - Self { - entries: Vec::new(), - } - } - - /// [`TreeBuilder::insert`] creates a [`TreeEntry::Insert`] entry in the - /// `TreeBuilder`. - pub fn insert(mut self, name: P, oid: Oid, mode: M) -> Self - where - P: AsRef, - M: Into, - { - 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

(mut self, name: P) -> Self - where - P: AsRef, - { - 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 { - 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(storage: &S, oid: Oid) -> Result -where - S: Read, -{ - 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(storage: &S, oid: Oid) -> Result -where - S: Read, -{ - 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(storage: &S, oid: Oid) -> Result -where - S: Read, -{ - 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(storage: &S, oid: Oid) -> Result -where - S: Read, -{ - 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(storage: &S, oid: Oid) -> Result -where - S: Read, -{ - 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}")) -} diff --git a/git-storage/src/odb/read.rs b/git-storage/src/odb/read.rs deleted file mode 100644 index a878165..0000000 --- a/git-storage/src/odb/read.rs +++ /dev/null @@ -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, 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, 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, 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, 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, Self::FindTree>; -} diff --git a/git-storage/src/odb/write.rs b/git-storage/src/odb/write.rs deleted file mode 100644 index 1b053c0..0000000 --- a/git-storage/src/odb/write.rs +++ /dev/null @@ -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; - - /// 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; - - /// 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(&self, name: R, target: &Object, message: &str) -> Result - where - R: AsRef; - - /// Write a [`super::Tree`] using the provided `builder`. - fn write_tree(&self, builder: TreeBuilder) -> Result; -} diff --git a/git-storage/src/refdb.rs b/git-storage/src/refdb.rs deleted file mode 100644 index 7886f69..0000000 --- a/git-storage/src/refdb.rs +++ /dev/null @@ -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> for Reference { - type Error = error::ParseReference; - - fn try_from(value: git2::Reference) -> Result { - 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 for Target { - fn from(oid: Oid) -> Self { - Self::Direct { oid } - } -} - -impl From for Target { - fn from(oid: git2::Oid) -> Self { - Oid::from(oid).into() - } -} - -impl From 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 { - match &r.target { - Target::Direct { oid } => Ok(*oid), - Target::Symbolic { name } => repo.refname_to_id(name.as_str()).map(Oid::from), - } -} diff --git a/git-storage/src/refdb/iter.rs b/git-storage/src/refdb/iter.rs deleted file mode 100644 index 56d990e..0000000 --- a/git-storage/src/refdb/iter.rs +++ /dev/null @@ -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; - - fn next(&mut self) -> Option { - 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; - - fn next(&mut self) -> Option { - 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 - } -} diff --git a/git-storage/src/refdb/read.rs b/git-storage/src/refdb/read.rs deleted file mode 100644 index 71e674a..0000000 --- a/git-storage/src/refdb/read.rs +++ /dev/null @@ -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>; - - /// Find the reference that corresponds to `name`. If the reference does not - /// exist, then `None` is returned. - fn find_reference(&self, name: Ref) -> Result, Self::FindRef> - where - Ref: AsRef; - - /// Find the references that match `pattern`. - fn find_references(&self, pattern: Pat) -> Result - where - Pat: AsRef; - - /// Find the [`Oid`] for the reference that corresponds to `name`. If the - /// reference does not exist, then `None` is returned. - fn find_reference_oid(&self, name: Ref) -> Result, Self::FindRefOid> - where - Ref: AsRef; -} diff --git a/git-storage/src/refdb/write.rs b/git-storage/src/refdb/write.rs deleted file mode 100644 index fdc6aa0..0000000 --- a/git-storage/src/refdb/write.rs +++ /dev/null @@ -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, Self::UpdateError> - where - U: IntoIterator>; -} - -/// 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, - }, - /// 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, - }, - /// 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>, - /// The successful [`Update`]s applied to the refdb. - pub updated: Vec, -} - -/// 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, -} diff --git a/git-storage/src/refdb/write/previous.rs b/git-storage/src/refdb/write/previous.rs deleted file mode 100644 index 1ec1b8e..0000000 --- a/git-storage/src/refdb/write/previous.rs +++ /dev/null @@ -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) -> 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) -> 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), - }, - } - } -} diff --git a/git-storage/src/signature.rs b/git-storage/src/signature.rs deleted file mode 100644 index 9b67521..0000000 --- a/git-storage/src/signature.rs +++ /dev/null @@ -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::now(&self.name, &self.email) - } -} diff --git a/git-storage/t/Cargo.toml b/git-storage/t/Cargo.toml deleted file mode 100644 index ae5eaf9..0000000 --- a/git-storage/t/Cargo.toml +++ /dev/null @@ -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"] diff --git a/git-storage/t/src/gen.rs b/git-storage/t/src/gen.rs deleted file mode 100644 index 3ff4dc4..0000000 --- a/git-storage/t/src/gen.rs +++ /dev/null @@ -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, - 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, -} - -impl Tree { - /// Write the files to the filesystem and the repository `storage` - pub fn write(self, storage: &Write) -> Result { - 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 { - "[a-zA-Z0-9]+" -} - -pub fn gen_signature() -> impl Strategy { - 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> { - any::>() -} - -pub fn gen_mode() -> impl Strategy { - 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> { - 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 { - 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 } - }) -} diff --git a/git-storage/t/src/lib.rs b/git-storage/t/src/lib.rs deleted file mode 100644 index 31295a2..0000000 --- a/git-storage/t/src/lib.rs +++ /dev/null @@ -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; diff --git a/git-storage/t/src/properties.rs b/git-storage/t/src/properties.rs deleted file mode 100644 index 5ea704e..0000000 --- a/git-storage/t/src/properties.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright © 2022 The Radicle Link Contributors -// SPDX-License-Identifier: GPL-3.0-or-later - -mod odb; diff --git a/git-storage/t/src/properties/odb.rs b/git-storage/t/src/properties/odb.rs deleted file mode 100644 index 73ccb33..0000000 --- a/git-storage/t/src/properties/odb.rs +++ /dev/null @@ -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); - } -} diff --git a/git-storage/t/src/tmp.rs b/git-storage/t/src/tmp.rs deleted file mode 100644 index 825a98c..0000000 --- a/git-storage/t/src/tmp.rs +++ /dev/null @@ -1,14 +0,0 @@ -use git2::Repository; -use git_storage::{signature::UserInfo, Write}; -use test_helpers::tempdir::WithTmpDir; - -pub type TmpWriter = WithTmpDir; -pub type TmpRepo = WithTmpDir; - -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() -} diff --git a/test/Cargo.toml b/test/Cargo.toml index 9da547f..bac7c38 100644 --- a/test/Cargo.toml +++ b/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"]