radicle: Implement canonical fork

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-09-21 16:08:04 +02:00
parent a37424d723
commit f5bfb8d90a
No known key found for this signature in database
5 changed files with 67 additions and 5 deletions

View File

@ -500,8 +500,8 @@ mod test {
fixtures::project(tempdir.path().join("copy"), &storage, &alice).unwrap(); fixtures::project(tempdir.path().join("copy"), &storage, &alice).unwrap();
// Bob and Eve fork the project from Alice. // Bob and Eve fork the project from Alice.
rad::fork(&id, alice.public_key(), &bob, &storage).unwrap(); rad::fork_remote(&id, alice.public_key(), &bob, &storage).unwrap();
rad::fork(&id, alice.public_key(), &eve, &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 // 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 // want to have to create a repository object twice. Perhaps there should

View File

@ -88,12 +88,14 @@ pub enum ForkError {
Storage(#[from] storage::Error), Storage(#[from] storage::Error),
#[error("project `{0}` was not found in storage")] #[error("project `{0}` was not found in storage")]
NotFound(Id), NotFound(Id),
#[error("project identity error: {0}")]
InvalidIdentity(#[from] storage::git::IdentityError),
#[error("git: invalid reference")] #[error("git: invalid reference")]
InvalidReference, InvalidReference,
} }
/// Create a local tree for an existing project, from an existing remote. /// 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, proj: &Id,
remote: &RemoteId, remote: &RemoteId,
signer: G, signer: G,
@ -145,6 +147,55 @@ pub fn fork<'r, G: Signer, S: storage::WriteStorage<'r>>(
Ok(()) 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)] #[derive(Error, Debug)]
pub enum CheckoutError { pub enum CheckoutError {
#[error("git: {0}")] #[error("git: {0}")]
@ -248,7 +299,6 @@ mod tests {
let mut rng = fastrand::Rng::new(); let mut rng = fastrand::Rng::new();
let tempdir = tempfile::tempdir().unwrap(); let tempdir = tempfile::tempdir().unwrap();
let alice = MockSigner::new(&mut rng); let alice = MockSigner::new(&mut rng);
let alice_id = alice.public_key();
let bob = MockSigner::new(&mut rng); let bob = MockSigner::new(&mut rng);
let bob_id = bob.public_key(); let bob_id = bob.public_key();
let storage = Storage::open(tempdir.path().join("storage")).unwrap(); let storage = Storage::open(tempdir.path().join("storage")).unwrap();
@ -266,7 +316,7 @@ mod tests {
.unwrap(); .unwrap();
// Bob forks it and creates a checkout. // 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(); checkout(&id, bob_id, tempdir.path().join("copy"), &storage).unwrap();
let bob_remote = storage.repository(&id).unwrap().remote(bob_id).unwrap(); let bob_remote = storage.repository(&id).unwrap().remote(bob_id).unwrap();

View File

@ -19,6 +19,7 @@ use crate::git::Url;
use crate::git::{RefError, RefStr, RefString}; use crate::git::{RefError, RefStr, RefString};
use crate::identity; use crate::identity;
use crate::identity::{Id, IdError, Project}; use crate::identity::{Id, IdError, Project};
use crate::storage::git::IdentityError;
use crate::storage::refs::Refs; use crate::storage::refs::Refs;
use self::refs::SignedRefs; use self::refs::SignedRefs;
@ -254,6 +255,7 @@ pub trait ReadRepository<'r> {
fn remotes(&'r self) -> Result<Self::Remotes, git2::Error>; fn remotes(&'r self) -> Result<Self::Remotes, git2::Error>;
/// Return the project associated with this repository. /// Return the project associated with this repository.
fn project(&self) -> Result<Project, Error>; fn project(&self) -> Result<Project, Error>;
fn project_identity(&self) -> Result<(Oid, identity::Doc<Unverified>), IdentityError>;
} }
pub trait WriteRepository<'r>: ReadRepository<'r> { pub trait WriteRepository<'r>: ReadRepository<'r> {

View File

@ -448,6 +448,10 @@ impl<'r> ReadRepository<'r> for Repository {
fn project(&self) -> Result<Project, Error> { fn project(&self) -> Result<Project, Error> {
todo!() todo!()
} }
fn project_identity(&self) -> Result<(Oid, identity::Doc<Unverified>), IdentityError> {
Repository::identity(self)
}
} }
impl<'r> WriteRepository<'r> for Repository { impl<'r> WriteRepository<'r> for Repository {

View File

@ -125,6 +125,12 @@ impl ReadRepository<'_> for MockRepository {
fn project(&self) -> Result<Project, Error> { fn project(&self) -> Result<Project, Error> {
todo!() todo!()
} }
fn project_identity(
&self,
) -> Result<(Oid, crate::identity::Doc<crate::crypto::Unverified>), git::IdentityError> {
todo!()
}
} }
impl WriteRepository<'_> for MockRepository { impl WriteRepository<'_> for MockRepository {