diff --git a/radicle/src/identity/doc.rs b/radicle/src/identity/doc.rs index a3991292..ed34f137 100644 --- a/radicle/src/identity/doc.rs +++ b/radicle/src/identity/doc.rs @@ -500,8 +500,8 @@ mod test { fixtures::project(tempdir.path().join("copy"), &storage, &alice).unwrap(); // Bob and Eve fork the project from Alice. - rad::fork(&id, alice.public_key(), &bob, &storage).unwrap(); - rad::fork(&id, alice.public_key(), &eve, &storage).unwrap(); + rad::fork_remote(&id, alice.public_key(), &bob, &storage).unwrap(); + rad::fork_remote(&id, alice.public_key(), &eve, &storage).unwrap(); // TODO: In some cases we want to get the repo and the project, but don't // want to have to create a repository object twice. Perhaps there should diff --git a/radicle/src/rad.rs b/radicle/src/rad.rs index d96fc8f3..62f58e35 100644 --- a/radicle/src/rad.rs +++ b/radicle/src/rad.rs @@ -88,12 +88,14 @@ pub enum ForkError { Storage(#[from] storage::Error), #[error("project `{0}` was not found in storage")] NotFound(Id), + #[error("project identity error: {0}")] + InvalidIdentity(#[from] storage::git::IdentityError), #[error("git: invalid reference")] InvalidReference, } /// Create a local tree for an existing project, from an existing remote. -pub fn fork<'r, G: Signer, S: storage::WriteStorage<'r>>( +pub fn fork_remote<'r, G: Signer, S: storage::WriteStorage<'r>>( proj: &Id, remote: &RemoteId, signer: G, @@ -145,6 +147,55 @@ pub fn fork<'r, G: Signer, S: storage::WriteStorage<'r>>( Ok(()) } +pub fn fork<'r, G: Signer, S: storage::WriteStorage<'r>>( + proj: &Id, + signer: G, + storage: S, +) -> Result<(), ForkError> { + let me = signer.public_key(); + let repository = storage.repository(proj)?; + let (canonical_id, project) = repository.project_identity()?; + let raw = repository.raw(); + // TODO: Test the fork functions in isolation. + // TODO: Move to function on `Repository`. + let canonical_head = { + let mut heads = Vec::new(); + for delegate in project.delegates.iter() { + let name = format!("heads/{}", &project.default_branch); + let refname = git::RefString::try_from(name.as_str()).unwrap(); + let r = repository + .reference(&delegate.id, &refname)? + .unwrap() + .target() + .unwrap(); + + heads.push(r); + } + + match heads.as_slice() { + [head] => Ok(*head), + // FIXME: This branch is not tested. + heads => raw.merge_base_many(heads), + } + }?; + + raw.reference( + &git::refs::storage::branch(me, &project.default_branch), + canonical_head, + false, + &format!("creating default branch for {me}"), + )?; + raw.reference( + &git::refs::storage::id(me), + canonical_id.into(), + false, + &format!("creating identity branch for {me}"), + )?; + storage.sign_refs(&repository, &signer)?; + + Ok(()) +} + #[derive(Error, Debug)] pub enum CheckoutError { #[error("git: {0}")] @@ -248,7 +299,6 @@ mod tests { let mut rng = fastrand::Rng::new(); let tempdir = tempfile::tempdir().unwrap(); let alice = MockSigner::new(&mut rng); - let alice_id = alice.public_key(); let bob = MockSigner::new(&mut rng); let bob_id = bob.public_key(); let storage = Storage::open(tempdir.path().join("storage")).unwrap(); @@ -266,7 +316,7 @@ mod tests { .unwrap(); // Bob forks it and creates a checkout. - fork(&id, alice_id, &bob, &storage).unwrap(); + fork(&id, &bob, &storage).unwrap(); checkout(&id, bob_id, tempdir.path().join("copy"), &storage).unwrap(); let bob_remote = storage.repository(&id).unwrap().remote(bob_id).unwrap(); diff --git a/radicle/src/storage.rs b/radicle/src/storage.rs index 8901ec8c..189a372b 100644 --- a/radicle/src/storage.rs +++ b/radicle/src/storage.rs @@ -19,6 +19,7 @@ use crate::git::Url; use crate::git::{RefError, RefStr, RefString}; use crate::identity; use crate::identity::{Id, IdError, Project}; +use crate::storage::git::IdentityError; use crate::storage::refs::Refs; use self::refs::SignedRefs; @@ -254,6 +255,7 @@ pub trait ReadRepository<'r> { fn remotes(&'r self) -> Result; /// Return the project associated with this repository. fn project(&self) -> Result; + fn project_identity(&self) -> Result<(Oid, identity::Doc), IdentityError>; } pub trait WriteRepository<'r>: ReadRepository<'r> { diff --git a/radicle/src/storage/git.rs b/radicle/src/storage/git.rs index e51f6c66..eeab3691 100644 --- a/radicle/src/storage/git.rs +++ b/radicle/src/storage/git.rs @@ -448,6 +448,10 @@ impl<'r> ReadRepository<'r> for Repository { fn project(&self) -> Result { todo!() } + + fn project_identity(&self) -> Result<(Oid, identity::Doc), IdentityError> { + Repository::identity(self) + } } impl<'r> WriteRepository<'r> for Repository { diff --git a/radicle/src/test/storage.rs b/radicle/src/test/storage.rs index 21eb3224..a1bd6975 100644 --- a/radicle/src/test/storage.rs +++ b/radicle/src/test/storage.rs @@ -125,6 +125,12 @@ impl ReadRepository<'_> for MockRepository { fn project(&self) -> Result { todo!() } + + fn project_identity( + &self, + ) -> Result<(Oid, crate::identity::Doc), git::IdentityError> { + todo!() + } } impl WriteRepository<'_> for MockRepository {