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 trait Signer {
pub trait Signer: 'static {
/// Return this signer's public/verification key.
fn public_key(&self) -> &PublicKey;
}

View File

@ -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(())
}

View File

@ -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<S: storage::WriteStorage>(
repo: &git2::Repository,
name: &str,
description: &str,
delegate: UserId,
storage: S,
) -> Result<ProjId, InitError> {
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();
}
}

View File

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

View File

@ -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<refspec::PatternString> =
Lazy::new(|| refspec::pattern!("refs/heads/rad/id"));
pub static RADICLE_ID_REF: Lazy<refspec::PatternString> =
Lazy::new(|| refspec::pattern!("refs/heads/radicle/id"));
pub static NAMESPACES_GLOB: Lazy<refspec::PatternString> =
Lazy::new(|| refspec::pattern!("refs/namespaces/*"));
pub struct Storage {
path: PathBuf,
}
impl From<PathBuf> for Storage {
fn from(path: PathBuf) -> Self {
Self { path }
}
signer: Box<dyn Signer>,
}
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<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();
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<Remotes<Unverified>, 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();

View File

@ -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<P: AsRef<Path>>(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::<ProjId>(3..5);
let user_ids = arbitrary::set::<UserId>(1..3);

View File

@ -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<Remotes<Unverified>, Error> {
todo!()
}
}
impl WriteRepository for MockRepository {
fn fetch(&mut self, _url: &Url) -> Result<(), git2::Error> {
Ok(())

View File

@ -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);