Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node: Finalize `init`
Alexis Sellier committed 3 years ago
commit 68099688e6c31ee3c836dd59d8e70689da573d82
parent 3e173a18abcf0cfc2223d0a4afca887ec322a7b1
8 files changed +143 -25
modified 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;
}
modified 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(())
+
}
modified 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<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();
    }
}
modified node/src/storage.rs
@@ -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()
    }
modified 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<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();
modified 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<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);

modified 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<Remotes<Unverified>, Error> {
+
        todo!()
+
    }
+
}
+

impl WriteRepository for MockRepository {
    fn fetch(&mut self, _url: &Url) -> Result<(), git2::Error> {
        Ok(())
modified 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);