radicle: Implement canonical fork
Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
a37424d723
commit
f5bfb8d90a
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue