node: Finalize `init`

Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
Alexis Sellier 2022-09-01 00:59:03 +02:00
parent 3e173a18ab
commit 68099688e6
No known key found for this signature in database
8 changed files with 143 additions and 25 deletions

View File

@ -6,7 +6,7 @@ use thiserror::Error;
pub use ed25519::Signature; pub use ed25519::Signature;
pub trait Signer { pub trait Signer: 'static {
/// Return this signer's public/verification key. /// Return this signer's public/verification key.
fn public_key(&self) -> &PublicKey; fn public_key(&self) -> &PublicKey;
} }

View File

@ -1,10 +1,12 @@
use std::str::FromStr; use std::str::FromStr;
use git_ref_format as format;
use git_url::Url;
use radicle_git_ext as git_ext;
use crate::collections::HashMap; use crate::collections::HashMap;
use crate::identity::UserId; use crate::identity::UserId;
use crate::storage::{Remote, Remotes, Unverified}; use crate::storage::{Remote, Remotes, Unverified};
use git_ref_format as format;
use git_url::Url;
/// 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;
@ -91,3 +93,43 @@ pub fn commit<'a>(
Ok(commit) Ok(commit)
} }
/// Set the upstream of the given branch to the given remote.
///
/// This writes to the `config` directly. The entry will look like the
/// following:
///
/// ```text
/// [branch "main"]
/// remote = rad
/// merge = refs/heads/main
/// ```
pub fn set_upstream(
repo: &git2::Repository,
remote: &str,
branch: &str,
merge: &str,
) -> Result<(), git2::Error> {
let mut config = repo.config()?;
let branch_remote = format!("branch.{}.remote", branch);
let branch_merge = format!("branch.{}.merge", branch);
config.remove_multivar(&branch_remote, ".*").or_else(|e| {
if git_ext::is_not_found_err(&e) {
Ok(())
} else {
Err(e)
}
})?;
config.remove_multivar(&branch_merge, ".*").or_else(|e| {
if git_ext::is_not_found_err(&e) {
Ok(())
} else {
Err(e)
}
})?;
config.set_multivar(&branch_remote, ".*", remote)?;
config.set_multivar(&branch_merge, ".*", merge)?;
Ok(())
}

View File

@ -1,11 +1,17 @@
use std::{fs, io}; use std::{fs, io};
use git_url::Url;
use nonempty::NonEmpty; use nonempty::NonEmpty;
use thiserror::Error; use thiserror::Error;
use crate::git;
use crate::identity::{ProjId, UserId}; use crate::identity::{ProjId, UserId};
use crate::storage::git::RADICLE_ID_REF;
use crate::storage::ReadRepository as _;
use crate::{identity, storage}; use crate::{identity, storage};
pub const REMOTE_NAME: &str = "rad";
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum InitError { pub enum InitError {
#[error("doc: {0}")] #[error("doc: {0}")]
@ -14,6 +20,8 @@ pub enum InitError {
Git(#[from] git2::Error), Git(#[from] git2::Error),
#[error("i/o: {0}")] #[error("i/o: {0}")]
Io(#[from] io::Error), Io(#[from] io::Error),
#[error("storage: {0}")]
Storage(#[from] storage::Error),
#[error("cannot initialize project inside a bare repository")] #[error("cannot initialize project inside a bare repository")]
BareRepo, BareRepo,
#[error("cannot initialize project from detached head state")] #[error("cannot initialize project from detached head state")]
@ -23,11 +31,12 @@ pub enum InitError {
} }
/// Initialize a new radicle project from a git repository. /// Initialize a new radicle project from a git repository.
pub fn init( pub fn init<S: storage::WriteStorage>(
repo: &git2::Repository, repo: &git2::Repository,
name: &str, name: &str,
description: &str, description: &str,
delegate: UserId, delegate: UserId,
storage: S,
) -> Result<ProjId, InitError> { ) -> Result<ProjId, InitError> {
let delegate = identity::Delegate { let delegate = identity::Delegate {
// TODO: Use actual user name. // TODO: Use actual user name.
@ -65,10 +74,11 @@ pub fn init(
let mut index = repo.index()?; let mut index = repo.index()?;
index.add_path(filename)?; index.add_path(filename)?;
let rad_id_ref = RADICLE_ID_REF.as_str();
let tree_id = index.write_tree()?; let tree_id = index.write_tree()?;
let tree = repo.find_tree(tree_id)?; let tree = repo.find_tree(tree_id)?;
let _oid = repo.commit( let _oid = repo.commit(
Some(storage::git::RAD_ID_REF.as_str()), Some(rad_id_ref),
&sig, &sig,
&sig, &sig,
"Initialize Radicle", "Initialize Radicle",
@ -81,6 +91,38 @@ pub fn init(
// called the same name. Ideally we are able to create the file in the id branch. // called the same name. Ideally we are able to create the file in the id branch.
fs::remove_file(path)?; fs::remove_file(path)?;
let project = storage.repository(&id)?;
let url = Url {
scheme: git_url::Scheme::File,
path: project.path().to_string_lossy().to_string().into(),
..Url::default()
};
let user_id = storage.user_id();
let fetch = format!("+refs/remotes/{user_id}/heads/*:refs/remotes/rad/*");
let push = format!("refs/heads/*:refs/remotes/{user_id}/heads/*");
let mut remote = repo.remote_with_fetch(REMOTE_NAME, url.to_string().as_str(), &fetch)?;
repo.remote_add_push(REMOTE_NAME, &push)?;
git::set_upstream(
repo,
REMOTE_NAME,
"master",
&format!("refs/remotes/{user_id}/heads/master"),
)?;
// TODO: Note that you'll likely want to use `RemoteCallbacks` and set
// `push_update_reference` to test whether all the references were pushed
// successfully.
remote.push::<&str>(
&[
&format!("refs/heads/master:refs/remotes/{user_id}/heads/master"),
&format!("{rad_id_ref}:refs/remotes/{user_id}/heads/rad/id"),
],
None,
)?;
Ok(id) Ok(id)
} }
@ -89,12 +131,15 @@ mod tests {
use super::*; use super::*;
use crate::crypto::Signer; use crate::crypto::Signer;
use crate::git; use crate::git;
use crate::storage::git::Storage;
use crate::test::crypto; use crate::test::crypto;
#[test] #[test]
fn test_init() { fn test_init() {
let tempdir = tempfile::tempdir().unwrap(); let tempdir = tempfile::tempdir().unwrap();
let repo = git2::Repository::init(tempdir.path()).unwrap(); let signer = crypto::MockSigner::default();
let mut storage = Storage::open(tempdir.path().join("storage"), signer).unwrap();
let repo = git2::Repository::init(tempdir.path().join("working")).unwrap();
let sig = git2::Signature::now("anonymous", "anonymous@radicle.xyz").unwrap(); let sig = git2::Signature::now("anonymous", "anonymous@radicle.xyz").unwrap();
let head = git::initial_commit(&repo, &sig).unwrap(); let head = git::initial_commit(&repo, &sig).unwrap();
let head = git::commit(&repo, &head, "Second commit", "anonymous").unwrap(); let head = git::commit(&repo, &head, "Second commit", "anonymous").unwrap();
@ -104,6 +149,6 @@ mod tests {
let signer = crypto::MockSigner::new(&mut fastrand::Rng::new()); let signer = crypto::MockSigner::new(&mut fastrand::Rng::new());
let delegate = *signer.public_key(); let delegate = *signer.public_key();
init(&repo, "acme", "Acme's repo", delegate).unwrap(); init(&repo, "acme", "Acme's repo", delegate, &mut storage).unwrap();
} }
} }

View File

@ -116,22 +116,24 @@ impl Remote<Unverified> {
} }
pub trait ReadStorage { pub trait ReadStorage {
fn user_id(&self) -> &UserId;
fn url(&self) -> Url; fn url(&self) -> Url;
fn get(&self, proj: &ProjId) -> Result<Option<Remotes<Unverified>>, Error>; fn get(&self, proj: &ProjId) -> Result<Option<Remotes<Unverified>>, Error>;
fn inventory(&self) -> Result<Inventory, Error>; fn inventory(&self) -> Result<Inventory, Error>;
} }
pub trait WriteStorage { pub trait WriteStorage: ReadStorage {
type Repository: WriteRepository; type Repository: WriteRepository;
fn repository(&self, proj: &ProjId) -> Result<Self::Repository, Error>; fn repository(&self, proj: &ProjId) -> Result<Self::Repository, Error>;
} }
pub trait ReadRepository { pub trait ReadRepository {
fn path(&self) -> &Path;
fn remotes(&self) -> Result<Remotes<Unverified>, Error>; fn remotes(&self) -> Result<Remotes<Unverified>, Error>;
} }
pub trait WriteRepository { pub trait WriteRepository: ReadRepository {
fn fetch(&mut self, url: &Url) -> Result<(), git2::Error>; fn fetch(&mut self, url: &Url) -> Result<(), git2::Error>;
fn namespace(&mut self, user: &UserId) -> Result<&mut git2::Repository, git2::Error>; fn namespace(&mut self, user: &UserId) -> Result<&mut git2::Repository, git2::Error>;
} }
@ -139,8 +141,12 @@ pub trait WriteRepository {
impl<T, S> ReadStorage for T impl<T, S> ReadStorage for T
where where
T: Deref<Target = S>, T: Deref<Target = S>,
S: ReadStorage, S: ReadStorage + 'static,
{ {
fn user_id(&self) -> &UserId {
self.deref().user_id()
}
fn url(&self) -> Url { fn url(&self) -> Url {
self.deref().url() self.deref().url()
} }

View File

@ -9,6 +9,7 @@ use radicle_git_ext as git_ext;
pub use radicle_git_ext::Oid; pub use radicle_git_ext::Oid;
use crate::collections::HashMap; use crate::collections::HashMap;
use crate::crypto::Signer;
use crate::git; use crate::git;
use crate::identity::{ProjId, UserId}; use crate::identity::{ProjId, UserId};
@ -17,19 +18,14 @@ use super::{
WriteStorage, WriteStorage,
}; };
pub static RAD_ID_REF: Lazy<refspec::PatternString> = pub static RADICLE_ID_REF: Lazy<refspec::PatternString> =
Lazy::new(|| refspec::pattern!("refs/heads/rad/id")); Lazy::new(|| refspec::pattern!("refs/heads/radicle/id"));
pub static NAMESPACES_GLOB: Lazy<refspec::PatternString> = pub static NAMESPACES_GLOB: Lazy<refspec::PatternString> =
Lazy::new(|| refspec::pattern!("refs/namespaces/*")); Lazy::new(|| refspec::pattern!("refs/namespaces/*"));
pub struct Storage { pub struct Storage {
path: PathBuf, path: PathBuf,
} signer: Box<dyn Signer>,
impl From<PathBuf> for Storage {
fn from(path: PathBuf) -> Self {
Self { path }
}
} }
impl fmt::Debug for Storage { impl fmt::Debug for Storage {
@ -39,6 +35,10 @@ impl fmt::Debug for Storage {
} }
impl ReadStorage for Storage { impl ReadStorage for Storage {
fn user_id(&self) -> &UserId {
self.signer.public_key()
}
fn url(&self) -> Url { fn url(&self) -> Url {
Url { Url {
scheme: git_url::Scheme::File, scheme: git_url::Scheme::File,
@ -65,7 +65,7 @@ impl WriteStorage for Storage {
} }
impl Storage { impl Storage {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> { pub fn open<P: AsRef<Path>>(path: P, signer: impl Signer) -> Result<Self, io::Error> {
let path = path.as_ref().to_path_buf(); let path = path.as_ref().to_path_buf();
match fs::create_dir_all(&path) { match fs::create_dir_all(&path) {
@ -74,7 +74,10 @@ impl Storage {
Ok(()) => {} Ok(()) => {}
} }
Ok(Self { path }) Ok(Self {
path,
signer: Box::new(signer),
})
} }
pub fn path(&self) -> &Path { pub fn path(&self) -> &Path {
@ -132,6 +135,10 @@ impl Repository {
} }
impl ReadRepository for Repository { impl ReadRepository for Repository {
fn path(&self) -> &Path {
self.backend.path()
}
fn remotes(&self) -> Result<Remotes<Unverified>, Error> { fn remotes(&self) -> Result<Remotes<Unverified>, Error> {
let refs = self.backend.references_glob(NAMESPACES_GLOB.as_str())?; let refs = self.backend.references_glob(NAMESPACES_GLOB.as_str())?;
let mut remotes = HashMap::default(); let mut remotes = HashMap::default();
@ -196,6 +203,7 @@ mod tests {
use super::*; use super::*;
use crate::git; use crate::git;
use crate::storage::{ReadStorage, WriteRepository}; use crate::storage::{ReadStorage, WriteRepository};
use crate::test::crypto::MockSigner;
use crate::test::fixtures; use crate::test::fixtures;
use git_url::Url; use git_url::Url;
@ -222,7 +230,7 @@ mod tests {
fn test_fetch() { fn test_fetch() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let alice = fixtures::storage(tmp.path().join("alice")); let alice = fixtures::storage(tmp.path().join("alice"));
let bob = Storage::open(tmp.path().join("bob")).unwrap(); let bob = Storage::open(tmp.path().join("bob"), MockSigner::default()).unwrap();
let inventory = alice.inventory().unwrap(); let inventory = alice.inventory().unwrap();
let proj = inventory.first().unwrap(); let proj = inventory.first().unwrap();
let remotes = alice.repository(proj).unwrap().remotes().unwrap(); let remotes = alice.repository(proj).unwrap().remotes().unwrap();

View File

@ -5,10 +5,11 @@ use crate::identity::{ProjId, UserId};
use crate::storage::git::Storage; use crate::storage::git::Storage;
use crate::storage::{WriteRepository, WriteStorage}; use crate::storage::{WriteRepository, WriteStorage};
use crate::test::arbitrary; use crate::test::arbitrary;
use crate::test::crypto::MockSigner;
pub fn storage<P: AsRef<Path>>(path: P) -> Storage { pub fn storage<P: AsRef<Path>>(path: P) -> Storage {
let path = path.as_ref(); let path = path.as_ref();
let storage = Storage::open(path).unwrap(); let storage = Storage::open(path, MockSigner::default()).unwrap();
let proj_ids = arbitrary::set::<ProjId>(3..5); let proj_ids = arbitrary::set::<ProjId>(3..5);
let user_ids = arbitrary::set::<UserId>(1..3); let user_ids = arbitrary::set::<UserId>(1..3);

View File

@ -1,8 +1,9 @@
use git_url::Url; use git_url::Url;
use crate::identity::ProjId; use crate::identity::{ProjId, UserId};
use crate::storage::{ use crate::storage::{
Error, Inventory, ReadStorage, Remotes, Unverified, WriteRepository, WriteStorage, Error, Inventory, ReadRepository, ReadStorage, Remotes, Unverified, WriteRepository,
WriteStorage,
}; };
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -23,6 +24,10 @@ impl MockStorage {
} }
impl ReadStorage for MockStorage { impl ReadStorage for MockStorage {
fn user_id(&self) -> &UserId {
todo!()
}
fn url(&self) -> Url { fn url(&self) -> Url {
Url { Url {
scheme: git_url::Scheme::Radicle, scheme: git_url::Scheme::Radicle,
@ -59,6 +64,16 @@ impl WriteStorage for MockStorage {
pub struct MockRepository {} pub struct MockRepository {}
impl ReadRepository for MockRepository {
fn path(&self) -> &std::path::Path {
todo!()
}
fn remotes(&self) -> Result<Remotes<Unverified>, Error> {
todo!()
}
}
impl WriteRepository for MockRepository { impl WriteRepository for MockRepository {
fn fetch(&mut self, _url: &Url) -> Result<(), git2::Error> { fn fetch(&mut self, _url: &Url) -> Result<(), git2::Error> {
Ok(()) Ok(())

View File

@ -13,6 +13,7 @@ use crate::protocol::peer::*;
use crate::protocol::*; use crate::protocol::*;
use crate::storage::git::Storage; use crate::storage::git::Storage;
use crate::storage::ReadStorage; use crate::storage::ReadStorage;
use crate::test::crypto::MockSigner;
use crate::test::fixtures; use crate::test::fixtures;
#[allow(unused)] #[allow(unused)]
use crate::test::logger; use crate::test::logger;
@ -133,7 +134,7 @@ fn test_inventory_sync() {
let mut alice = Peer::new( let mut alice = Peer::new(
"alice", "alice",
[7, 7, 7, 7], [7, 7, 7, 7],
Storage::open(tmp.path().join("alice")).unwrap(), Storage::open(tmp.path().join("alice"), MockSigner::default()).unwrap(),
); );
let bob_storage = fixtures::storage(tmp.path().join("bob")); let bob_storage = fixtures::storage(tmp.path().join("bob"));
let bob = Peer::new("bob", [8, 8, 8, 8], bob_storage); let bob = Peer::new("bob", [8, 8, 8, 8], bob_storage);