node: Change storage layout

Projects now have their own repos.

Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
Alexis Sellier 2022-08-29 11:16:53 +02:00
parent 5c41af060f
commit f18afb793c
No known key found for this signature in database
8 changed files with 164 additions and 265 deletions

View File

@ -1,85 +1,62 @@
use crate::identity::ProjId; use std::str::FromStr;
use crate::storage::{Error, WriteStorage};
use crate::collections::HashMap;
use crate::identity::UserId;
use crate::storage::{Remote, Remotes, Unverified};
use git_ref_format as format;
/// Default port of the `git` transport protocol. /// Default port of the `git` transport protocol.
pub const PROTOCOL_PORT: u16 = 9418; pub const PROTOCOL_PORT: u16 = 9418;
/// Fetch all remotes of a project from the given URL. #[derive(thiserror::Error, Debug)]
pub fn fetch<S: WriteStorage>(proj: &ProjId, url: &str, mut storage: S) -> Result<(), Error> { pub enum RefError {
// TODO: Use `Url` type? #[error("invalid ref name '{0}'")]
// TODO: Have function to fetch specific remotes. InvalidName(format::RefString),
// TODO: Return meaningful info on success. #[error("invalid ref format: {0}")]
// Format(#[from] format::Error),
// Repository layout should look like this:
//
// /refs/namespaces/<project>
// /refs/namespaces/<remote>
// /heads
// /master
// /tags
// ...
//
let repo = storage.repository();
let refs: &[&str] = &[&format!(
"refs/namespaces/{}/refs/*:refs/namespaces/{}/refs/*",
proj, proj
)];
let mut remote = repo.remote_anonymous(url)?;
let mut opts = git2::FetchOptions::default();
remote.fetch(refs, Some(&mut opts), None)?;
Ok(())
} }
#[cfg(test)] #[derive(thiserror::Error, Debug)]
mod tests { pub enum ListRefsError {
use super::*; #[error("git error: {0}")]
use crate::hash::Digest; Git(#[from] git2::Error),
use crate::identity::ProjId; #[error("invalid ref: {0}")]
use crate::storage::Storage; InvalidRef(#[from] RefError),
}
/// Create an initial empty commit.
fn initial_commit(repo: &git2::Repository) -> Result<git2::Oid, Error> { /// List remote refs of a project, given the remote URL.
// First use the config to initialize a commit signature for the user. pub fn list_remotes(url: &str) -> Result<Remotes<Unverified>, ListRefsError> {
let sig = git2::Signature::now("cloudhead", "cloudhead@radicle.xyz")?; let mut remotes = HashMap::default();
// Now let's create an empty tree for this commit. let mut remote = git2::Remote::create_detached(url)?;
let tree_id = repo.index()?.write_tree()?;
let tree = repo.find_tree(tree_id)?; remote.connect(git2::Direction::Fetch)?;
let oid = repo.commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[])?;
let refs = remote.list()?;
Ok(oid) for r in refs {
} let (id, refname) = parse_ref::<UserId>(r.name())?;
let entry = remotes
#[test] .entry(id.clone())
fn test_fetch() { .or_insert_with(|| Remote::new(id, HashMap::default()));
let path = tempfile::tempdir().unwrap().into_path();
let alice = git2::Repository::init_bare(path.join("alice")).unwrap(); entry.refs.insert(refname.to_string(), r.oid().into());
let bob = git2::Repository::init_bare(path.join("bob")).unwrap(); }
let mut bob_storage = Storage::from(bob);
let proj = ProjId::from(Digest::new(&[42])); Ok(Remotes::new(remotes))
let master = format!("refs/namespaces/{}/refs/heads/master", proj); }
let alice_oid = initial_commit(&alice).unwrap();
/// Parse a ref string.
alice pub fn parse_ref<T: FromStr>(s: &str) -> Result<(T, format::RefString), RefError> {
.reference(&master, alice_oid, false, "Create master branch") let input = format::RefStr::try_from_str(s)?;
.unwrap(); let suffix = input
.strip_prefix(format::refname!("refs/namespaces"))
// Have Bob fetch Alice's refs. .ok_or_else(|| RefError::InvalidName(input.to_owned()))?;
fetch(
&proj, let mut components = suffix.components();
&format!("file://{}/alice", path.display()), let id = components
&mut bob_storage, .next()
) .ok_or_else(|| RefError::InvalidName(input.to_owned()))?;
.unwrap(); let id = T::from_str(&id.to_string()).map_err(|_| RefError::InvalidName(input.to_owned()))?;
let refstr = components.collect::<format::RefString>();
let bob_oid = bob_storage
.repository() Ok((id, refstr))
.find_reference(&master)
.unwrap()
.target()
.unwrap();
assert_eq!(alice_oid, bob_oid);
}
} }

View File

@ -1,4 +1,4 @@
use std::{fmt, io, ops::Deref, str::FromStr}; use std::{ffi::OsString, fmt, io, ops::Deref, str::FromStr};
use ed25519_consensus::{VerificationKey, VerificationKeyBytes}; use ed25519_consensus::{VerificationKey, VerificationKeyBytes};
use nonempty::NonEmpty; use nonempty::NonEmpty;
@ -10,8 +10,6 @@ use crate::hash;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ProjIdError { pub enum ProjIdError {
#[error("invalid ref '{0}'")]
InvalidRef(String),
#[error("invalid digest: {0}")] #[error("invalid digest: {0}")]
InvalidDigest(#[from] hash::DecodeError), InvalidDigest(#[from] hash::DecodeError),
} }
@ -35,24 +33,25 @@ impl ProjId {
pub fn encode(&self) -> String { pub fn encode(&self) -> String {
multibase::encode(multibase::Base::Base58Btc, &self.0.as_ref()) multibase::encode(multibase::Base::Base58Btc, &self.0.as_ref())
} }
pub(crate) fn from_ref(s: &str) -> Result<ProjId, ProjIdError> {
if let Some(s) = s.split('/').nth(2) {
let id = Self::from_str(s)?;
return Ok(id);
}
Err(ProjIdError::InvalidRef(s.to_owned()))
}
} }
impl FromStr for ProjId { impl FromStr for ProjId {
type Err = hash::DecodeError; type Err = ProjIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(hash::Digest::from_str(s)?)) Ok(Self(hash::Digest::from_str(s)?))
} }
} }
impl TryFrom<OsString> for ProjId {
type Error = ProjIdError;
fn try_from(value: OsString) -> Result<Self, Self::Error> {
let string = value.to_string_lossy();
Self::from_str(&string)
}
}
impl From<hash::Digest> for ProjId { impl From<hash::Digest> for ProjId {
fn from(digest: hash::Digest) -> Self { fn from(digest: hash::Digest) -> Self {
Self(digest) Self(digest)
@ -134,8 +133,6 @@ pub enum UserIdError {
InvalidKey(#[from] ed25519_consensus::Error), InvalidKey(#[from] ed25519_consensus::Error),
} }
impl UserId {}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum DocError { pub enum DocError {
#[error("toml: {0}")] #[error("toml: {0}")]

View File

@ -16,9 +16,8 @@ use crate::address_manager::AddressManager;
use crate::clock::RefClock; use crate::clock::RefClock;
use crate::collections::{HashMap, HashSet}; use crate::collections::{HashMap, HashSet};
use crate::decoder::Decoder; use crate::decoder::Decoder;
use crate::git;
use crate::identity::{ProjId, UserId}; use crate::identity::{ProjId, UserId};
use crate::storage; use crate::storage::{self, WriteRepository};
use crate::storage::{Inventory, ReadStorage, Remotes, Unverified, WriteStorage}; use crate::storage::{Inventory, ReadStorage, Remotes, Unverified, WriteStorage};
/// Network peer identifier. /// Network peer identifier.
@ -452,7 +451,11 @@ where
match cmd { match cmd {
Command::Connect(addr) => self.context.connect(addr), Command::Connect(addr) => self.context.connect(addr),
Command::Fetch(proj, remote) => { Command::Fetch(proj, remote) => {
git::fetch(&proj, &format!("git://{}", remote), &mut self.storage).unwrap(); self.storage
.repository(&proj)
.unwrap()
.fetch(&format!("git://{}", remote))
.unwrap();
} }
} }
} }

View File

@ -1,22 +1,22 @@
pub mod git;
use std::collections::hash_map;
use std::io;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::path::Path; use std::path::Path;
use std::{fmt, fs, io};
use git_ref_format::refspec;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use radicle_git_ext as git_ext;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
pub use radicle_git_ext::Oid; pub use radicle_git_ext::Oid;
use crate::collections::HashMap; use crate::collections::HashMap;
use crate::git::RefError;
use crate::identity; use crate::identity;
use crate::identity::{ProjId, ProjIdError, UserId}; use crate::identity::{ProjId, ProjIdError, UserId};
pub static RAD_ID_GLOB: Lazy<refspec::PatternString> =
Lazy::new(|| refspec::pattern!("refs/namespaces/*/refs/rad/id"));
pub static IDENTITY_PATH: Lazy<&Path> = Lazy::new(|| Path::new(".rad/identity.toml")); pub static IDENTITY_PATH: Lazy<&Path> = Lazy::new(|| Path::new(".rad/identity.toml"));
pub type BranchName = String; pub type BranchName = String;
@ -27,6 +27,8 @@ pub type Inventory = Vec<(ProjId, HashMap<String, Remote<Unverified>>)>;
pub enum Error { pub enum Error {
#[error("invalid git reference")] #[error("invalid git reference")]
InvalidRef, InvalidRef,
#[error("git reference error: {0}")]
Ref(#[from] RefError),
#[error("git: {0}")] #[error("git: {0}")]
Git(#[from] git2::Error), Git(#[from] git2::Error),
#[error("id: {0}")] #[error("id: {0}")]
@ -51,7 +53,7 @@ pub struct Verified;
pub struct Unverified; pub struct Unverified;
/// Project remotes. Tracks the git state of a project. /// Project remotes. Tracks the git state of a project.
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Remotes<V>(HashMap<RemoteId, Remote<V>>); pub struct Remotes<V>(HashMap<RemoteId, Remote<V>>);
impl Remotes<Unverified> { impl Remotes<Unverified> {
@ -60,6 +62,21 @@ impl Remotes<Unverified> {
} }
} }
impl Default for Remotes<Unverified> {
fn default() -> Self {
Self(HashMap::default())
}
}
impl<V> IntoIterator for Remotes<V> {
type Item = (RemoteId, Remote<V>);
type IntoIter = hash_map::IntoIter<RemoteId, Remote<V>>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[allow(clippy::from_over_into)] #[allow(clippy::from_over_into)]
impl Into<HashMap<String, Remote<Unverified>>> for Remotes<Unverified> { impl Into<HashMap<String, Remote<Unverified>>> for Remotes<Unverified> {
fn into(self) -> HashMap<String, Remote<Unverified>> { fn into(self) -> HashMap<String, Remote<Unverified>> {
@ -73,20 +90,23 @@ impl Into<HashMap<String, Remote<Unverified>>> for Remotes<Unverified> {
} }
/// A project remote. /// A project remote.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Remote<V> { pub struct Remote<V> {
/// ID of remote.
pub id: UserId,
/// Git references published under this remote, and their hashes. /// Git references published under this remote, and their hashes.
refs: HashMap<RefName, Oid>, pub refs: HashMap<RefName, Oid>,
/// Whether this remote is of a project delegate. /// Whether this remote is of a project delegate.
delegate: bool, pub delegate: bool,
/// Whether the remote is verified or not, ie. whether its signed refs were checked. /// Whether the remote is verified or not, ie. whether its signed refs were checked.
#[serde(skip)] #[serde(skip)]
verified: PhantomData<V>, verified: PhantomData<V>,
} }
impl Remote<Unverified> { impl Remote<Unverified> {
pub fn new(refs: HashMap<RefName, Oid>) -> Self { pub fn new(id: UserId, refs: HashMap<RefName, Oid>) -> Self {
Self { Self {
id,
refs, refs,
delegate: false, delegate: false,
verified: PhantomData, verified: PhantomData,
@ -100,12 +120,18 @@ pub trait ReadStorage {
} }
pub trait WriteStorage { pub trait WriteStorage {
fn repository(&mut self) -> &mut git2::Repository; type Repository: WriteRepository;
fn namespace(
&mut self, fn repository(&self, proj: &ProjId) -> Result<Self::Repository, Error>;
proj: &ProjId, }
user: &UserId,
) -> Result<&mut git2::Repository, git2::Error>; pub trait ReadRepository {
fn remotes(&self) -> Result<Remotes<Unverified>, Error>;
}
pub trait WriteRepository {
fn fetch(&mut self, url: &str) -> Result<(), git2::Error>;
fn namespace(&mut self, user: &UserId) -> Result<&mut git2::Repository, git2::Error>;
} }
impl<T, S> ReadStorage for T impl<T, S> ReadStorage for T
@ -127,114 +153,10 @@ where
T: DerefMut<Target = S>, T: DerefMut<Target = S>,
S: WriteStorage + 'static, S: WriteStorage + 'static,
{ {
fn repository(&mut self) -> &mut git2::Repository { type Repository = S::Repository;
self.deref_mut().repository()
}
fn namespace( fn repository(&self, proj: &ProjId) -> Result<Self::Repository, Error> {
&mut self, self.deref().repository(proj)
proj: &ProjId,
user: &UserId,
) -> Result<&mut git2::Repository, git2::Error> {
self.deref_mut().namespace(proj, user)
}
}
pub struct Storage {
backend: git2::Repository,
}
impl From<git2::Repository> for Storage {
fn from(backend: git2::Repository) -> Self {
Self { backend }
}
}
impl fmt::Debug for Storage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Storage(..)")
}
}
impl ReadStorage for Storage {
fn get(&self, _id: &ProjId) -> Result<Option<Remotes<Unverified>>, Error> {
todo!()
}
fn inventory(&self) -> Result<Inventory, Error> {
let glob: String = RAD_ID_GLOB.clone().into();
let refs = self.backend.references_glob(glob.as_str())?;
for r in refs {
let r = r?;
let name = r.name().ok_or(Error::InvalidRef)?;
let _id = ProjId::from_ref(name)?;
todo!();
}
Ok(vec![])
}
}
impl WriteStorage for Storage {
fn repository(&mut self) -> &mut git2::Repository {
&mut self.backend
}
fn namespace(
&mut self,
proj: &ProjId,
user: &UserId,
) -> Result<&mut git2::Repository, git2::Error> {
let path = self.backend.path();
self.backend = git2::Repository::open_bare(path)?;
self.backend.set_namespace(&format!("{}/{}", proj, user))?;
Ok(&mut self.backend)
}
}
impl Storage {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, git2::Error> {
let path = path.as_ref();
let backend = match git2::Repository::open_bare(path) {
Err(e) if git_ext::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 { backend })
}
pub fn create(
&self,
repo: &git2::Repository,
identity: impl Into<identity::Doc>,
) -> Result<(ProjId, git2::Reference), Error> {
let doc = identity.into();
let file = fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(*IDENTITY_PATH)?;
let id = doc.write(file)?;
let ref_name = RAD_ID_GLOB.replace('*', &id.encode());
let oid = repo.head()?.target().ok_or(Error::InvalidHead)?;
let reference = self.backend.reference(&ref_name, oid, false, "")?;
// TODO: Push project to monorepo.
Ok((id, reference))
} }
} }

View File

@ -1,29 +1,26 @@
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::{fmt, fs};
use std::{fmt, fs, io};
use git_ref_format::refspec; use git_ref_format::refspec;
use git_url::Url;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use radicle_git_ext as git_ext; use radicle_git_ext as git_ext;
use serde::{Deserialize, Serialize};
pub use radicle_git_ext::Oid; pub use radicle_git_ext::Oid;
use crate::collections::HashMap; use crate::collections::HashMap;
use crate::git; use crate::git;
use crate::identity; use crate::identity;
use crate::identity::{ProjId, ProjIdError, UserId}; use crate::identity::{ProjId, UserId};
use super::{ use super::{
Error, Inventory, ReadRepository, ReadStorage, Remote, Remotes, Unverified, Verified, Error, Inventory, ReadRepository, ReadStorage, Remote, Remotes, Unverified, WriteRepository,
WriteRepository, WriteStorage, WriteStorage,
}; };
pub static RAD_ROOT_GLOB: Lazy<refspec::PatternString> = pub static RAD_ROOT_GLOB: Lazy<refspec::PatternString> =
Lazy::new(|| refspec::pattern!("refs/namespaces/*/refs/rad/root")); Lazy::new(|| refspec::pattern!("refs/namespaces/*/refs/rad/root"));
pub static NAMESPACES_GLOB: Lazy<refspec::PatternString> =
Lazy::new(|| refspec::pattern!("refs/namespaces/*"));
pub static IDENTITY_PATH: Lazy<&Path> = Lazy::new(|| Path::new(".rad/identity.toml")); pub static IDENTITY_PATH: Lazy<&Path> = Lazy::new(|| Path::new(".rad/identity.toml"));
pub struct Storage { pub struct Storage {
@ -48,7 +45,6 @@ impl ReadStorage for Storage {
} }
fn inventory(&self) -> Result<Inventory, Error> { fn inventory(&self) -> Result<Inventory, Error> {
let glob: String = RAD_ROOT_GLOB.clone().into();
let projs = self.projects()?; let projs = self.projects()?;
let mut inv = Vec::new(); let mut inv = Vec::new();
@ -57,7 +53,7 @@ impl ReadStorage for Storage {
let remotes = repo let remotes = repo
.remotes()? .remotes()?
.into_iter() .into_iter()
.map(|r| (r.id.to_string(), r)) .map(|(id, r)| (id.to_string(), r))
.collect(); .collect();
inv.push((proj, remotes)); inv.push((proj, remotes));
@ -157,8 +153,8 @@ impl Repository {
} }
impl ReadRepository for Repository { impl ReadRepository for Repository {
fn remotes(&self) -> Result<Vec<Remote<Unverified>>, Error> { fn remotes(&self) -> Result<Remotes<Unverified>, Error> {
let refs = self.backend.references_glob(RAD_ROOT_GLOB.as_str())?; let refs = self.backend.references_glob(NAMESPACES_GLOB.as_str())?;
let mut remotes = HashMap::default(); let mut remotes = HashMap::default();
for r in refs { for r in refs {
@ -172,7 +168,7 @@ impl ReadRepository for Repository {
entry.refs.insert(refname.to_string(), oid.into()); entry.refs.insert(refname.to_string(), oid.into());
} }
Ok(remotes.into_values().collect()) Ok(Remotes::new(remotes))
} }
} }
@ -192,7 +188,7 @@ impl WriteRepository for Repository {
// /tags // /tags
// ... // ...
// //
let refs: &[&str] = &[&format!("refs/namespaces/*:refs/namespaces/*")]; let refs: &[&str] = &["refs/namespaces/*:refs/namespaces/*"];
let mut remote = self.backend.remote_anonymous(url)?; let mut remote = self.backend.remote_anonymous(url)?;
let mut opts = git2::FetchOptions::default(); let mut opts = git2::FetchOptions::default();
@ -221,38 +217,22 @@ impl From<git2::Repository> for Repository {
mod tests { mod tests {
use super::*; use super::*;
use crate::git; use crate::git;
use crate::hash::Digest;
use crate::identity::ProjId;
use crate::storage::{ReadStorage, WriteRepository}; use crate::storage::{ReadStorage, WriteRepository};
use crate::test::fixtures; use crate::test::fixtures;
/// Create an initial empty commit.
fn initial_commit(repo: &git2::Repository) -> Result<git2::Oid, Error> {
// First use the config to initialize a commit signature for the user.
let sig = git2::Signature::now("cloudhead", "cloudhead@radicle.xyz")?;
// Now let's create an empty tree for this commit.
let tree_id = repo.index()?.write_tree()?;
let tree = repo.find_tree(tree_id)?;
let oid = repo.commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[])?;
Ok(oid)
}
#[test] #[test]
fn test_ls_remote() { fn test_list_remotes() {
crate::test::logger::init(log::Level::Debug);
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();
let storage = fixtures::storage(dir.path()); let storage = fixtures::storage(dir.path());
let inv = storage.inventory().unwrap(); let inv = storage.inventory().unwrap();
let (proj, _) = inv.first().unwrap(); let (proj, _) = inv.first().unwrap();
let refs = git::list_refs(&format!( let refs = git::list_remotes(&format!(
"file://{}", "file://{}",
dir.path().join(&proj.to_string()).display(), dir.path().join(&proj.to_string()).display(),
)) ))
.unwrap(); .unwrap();
let remotes = storage.repository(&proj).unwrap().remotes().unwrap(); let remotes = storage.repository(proj).unwrap().remotes().unwrap();
assert_eq!(refs, remotes); assert_eq!(refs, remotes);
} }
@ -267,7 +247,7 @@ mod tests {
let refname = "refs/heads/master"; let refname = "refs/heads/master";
// Have Bob fetch Alice's refs. // Have Bob fetch Alice's refs.
bob.repository(&proj) bob.repository(proj)
.unwrap() .unwrap()
.fetch(&format!( .fetch(&format!(
"file://{}", "file://{}",
@ -275,14 +255,14 @@ mod tests {
)) ))
.unwrap(); .unwrap();
for (_, remote) in remotes { for remote in remotes.values() {
let alice_oid = alice let alice_oid = alice
.repository(&proj) .repository(proj)
.unwrap() .unwrap()
.find_reference(&remote.id, refname) .find_reference(&remote.id, refname)
.unwrap(); .unwrap();
let bob_oid = bob let bob_oid = bob
.repository(&proj) .repository(proj)
.unwrap() .unwrap()
.find_reference(&remote.id, refname) .find_reference(&remote.id, refname)
.unwrap(); .unwrap();

View File

@ -26,6 +26,7 @@ impl quickcheck::Arbitrary for storage::Remote<storage::Unverified> {
let mut refs: HashMap<storage::BranchName, storage::Oid> = HashMap::with_hasher(rng.into()); let mut refs: HashMap<storage::BranchName, storage::Oid> = HashMap::with_hasher(rng.into());
let mut bytes: [u8; 20] = [0; 20]; let mut bytes: [u8; 20] = [0; 20];
let names = &["master", "dev", "feature/1", "feature/2", "feature/3"]; let names = &["master", "dev", "feature/1", "feature/2", "feature/3"];
let id = UserId::arbitrary(g);
for _ in 0..g.size().min(2) { for _ in 0..g.size().min(2) {
if let Some(name) = g.choose(names) { if let Some(name) = g.choose(names) {
@ -36,7 +37,7 @@ impl quickcheck::Arbitrary for storage::Remote<storage::Unverified> {
refs.insert(name.to_string(), oid); refs.insert(name.to_string(), oid);
} }
} }
storage::Remote::new(refs) storage::Remote::new(id, refs)
} }
} }

View File

@ -4,7 +4,8 @@ use std::str::FromStr;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::identity::{ProjId, UserId}; use crate::identity::{ProjId, UserId};
use crate::storage::{Storage, WriteStorage}; use crate::storage::git::Storage;
use crate::storage::{WriteRepository, WriteStorage};
pub static USER_IDS: Lazy<[UserId; 16]> = Lazy::new(|| { pub static USER_IDS: Lazy<[UserId; 16]> = Lazy::new(|| {
[ [
@ -48,16 +49,23 @@ pub static PROJ_IDS: Lazy<[ProjId; 16]> = Lazy::new(|| {
] ]
}); });
pub fn storage(path: &Path) -> Storage { pub fn storage<P: AsRef<Path>>(path: P) -> Storage {
let mut storage = Storage::open(path).unwrap(); let path = path.as_ref();
let storage = Storage::new(path);
for proj in PROJ_IDS.iter().take(3) { for proj in PROJ_IDS.iter().take(3) {
log::debug!("creating {}...", proj);
let mut repo = storage.repository(proj).unwrap();
for user in USER_IDS.iter().take(3) { for user in USER_IDS.iter().take(3) {
let repo = storage.namespace(proj, user).unwrap(); let repo = repo.namespace(user).unwrap();
let head_oid = initial_commit(repo).unwrap(); let head_oid = initial_commit(repo).unwrap();
let head = repo.find_commit(head_oid).unwrap(); let head = repo.find_commit(head_oid).unwrap();
log::debug!("creating {}...", repo.namespace().unwrap()); log::debug!("{}: creating {}...", proj, repo.namespace().unwrap());
repo.reference("refs/rad/root", head_oid, false, "test")
.unwrap();
// TODO: Different commits. // TODO: Different commits.
repo.branch("master", &head, false).unwrap(); repo.branch("master", &head, false).unwrap();

View File

@ -1,5 +1,7 @@
use crate::identity::ProjId; use crate::identity::ProjId;
use crate::storage::{Error, Inventory, ReadStorage, Remotes, Unverified, WriteStorage}; use crate::storage::{
Error, Inventory, ReadStorage, Remotes, Unverified, WriteRepository, WriteStorage,
};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MockStorage { pub struct MockStorage {
@ -38,13 +40,22 @@ impl ReadStorage for MockStorage {
} }
impl WriteStorage for MockStorage { impl WriteStorage for MockStorage {
fn repository(&mut self) -> &mut git2::Repository { type Repository = MockRepository;
fn repository(&self, _proj: &ProjId) -> Result<Self::Repository, Error> {
todo!()
}
}
pub struct MockRepository {}
impl WriteRepository for MockRepository {
fn fetch(&mut self, _url: &str) -> Result<(), git2::Error> {
todo!() todo!()
} }
fn namespace( fn namespace(
&mut self, &mut self,
_proj: &ProjId,
_user: &crate::identity::UserId, _user: &crate::identity::UserId,
) -> Result<&mut git2::Repository, git2::Error> { ) -> Result<&mut git2::Repository, git2::Error> {
todo!() todo!()