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();
// 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

View File

@ -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();

View File

@ -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<Self::Remotes, git2::Error>;
/// Return the project associated with this repository.
fn project(&self) -> Result<Project, Error>;
fn project_identity(&self) -> Result<(Oid, identity::Doc<Unverified>), IdentityError>;
}
pub trait WriteRepository<'r>: ReadRepository<'r> {

View File

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

View File

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