node: Finalize `init`
Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
parent
3e173a18ab
commit
68099688e6
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue