Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node: Add `rad` module with `init`
Alexis Sellier committed 3 years ago
commit e178ff40575c862f8faf1be045a673e8cb4b5e98
parent 1057fb952c732f12cbd5b6a71ce6420d5d0c8dd1
5 files changed +153 -34
modified node/src/git.rs
@@ -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)
+
}
modified node/src/identity.rs
@@ -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>,
}

modified node/src/lib.rs
@@ -12,6 +12,7 @@ mod hash;
mod identity;
mod logger;
mod protocol;
+
mod rad;
mod storage;
#[cfg(test)]
mod test;
added node/src/rad.rs
@@ -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();
+
    }
+
}
modified node/src/test/fixtures.rs
@@ -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::*;