node: Add `rad` module with `init`
Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
parent
1057fb952c
commit
e178ff4057
|
|
@ -62,3 +62,32 @@ pub fn parse_ref<T: FromStr>(s: &str) -> Result<(T, format::RefString), RefError
|
|||
|
||||
Ok((id, refstr))
|
||||
}
|
||||
|
||||
/// Create an initial empty commit.
|
||||
pub fn initial_commit<'a>(
|
||||
repo: &'a git2::Repository,
|
||||
sig: &git2::Signature,
|
||||
) -> Result<git2::Commit<'a>, git2::Error> {
|
||||
let tree_id = repo.index()?.write_tree()?;
|
||||
let tree = repo.find_tree(tree_id)?;
|
||||
let oid = repo.commit(None, sig, sig, "Initial commit", &tree, &[])?;
|
||||
let commit = repo.find_commit(oid).unwrap();
|
||||
|
||||
Ok(commit)
|
||||
}
|
||||
|
||||
/// Create a commit.
|
||||
pub fn commit<'a>(
|
||||
repo: &'a git2::Repository,
|
||||
parent: &'a git2::Commit,
|
||||
message: &str,
|
||||
user: &str,
|
||||
) -> Result<git2::Commit<'a>, git2::Error> {
|
||||
let sig = git2::Signature::now(user, "anonymous@radicle.xyz")?;
|
||||
let tree_id = repo.index()?.write_tree()?;
|
||||
let tree = repo.find_tree(tree_id)?;
|
||||
let oid = repo.commit(None, &sig, &sig, message, &tree, &[parent])?;
|
||||
let commit = repo.find_commit(oid).unwrap();
|
||||
|
||||
Ok(commit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,12 @@ impl Did {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<crypto::PublicKey> for Did {
|
||||
fn from(key: crypto::PublicKey) -> Self {
|
||||
Self(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Did {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.encode())
|
||||
|
|
@ -94,8 +100,9 @@ pub struct Delegate {
|
|||
pub struct Doc {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub default_branch: String,
|
||||
pub version: u32,
|
||||
pub parent: Oid,
|
||||
pub parent: Option<Oid>,
|
||||
pub delegate: NonEmpty<Delegate>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ mod hash;
|
|||
mod identity;
|
||||
mod logger;
|
||||
mod protocol;
|
||||
mod rad;
|
||||
mod storage;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
use std::{fs, io};
|
||||
|
||||
use nonempty::NonEmpty;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::identity;
|
||||
use crate::identity::{ProjId, UserId};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum InitError {
|
||||
#[error("doc: {0}")]
|
||||
Doc(#[from] identity::DocError),
|
||||
#[error("git: {0}")]
|
||||
Git(#[from] git2::Error),
|
||||
#[error("i/o: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("cannot initialize project inside a bare repository")]
|
||||
BareRepo,
|
||||
#[error("cannot initialize project from detached head state")]
|
||||
DetachedHead,
|
||||
#[error("HEAD reference is not valid UTF-8")]
|
||||
InvalidHead,
|
||||
}
|
||||
|
||||
/// Initialize a new radicle project from a git repository.
|
||||
pub fn init(
|
||||
repo: &git2::Repository,
|
||||
name: &str,
|
||||
description: &str,
|
||||
delegate: UserId,
|
||||
) -> Result<ProjId, InitError> {
|
||||
let delegate = identity::Delegate {
|
||||
// TODO: Use actual user name.
|
||||
name: String::from("anonymous"),
|
||||
id: identity::Did::from(delegate),
|
||||
};
|
||||
|
||||
let head = repo.head()?;
|
||||
let default_branch = if head.is_branch() {
|
||||
head.shorthand().ok_or(InitError::InvalidHead)?.to_owned()
|
||||
} else {
|
||||
return Err(InitError::DetachedHead);
|
||||
};
|
||||
|
||||
let doc = identity::Doc {
|
||||
name: name.to_owned(),
|
||||
description: description.to_owned(),
|
||||
default_branch,
|
||||
version: 1,
|
||||
parent: None,
|
||||
delegate: NonEmpty::new(delegate),
|
||||
};
|
||||
let sig = repo
|
||||
.signature()
|
||||
.or_else(|_| git2::Signature::now("anonymous", "anonymous@anonymous.xyz"))?;
|
||||
|
||||
let base = repo.workdir().ok_or(InitError::BareRepo)?;
|
||||
let path = base.join("Project.toml");
|
||||
let file = fs::OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(&path)?;
|
||||
let id = doc.write(file)?;
|
||||
let relative = path.as_path().strip_prefix(base).unwrap();
|
||||
|
||||
let mut index = repo.index()?;
|
||||
index.add_path(relative)?;
|
||||
|
||||
let tree_id = index.write_tree()?;
|
||||
let tree = repo.find_tree(tree_id)?;
|
||||
let _oid = repo.commit(
|
||||
Some("refs/heads/rad/id"),
|
||||
&sig,
|
||||
&sig,
|
||||
"Initialize Radicle",
|
||||
&tree,
|
||||
&[],
|
||||
)?;
|
||||
|
||||
// Remove identity document from current branch.
|
||||
// FIXME: We shouldn't have to do this, as the user may have an unrelated file
|
||||
// called the same name. Ideally we are able to create the file in the id branch.
|
||||
fs::remove_file(path)?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::crypto::Signer;
|
||||
use crate::git;
|
||||
use crate::test::crypto;
|
||||
|
||||
#[test]
|
||||
fn test_init() {
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let repo = git2::Repository::init(tempdir.path()).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();
|
||||
|
||||
repo.branch("master", &head, false).unwrap();
|
||||
|
||||
let signer = crypto::MockSigner::new(&mut fastrand::Rng::new());
|
||||
let delegate = *signer.public_key();
|
||||
|
||||
init(&repo, "acme", "Acme's repo", delegate).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::git;
|
||||
use crate::identity::{ProjId, UserId};
|
||||
use crate::storage::git::Storage;
|
||||
use crate::storage::{WriteRepository, WriteStorage};
|
||||
|
|
@ -19,53 +20,24 @@ pub fn storage<P: AsRef<Path>>(path: P) -> Storage {
|
|||
|
||||
for user in user_ids.iter() {
|
||||
let repo = repo.namespace(user).unwrap();
|
||||
let head = initial_commit(repo, &user.to_string()).unwrap();
|
||||
let sig = git2::Signature::now(&user.to_string(), "anonymous@radicle.xyz").unwrap();
|
||||
let head = git::initial_commit(repo, &sig).unwrap();
|
||||
|
||||
log::debug!("{}: creating {}...", proj, repo.namespace().unwrap());
|
||||
|
||||
repo.reference("refs/rad/root", head.id(), false, "test")
|
||||
.unwrap();
|
||||
|
||||
let head = commit(repo, &head, "Second commit", &user.to_string()).unwrap();
|
||||
let head = git::commit(repo, &head, "Second commit", &user.to_string()).unwrap();
|
||||
repo.branch("master", &head, false).unwrap();
|
||||
|
||||
let head = commit(repo, &head, "Third commit", &user.to_string()).unwrap();
|
||||
let head = git::commit(repo, &head, "Third commit", &user.to_string()).unwrap();
|
||||
repo.branch("patch/3", &head, false).unwrap();
|
||||
}
|
||||
}
|
||||
storage
|
||||
}
|
||||
|
||||
/// Create a commit.
|
||||
fn commit<'a>(
|
||||
repo: &'a git2::Repository,
|
||||
parent: &'a git2::Commit,
|
||||
message: &str,
|
||||
user: &str,
|
||||
) -> Result<git2::Commit<'a>, git2::Error> {
|
||||
let sig = git2::Signature::now(user, "anonymous@radicle.xyz")?;
|
||||
let tree_id = repo.index()?.write_tree()?;
|
||||
let tree = repo.find_tree(tree_id)?;
|
||||
let oid = repo.commit(None, &sig, &sig, message, &tree, &[parent])?;
|
||||
let commit = repo.find_commit(oid).unwrap();
|
||||
|
||||
Ok(commit)
|
||||
}
|
||||
|
||||
/// Create an initial empty commit.
|
||||
fn initial_commit<'a>(
|
||||
repo: &'a git2::Repository,
|
||||
user: &str,
|
||||
) -> Result<git2::Commit<'a>, git2::Error> {
|
||||
let sig = git2::Signature::now(user, "anonymous@radicle.xyz")?;
|
||||
let tree_id = repo.index()?.write_tree()?;
|
||||
let tree = repo.find_tree(tree_id)?;
|
||||
let oid = repo.commit(None, &sig, &sig, "Initial commit", &tree, &[])?;
|
||||
let commit = repo.find_commit(oid).unwrap();
|
||||
|
||||
Ok(commit)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
Loading…
Reference in New Issue