From 68099688e6c31ee3c836dd59d8e70689da573d82 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Thu, 1 Sep 2022 00:59:03 +0200 Subject: [PATCH] node: Finalize `init` Signed-off-by: Alexis Sellier --- node/src/crypto.rs | 2 +- node/src/git.rs | 46 +++++++++++++++++++++++++++++++-- node/src/rad.rs | 53 ++++++++++++++++++++++++++++++++++++--- node/src/storage.rs | 12 ++++++--- node/src/storage/git.rs | 30 ++++++++++++++-------- node/src/test/fixtures.rs | 3 ++- node/src/test/storage.rs | 19 ++++++++++++-- node/src/test/tests.rs | 3 ++- 8 files changed, 143 insertions(+), 25 deletions(-) diff --git a/node/src/crypto.rs b/node/src/crypto.rs index 2e61cac3..53a570f0 100644 --- a/node/src/crypto.rs +++ b/node/src/crypto.rs @@ -6,7 +6,7 @@ use thiserror::Error; pub use ed25519::Signature; -pub trait Signer { +pub trait Signer: 'static { /// Return this signer's public/verification key. fn public_key(&self) -> &PublicKey; } diff --git a/node/src/git.rs b/node/src/git.rs index c957b857..9ca47c26 100644 --- a/node/src/git.rs +++ b/node/src/git.rs @@ -1,10 +1,12 @@ 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::identity::UserId; use crate::storage::{Remote, Remotes, Unverified}; -use git_ref_format as format; -use git_url::Url; /// Default port of the `git` transport protocol. pub const PROTOCOL_PORT: u16 = 9418; @@ -91,3 +93,43 @@ pub fn commit<'a>( 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(()) +} diff --git a/node/src/rad.rs b/node/src/rad.rs index 04774894..02aa18af 100644 --- a/node/src/rad.rs +++ b/node/src/rad.rs @@ -1,11 +1,17 @@ use std::{fs, io}; +use git_url::Url; use nonempty::NonEmpty; use thiserror::Error; +use crate::git; use crate::identity::{ProjId, UserId}; +use crate::storage::git::RADICLE_ID_REF; +use crate::storage::ReadRepository as _; use crate::{identity, storage}; +pub const REMOTE_NAME: &str = "rad"; + #[derive(Error, Debug)] pub enum InitError { #[error("doc: {0}")] @@ -14,6 +20,8 @@ pub enum InitError { Git(#[from] git2::Error), #[error("i/o: {0}")] Io(#[from] io::Error), + #[error("storage: {0}")] + Storage(#[from] storage::Error), #[error("cannot initialize project inside a bare repository")] BareRepo, #[error("cannot initialize project from detached head state")] @@ -23,11 +31,12 @@ pub enum InitError { } /// Initialize a new radicle project from a git repository. -pub fn init( +pub fn init( repo: &git2::Repository, name: &str, description: &str, delegate: UserId, + storage: S, ) -> Result { let delegate = identity::Delegate { // TODO: Use actual user name. @@ -65,10 +74,11 @@ pub fn init( let mut index = repo.index()?; index.add_path(filename)?; + let rad_id_ref = RADICLE_ID_REF.as_str(); let tree_id = index.write_tree()?; let tree = repo.find_tree(tree_id)?; let _oid = repo.commit( - Some(storage::git::RAD_ID_REF.as_str()), + Some(rad_id_ref), &sig, &sig, "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. 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) } @@ -89,12 +131,15 @@ mod tests { use super::*; use crate::crypto::Signer; use crate::git; + use crate::storage::git::Storage; use crate::test::crypto; #[test] fn test_init() { 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 head = git::initial_commit(&repo, &sig).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 delegate = *signer.public_key(); - init(&repo, "acme", "Acme's repo", delegate).unwrap(); + init(&repo, "acme", "Acme's repo", delegate, &mut storage).unwrap(); } } diff --git a/node/src/storage.rs b/node/src/storage.rs index e4271a79..4b12de20 100644 --- a/node/src/storage.rs +++ b/node/src/storage.rs @@ -116,22 +116,24 @@ impl Remote { } pub trait ReadStorage { + fn user_id(&self) -> &UserId; fn url(&self) -> Url; fn get(&self, proj: &ProjId) -> Result>, Error>; fn inventory(&self) -> Result; } -pub trait WriteStorage { +pub trait WriteStorage: ReadStorage { type Repository: WriteRepository; fn repository(&self, proj: &ProjId) -> Result; } pub trait ReadRepository { + fn path(&self) -> &Path; fn remotes(&self) -> Result, Error>; } -pub trait WriteRepository { +pub trait WriteRepository: ReadRepository { fn fetch(&mut self, url: &Url) -> Result<(), git2::Error>; fn namespace(&mut self, user: &UserId) -> Result<&mut git2::Repository, git2::Error>; } @@ -139,8 +141,12 @@ pub trait WriteRepository { impl ReadStorage for T where T: Deref, - S: ReadStorage, + S: ReadStorage + 'static, { + fn user_id(&self) -> &UserId { + self.deref().user_id() + } + fn url(&self) -> Url { self.deref().url() } diff --git a/node/src/storage/git.rs b/node/src/storage/git.rs index fc481d7d..9d040e5e 100644 --- a/node/src/storage/git.rs +++ b/node/src/storage/git.rs @@ -9,6 +9,7 @@ use radicle_git_ext as git_ext; pub use radicle_git_ext::Oid; use crate::collections::HashMap; +use crate::crypto::Signer; use crate::git; use crate::identity::{ProjId, UserId}; @@ -17,19 +18,14 @@ use super::{ WriteStorage, }; -pub static RAD_ID_REF: Lazy = - Lazy::new(|| refspec::pattern!("refs/heads/rad/id")); +pub static RADICLE_ID_REF: Lazy = + Lazy::new(|| refspec::pattern!("refs/heads/radicle/id")); pub static NAMESPACES_GLOB: Lazy = Lazy::new(|| refspec::pattern!("refs/namespaces/*")); pub struct Storage { path: PathBuf, -} - -impl From for Storage { - fn from(path: PathBuf) -> Self { - Self { path } - } + signer: Box, } impl fmt::Debug for Storage { @@ -39,6 +35,10 @@ impl fmt::Debug for Storage { } impl ReadStorage for Storage { + fn user_id(&self) -> &UserId { + self.signer.public_key() + } + fn url(&self) -> Url { Url { scheme: git_url::Scheme::File, @@ -65,7 +65,7 @@ impl WriteStorage for Storage { } impl Storage { - pub fn open>(path: P) -> Result { + pub fn open>(path: P, signer: impl Signer) -> Result { let path = path.as_ref().to_path_buf(); match fs::create_dir_all(&path) { @@ -74,7 +74,10 @@ impl Storage { Ok(()) => {} } - Ok(Self { path }) + Ok(Self { + path, + signer: Box::new(signer), + }) } pub fn path(&self) -> &Path { @@ -132,6 +135,10 @@ impl Repository { } impl ReadRepository for Repository { + fn path(&self) -> &Path { + self.backend.path() + } + fn remotes(&self) -> Result, Error> { let refs = self.backend.references_glob(NAMESPACES_GLOB.as_str())?; let mut remotes = HashMap::default(); @@ -196,6 +203,7 @@ mod tests { use super::*; use crate::git; use crate::storage::{ReadStorage, WriteRepository}; + use crate::test::crypto::MockSigner; use crate::test::fixtures; use git_url::Url; @@ -222,7 +230,7 @@ mod tests { fn test_fetch() { let tmp = tempfile::tempdir().unwrap(); 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 proj = inventory.first().unwrap(); let remotes = alice.repository(proj).unwrap().remotes().unwrap(); diff --git a/node/src/test/fixtures.rs b/node/src/test/fixtures.rs index 072135d2..dbf1049d 100644 --- a/node/src/test/fixtures.rs +++ b/node/src/test/fixtures.rs @@ -5,10 +5,11 @@ use crate::identity::{ProjId, UserId}; use crate::storage::git::Storage; use crate::storage::{WriteRepository, WriteStorage}; use crate::test::arbitrary; +use crate::test::crypto::MockSigner; pub fn storage>(path: P) -> Storage { let path = path.as_ref(); - let storage = Storage::open(path).unwrap(); + let storage = Storage::open(path, MockSigner::default()).unwrap(); let proj_ids = arbitrary::set::(3..5); let user_ids = arbitrary::set::(1..3); diff --git a/node/src/test/storage.rs b/node/src/test/storage.rs index 750073ca..aba1233c 100644 --- a/node/src/test/storage.rs +++ b/node/src/test/storage.rs @@ -1,8 +1,9 @@ use git_url::Url; -use crate::identity::ProjId; +use crate::identity::{ProjId, UserId}; use crate::storage::{ - Error, Inventory, ReadStorage, Remotes, Unverified, WriteRepository, WriteStorage, + Error, Inventory, ReadRepository, ReadStorage, Remotes, Unverified, WriteRepository, + WriteStorage, }; #[derive(Clone, Debug)] @@ -23,6 +24,10 @@ impl MockStorage { } impl ReadStorage for MockStorage { + fn user_id(&self) -> &UserId { + todo!() + } + fn url(&self) -> Url { Url { scheme: git_url::Scheme::Radicle, @@ -59,6 +64,16 @@ impl WriteStorage for MockStorage { pub struct MockRepository {} +impl ReadRepository for MockRepository { + fn path(&self) -> &std::path::Path { + todo!() + } + + fn remotes(&self) -> Result, Error> { + todo!() + } +} + impl WriteRepository for MockRepository { fn fetch(&mut self, _url: &Url) -> Result<(), git2::Error> { Ok(()) diff --git a/node/src/test/tests.rs b/node/src/test/tests.rs index 966e5ae0..900d3ab3 100644 --- a/node/src/test/tests.rs +++ b/node/src/test/tests.rs @@ -13,6 +13,7 @@ use crate::protocol::peer::*; use crate::protocol::*; use crate::storage::git::Storage; use crate::storage::ReadStorage; +use crate::test::crypto::MockSigner; use crate::test::fixtures; #[allow(unused)] use crate::test::logger; @@ -133,7 +134,7 @@ fn test_inventory_sync() { let mut alice = Peer::new( "alice", [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 = Peer::new("bob", [8, 8, 8, 8], bob_storage);