node: Add verification on fetch
This commit is contained in:
parent
9cda5507a5
commit
38dc571f2e
|
|
@ -30,9 +30,9 @@ radicle-git-ext = { version = "0", features = ["serde"] }
|
||||||
nonempty = { version = "0.8.0", features = ["serialize"] }
|
nonempty = { version = "0.8.0", features = ["serialize"] }
|
||||||
nakamoto-net = { version = "0.3.0" }
|
nakamoto-net = { version = "0.3.0" }
|
||||||
nakamoto-net-poll = { version = "0.3.0" }
|
nakamoto-net-poll = { version = "0.3.0" }
|
||||||
|
tempfile = { version = "3.3.0" }
|
||||||
thiserror = { version = "1" }
|
thiserror = { version = "1" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
quickcheck = { version = "1", default-features = false }
|
quickcheck = { version = "1", default-features = false }
|
||||||
quickcheck_macros = { version = "1", default-features = false }
|
quickcheck_macros = { version = "1", default-features = false }
|
||||||
tempfile = { version = "3.3.0" }
|
|
||||||
|
|
|
||||||
|
|
@ -83,23 +83,45 @@ pub fn initial_commit<'a>(
|
||||||
Ok(commit)
|
Ok(commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a commit.
|
/// Create a commit and update the given ref to it.
|
||||||
pub fn commit<'a>(
|
pub fn commit<'a>(
|
||||||
repo: &'a git2::Repository,
|
repo: &'a git2::Repository,
|
||||||
parent: &'a git2::Commit,
|
parent: &'a git2::Commit,
|
||||||
|
target: &RefStr,
|
||||||
message: &str,
|
message: &str,
|
||||||
user: &str,
|
user: &str,
|
||||||
) -> Result<git2::Commit<'a>, git2::Error> {
|
) -> Result<git2::Commit<'a>, git2::Error> {
|
||||||
let sig = git2::Signature::now(user, "anonymous@radicle.xyz")?;
|
let sig = git2::Signature::now(user, "anonymous@radicle.xyz")?;
|
||||||
let tree_id = repo.index()?.write_tree()?;
|
let tree_id = repo.index()?.write_tree()?;
|
||||||
let tree = repo.find_tree(tree_id)?;
|
let tree = repo.find_tree(tree_id)?;
|
||||||
// TODO: Take the ref as parameter.
|
let oid = repo.commit(Some(target.as_str()), &sig, &sig, message, &tree, &[parent])?;
|
||||||
let oid = repo.commit(None, &sig, &sig, message, &tree, &[parent])?;
|
|
||||||
let commit = repo.find_commit(oid).unwrap();
|
let commit = repo.find_commit(oid).unwrap();
|
||||||
|
|
||||||
Ok(commit)
|
Ok(commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push the refs to the radicle remote.
|
||||||
|
pub fn push(repo: &git2::Repository) -> Result<(), git2::Error> {
|
||||||
|
let mut remote = repo.find_remote("rad")?;
|
||||||
|
let refspecs = remote.push_refspecs().unwrap();
|
||||||
|
let refspec = refspecs.into_iter().next().unwrap().unwrap();
|
||||||
|
|
||||||
|
// The `git2` crate doesn't seem to support push refspecs with '*' in them,
|
||||||
|
// so we manually replace it with the current branch.
|
||||||
|
let head = repo.head().unwrap();
|
||||||
|
let branch = head.shorthand().unwrap();
|
||||||
|
let refspec = refspec.replace('*', branch);
|
||||||
|
|
||||||
|
remote.push::<&str>(&[&refspec], None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the repository head.
|
||||||
|
pub fn head(repo: &git2::Repository) -> Result<git2::Commit, git2::Error> {
|
||||||
|
let head = repo.head()?.peel_to_commit()?;
|
||||||
|
|
||||||
|
Ok(head)
|
||||||
|
}
|
||||||
|
|
||||||
/// Configure a repository's radicle remote.
|
/// Configure a repository's radicle remote.
|
||||||
///
|
///
|
||||||
/// Takes the repository in which to configure the remote, the name of the remote, the public
|
/// Takes the repository in which to configure the remote, the name of the remote, the public
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,8 @@ pub enum FetchError {
|
||||||
Git(#[from] git2::Error),
|
Git(#[from] git2::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Storage(#[from] storage::Error),
|
Storage(#[from] storage::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Fetch(#[from] storage::FetchError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of looking up seeds in our routing table.
|
/// Result of looking up seeds in our routing table.
|
||||||
|
|
@ -89,6 +91,7 @@ pub enum FetchLookup {
|
||||||
|
|
||||||
/// Result of a fetch request from a specific seed.
|
/// Result of a fetch request from a specific seed.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum FetchResult {
|
pub enum FetchResult {
|
||||||
/// Successful fetch from a seed.
|
/// Successful fetch from a seed.
|
||||||
Fetched {
|
Fetched {
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ mod tests {
|
||||||
let signer = crypto::MockSigner::default();
|
let signer = crypto::MockSigner::default();
|
||||||
let public_key = *signer.public_key();
|
let public_key = *signer.public_key();
|
||||||
let mut storage = Storage::open(tempdir.path().join("storage")).unwrap();
|
let mut storage = Storage::open(tempdir.path().join("storage")).unwrap();
|
||||||
let repo = fixtures::repository(tempdir.path().join("working"));
|
let (repo, _) = fixtures::repository(tempdir.path().join("working"));
|
||||||
|
|
||||||
let (proj, refs) = init(
|
let (proj, refs) = init(
|
||||||
&repo,
|
&repo,
|
||||||
|
|
@ -280,7 +280,7 @@ mod tests {
|
||||||
let bob = crypto::MockSigner::new(&mut rng);
|
let bob = crypto::MockSigner::new(&mut rng);
|
||||||
let bob_id = bob.public_key();
|
let bob_id = bob.public_key();
|
||||||
let mut storage = Storage::open(tempdir.path().join("storage")).unwrap();
|
let mut storage = Storage::open(tempdir.path().join("storage")).unwrap();
|
||||||
let original = fixtures::repository(tempdir.path().join("original"));
|
let (original, _) = fixtures::repository(tempdir.path().join("original"));
|
||||||
|
|
||||||
// Alice creates a project.
|
// Alice creates a project.
|
||||||
let (id, alice_refs) = init(
|
let (id, alice_refs) = init(
|
||||||
|
|
@ -311,7 +311,7 @@ mod tests {
|
||||||
let signer = crypto::MockSigner::default();
|
let signer = crypto::MockSigner::default();
|
||||||
let remote_id = signer.public_key();
|
let remote_id = signer.public_key();
|
||||||
let mut storage = Storage::open(tempdir.path().join("storage")).unwrap();
|
let mut storage = Storage::open(tempdir.path().join("storage")).unwrap();
|
||||||
let original = fixtures::repository(tempdir.path().join("original"));
|
let (original, _) = fixtures::repository(tempdir.path().join("original"));
|
||||||
|
|
||||||
let (id, _) = init(
|
let (id, _) = init(
|
||||||
&original,
|
&original,
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,18 @@ pub enum Error {
|
||||||
InvalidHead,
|
InvalidHead,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch error.
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
pub enum FetchError {
|
||||||
|
#[error("git: {0}")]
|
||||||
|
Git(#[from] git2::Error),
|
||||||
|
#[error("i/o: {0}")]
|
||||||
|
Io(#[from] io::Error),
|
||||||
|
#[error("verify: {0}")]
|
||||||
|
Verify(#[from] git::VerifyError),
|
||||||
|
}
|
||||||
|
|
||||||
pub type RemoteId = PublicKey;
|
pub type RemoteId = PublicKey;
|
||||||
|
|
||||||
/// An update to a reference.
|
/// An update to a reference.
|
||||||
|
|
@ -241,7 +253,7 @@ pub trait ReadRepository<'r> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait WriteRepository<'r>: ReadRepository<'r> {
|
pub trait WriteRepository<'r>: ReadRepository<'r> {
|
||||||
fn fetch(&mut self, url: &Url) -> Result<Vec<RefUpdate>, git2::Error>;
|
fn fetch(&mut self, url: &Url) -> Result<Vec<RefUpdate>, FetchError>;
|
||||||
fn raw(&self) -> &git2::Repository;
|
fn raw(&self) -> &git2::Repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{fmt, fs, io};
|
use std::{fmt, fs, io};
|
||||||
|
|
||||||
|
|
@ -14,7 +14,8 @@ use crate::identity::{Id, Project};
|
||||||
use crate::storage::refs;
|
use crate::storage::refs;
|
||||||
use crate::storage::refs::{Refs, SignedRefs};
|
use crate::storage::refs::{Refs, SignedRefs};
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
Error, Inventory, ReadRepository, ReadStorage, Remote, WriteRepository, WriteStorage,
|
Error, FetchError, Inventory, ReadRepository, ReadStorage, Remote, WriteRepository,
|
||||||
|
WriteStorage,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{RefUpdate, RemoteId};
|
use super::{RefUpdate, RemoteId};
|
||||||
|
|
@ -148,6 +149,24 @@ pub struct Repository {
|
||||||
// git config for later.
|
// git config for later.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum VerifyError {
|
||||||
|
#[error("invalid remote `{0}`")]
|
||||||
|
InvalidRemote(RemoteId),
|
||||||
|
#[error("invalid target `{2}` for reference `{1}` of remote `{0}`")]
|
||||||
|
InvalidRefTarget(RemoteId, git::RefString, git2::Oid),
|
||||||
|
#[error("invalid reference")]
|
||||||
|
InvalidRef,
|
||||||
|
#[error("ref error: {0}")]
|
||||||
|
Ref(#[from] git::RefError),
|
||||||
|
#[error("refs error: {0}")]
|
||||||
|
Refs(#[from] refs::Error),
|
||||||
|
#[error("unknown reference `{1}` in remote `{0}`")]
|
||||||
|
UnknownRef(RemoteId, git::RefString),
|
||||||
|
#[error("git: {0}")]
|
||||||
|
Git(#[from] git2::Error),
|
||||||
|
}
|
||||||
|
|
||||||
impl Repository {
|
impl Repository {
|
||||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||||
let backend = match git2::Repository::open_bare(path.as_ref()) {
|
let backend = match git2::Repository::open_bare(path.as_ref()) {
|
||||||
|
|
@ -174,6 +193,39 @@ impl Repository {
|
||||||
Ok(Self { backend })
|
Ok(Self { backend })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn head(&self) -> Result<git2::Commit, git2::Error> {
|
||||||
|
// TODO: Find longest history, get document and get head.
|
||||||
|
// Perhaps we should even set a local `HEAD` or at least `refs/heads/master`
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&self) -> Result<(), VerifyError> {
|
||||||
|
let remotes = self.remotes()?.collect::<Result<HashMap<_, _>, _>>()?;
|
||||||
|
|
||||||
|
for r in self.backend.references()? {
|
||||||
|
let r = r?;
|
||||||
|
let name = r.name().ok_or(VerifyError::InvalidRef)?;
|
||||||
|
let oid = r.target().ok_or(VerifyError::InvalidRef)?;
|
||||||
|
let (remote, refname) = git::parse_ref::<RemoteId>(name)?;
|
||||||
|
|
||||||
|
if refname == *refs::SIGNATURE_REF {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let remote = remotes
|
||||||
|
.get(&remote)
|
||||||
|
.ok_or(VerifyError::InvalidRemote(remote))?;
|
||||||
|
let signed_oid = remote
|
||||||
|
.refs
|
||||||
|
.get(&refname)
|
||||||
|
.ok_or_else(|| VerifyError::UnknownRef(remote.id, refname.clone()))?;
|
||||||
|
|
||||||
|
if git::Oid::from(oid) != *signed_oid {
|
||||||
|
return Err(VerifyError::InvalidRefTarget(remote.id, refname, oid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inspect(&self) -> Result<(), Error> {
|
pub fn inspect(&self) -> Result<(), Error> {
|
||||||
for r in self.backend.references()? {
|
for r in self.backend.references()? {
|
||||||
let r = r?;
|
let r = r?;
|
||||||
|
|
@ -228,6 +280,7 @@ impl<'r> ReadRepository<'r> for Repository {
|
||||||
remote: &RemoteId,
|
remote: &RemoteId,
|
||||||
name: &git::RefStr,
|
name: &git::RefStr,
|
||||||
) -> Result<Option<git2::Reference>, git2::Error> {
|
) -> Result<Option<git2::Reference>, git2::Error> {
|
||||||
|
let name = name.strip_prefix(git::refname!("refs")).unwrap_or(name);
|
||||||
let name = format!("refs/remotes/{remote}/{name}");
|
let name = format!("refs/remotes/{remote}/{name}");
|
||||||
self.backend.find_reference(&name).map(Some).or_else(|e| {
|
self.backend.find_reference(&name).map(Some).or_else(|e| {
|
||||||
if git::ext::is_not_found_err(&e) {
|
if git::ext::is_not_found_err(&e) {
|
||||||
|
|
@ -288,7 +341,7 @@ impl<'r> ReadRepository<'r> for Repository {
|
||||||
|
|
||||||
impl<'r> WriteRepository<'r> for Repository {
|
impl<'r> WriteRepository<'r> for Repository {
|
||||||
/// Fetch all remotes of a project from the given URL.
|
/// Fetch all remotes of a project from the given URL.
|
||||||
fn fetch(&mut self, url: &git::Url) -> Result<Vec<RefUpdate>, git2::Error> {
|
fn fetch(&mut self, url: &git::Url) -> Result<Vec<RefUpdate>, FetchError> {
|
||||||
// TODO: Have function to fetch specific remotes.
|
// TODO: Have function to fetch specific remotes.
|
||||||
//
|
//
|
||||||
// Repository layout should look like this:
|
// Repository layout should look like this:
|
||||||
|
|
@ -303,6 +356,33 @@ impl<'r> WriteRepository<'r> for Repository {
|
||||||
let refs: &[&str] = &["refs/remotes/*:refs/remotes/*"];
|
let refs: &[&str] = &["refs/remotes/*:refs/remotes/*"];
|
||||||
let mut updates = Vec::new();
|
let mut updates = Vec::new();
|
||||||
let mut callbacks = git2::RemoteCallbacks::new();
|
let mut callbacks = git2::RemoteCallbacks::new();
|
||||||
|
let tempdir = tempfile::tempdir()?;
|
||||||
|
// TODO: Comment
|
||||||
|
let staging = {
|
||||||
|
let mut builder = git2::build::RepoBuilder::new();
|
||||||
|
let path = tempdir.path().join("git");
|
||||||
|
let staging_repo = builder
|
||||||
|
.bare(true)
|
||||||
|
// TODO: Comment
|
||||||
|
.clone_local(git2::build::CloneLocal::Local)
|
||||||
|
.clone(
|
||||||
|
&git::Url {
|
||||||
|
scheme: git::url::Scheme::File,
|
||||||
|
path: self.backend.path().to_string_lossy().to_string().into(),
|
||||||
|
..git::Url::default()
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
&path,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
staging_repo
|
||||||
|
.remote_anonymous(&url)?
|
||||||
|
.fetch(refs, None, None)?;
|
||||||
|
// TODO: Comment
|
||||||
|
Repository::from(staging_repo).verify()?;
|
||||||
|
|
||||||
|
path
|
||||||
|
};
|
||||||
|
|
||||||
callbacks.update_tips(|name, old, new| {
|
callbacks.update_tips(|name, old, new| {
|
||||||
if let Ok(name) = git::RefString::try_from(name) {
|
if let Ok(name) = git::RefString::try_from(name) {
|
||||||
|
|
@ -316,7 +396,14 @@ impl<'r> WriteRepository<'r> for Repository {
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut remote = self.backend.remote_anonymous(&url)?;
|
let mut remote = self.backend.remote_anonymous(
|
||||||
|
&git::Url {
|
||||||
|
scheme: git::url::Scheme::File,
|
||||||
|
path: staging.to_string_lossy().to_string().into(),
|
||||||
|
..git::Url::default()
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
)?;
|
||||||
let mut opts = git2::FetchOptions::default();
|
let mut opts = git2::FetchOptions::default();
|
||||||
opts.remote_callbacks(callbacks);
|
opts.remote_callbacks(callbacks);
|
||||||
|
|
||||||
|
|
@ -343,9 +430,10 @@ impl From<git2::Repository> for Repository {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::assert_matches;
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::storage::refs::SIGNATURE_REF;
|
use crate::storage::refs::SIGNATURE_REF;
|
||||||
use crate::storage::{ReadStorage, WriteRepository};
|
use crate::storage::{ReadStorage, RefUpdate, WriteRepository};
|
||||||
use crate::test::arbitrary;
|
use crate::test::arbitrary;
|
||||||
use crate::test::crypto::MockSigner;
|
use crate::test::crypto::MockSigner;
|
||||||
use crate::test::fixtures;
|
use crate::test::fixtures;
|
||||||
|
|
@ -410,6 +498,13 @@ mod tests {
|
||||||
// Four refs are created for each remote.
|
// Four refs are created for each remote.
|
||||||
assert_eq!(updates.len(), remotes.len() * 4);
|
assert_eq!(updates.len(), remotes.len() * 4);
|
||||||
|
|
||||||
|
for update in updates {
|
||||||
|
assert_matches!(
|
||||||
|
update,
|
||||||
|
RefUpdate::Created { name, .. } if name.starts_with("refs/remotes")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for remote in remotes {
|
for remote in remotes {
|
||||||
let (id, _) = remote.unwrap();
|
let (id, _) = remote.unwrap();
|
||||||
let alice_repo = alice.repository(proj).unwrap();
|
let alice_repo = alice.repository(proj).unwrap();
|
||||||
|
|
@ -422,6 +517,57 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fetch_update() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let alice = Storage::open(tmp.path().join("alice/storage")).unwrap();
|
||||||
|
let bob = Storage::open(tmp.path().join("bob/storage")).unwrap();
|
||||||
|
|
||||||
|
let alice_signer = MockSigner::new(&mut fastrand::Rng::new());
|
||||||
|
let alice_id = alice_signer.public_key();
|
||||||
|
let (proj_id, _, proj_repo, alice_head) =
|
||||||
|
fixtures::project(tmp.path().join("alice/project"), &alice, &alice_signer).unwrap();
|
||||||
|
|
||||||
|
let refname = git::refname!("refs/heads/master");
|
||||||
|
let alice_url = git::Url {
|
||||||
|
scheme: git_url::Scheme::File,
|
||||||
|
path: alice
|
||||||
|
.path()
|
||||||
|
.join(proj_id.to_string())
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned()
|
||||||
|
.into(),
|
||||||
|
..git::Url::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Have Bob fetch Alice's refs.
|
||||||
|
let updates = bob.repository(&proj_id).unwrap().fetch(&alice_url).unwrap();
|
||||||
|
// Three refs are created: the branch, the signature and the id.
|
||||||
|
assert_eq!(updates.len(), 3);
|
||||||
|
|
||||||
|
let alice_proj_storage = alice.repository(&proj_id).unwrap();
|
||||||
|
let alice_head = proj_repo.find_commit(alice_head).unwrap();
|
||||||
|
let alice_head = git::commit(&proj_repo, &alice_head, &refname, "Making changes", "Alice")
|
||||||
|
.unwrap()
|
||||||
|
.id();
|
||||||
|
git::push(&proj_repo).unwrap();
|
||||||
|
alice.sign_refs(&alice_proj_storage, &alice_signer).unwrap();
|
||||||
|
|
||||||
|
// Have Bob fetch Alice's new commit.
|
||||||
|
let updates = bob.repository(&proj_id).unwrap().fetch(&alice_url).unwrap();
|
||||||
|
// The branch and signature refs are updated.
|
||||||
|
assert_matches!(
|
||||||
|
updates.as_slice(),
|
||||||
|
&[RefUpdate::Updated { .. }, RefUpdate::Updated { .. }]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bob's storage is updated.
|
||||||
|
let bob_repo = bob.repository(&proj_id).unwrap();
|
||||||
|
let bob_master = bob_repo.reference(alice_id, &refname).unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(bob_master.target().unwrap(), alice_head);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sign_refs() {
|
fn test_sign_refs() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
|
@ -435,15 +581,14 @@ mod tests {
|
||||||
let sig = git2::Signature::now(&alice.to_string(), "anonymous@radicle.xyz").unwrap();
|
let sig = git2::Signature::now(&alice.to_string(), "anonymous@radicle.xyz").unwrap();
|
||||||
let head = git::initial_commit(backend, &sig).unwrap();
|
let head = git::initial_commit(backend, &sig).unwrap();
|
||||||
|
|
||||||
let head = git::commit(backend, &head, "Second commit", &alice.to_string()).unwrap();
|
git::commit(
|
||||||
backend
|
backend,
|
||||||
.reference(
|
&head,
|
||||||
&format!("refs/remotes/{alice}/heads/master"),
|
&git::RefString::try_from(format!("refs/remotes/{alice}/heads/master")).unwrap(),
|
||||||
head.id(),
|
"Second commit",
|
||||||
false,
|
&alice.to_string(),
|
||||||
"test",
|
)
|
||||||
)
|
.unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let signed = storage.sign_refs(&project, &signer).unwrap();
|
let signed = storage.sign_refs(&project, &signer).unwrap();
|
||||||
let remote = project.remote(&alice).unwrap();
|
let remote = project.remote(&alice).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::crypto::Signer as _;
|
use crate::crypto::{Signer, Verified};
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::identity::Id;
|
use crate::identity::Id;
|
||||||
|
use crate::rad;
|
||||||
use crate::storage::git::Storage;
|
use crate::storage::git::Storage;
|
||||||
use crate::storage::WriteStorage;
|
use crate::storage::refs::SignedRefs;
|
||||||
|
use crate::storage::{BranchName, WriteStorage};
|
||||||
use crate::test::arbitrary;
|
use crate::test::arbitrary;
|
||||||
use crate::test::crypto::MockSigner;
|
use crate::test::crypto::MockSigner;
|
||||||
|
|
||||||
|
|
@ -37,21 +39,21 @@ pub fn storage<P: AsRef<Path>>(path: P) -> Storage {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let head = git::commit(raw, &head, "Second commit", &remote.to_string()).unwrap();
|
let head = git::commit(
|
||||||
raw.reference(
|
raw,
|
||||||
&format!("refs/remotes/{remote}/heads/master"),
|
&head,
|
||||||
head.id(),
|
&git::RefString::try_from(format!("refs/remotes/{remote}/heads/master")).unwrap(),
|
||||||
false,
|
"Second commit",
|
||||||
"test",
|
&remote.to_string(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let head = git::commit(raw, &head, "Third commit", &remote.to_string()).unwrap();
|
git::commit(
|
||||||
raw.reference(
|
raw,
|
||||||
&format!("refs/remotes/{remote}/heads/patch/3"),
|
&head,
|
||||||
head.id(),
|
&git::RefString::try_from(format!("refs/remotes/{remote}/heads/patch/3")).unwrap(),
|
||||||
false,
|
"Third commit",
|
||||||
"test",
|
&remote.to_string(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
@ -61,16 +63,44 @@ pub fn storage<P: AsRef<Path>>(path: P) -> Storage {
|
||||||
storage
|
storage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new repository at the given path, and initialize it into a project.
|
||||||
|
pub fn project<'r, P: AsRef<Path>, S: WriteStorage<'r>, G: Signer>(
|
||||||
|
path: P,
|
||||||
|
storage: S,
|
||||||
|
signer: G,
|
||||||
|
) -> Result<(Id, SignedRefs<Verified>, git2::Repository, git2::Oid), rad::InitError> {
|
||||||
|
let (repo, head) = repository(path);
|
||||||
|
let (id, refs) = rad::init(
|
||||||
|
&repo,
|
||||||
|
"acme",
|
||||||
|
"Acme's repository",
|
||||||
|
BranchName::from("master"),
|
||||||
|
signer,
|
||||||
|
storage,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok((id, refs, repo, head))
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a regular repository at the given path with a couple of commits.
|
/// Creates a regular repository at the given path with a couple of commits.
|
||||||
pub fn repository<P: AsRef<Path>>(path: P) -> git2::Repository {
|
pub fn repository<P: AsRef<Path>>(path: P) -> (git2::Repository, git2::Oid) {
|
||||||
let repo = git2::Repository::init(path).unwrap();
|
let repo = git2::Repository::init(path).unwrap();
|
||||||
{
|
let sig = git2::Signature::now("anonymous", "anonymous@radicle.xyz").unwrap();
|
||||||
let sig = git2::Signature::now("anonymous", "anonymous@radicle.xyz").unwrap();
|
let head = git::initial_commit(&repo, &sig).unwrap();
|
||||||
let head = git::initial_commit(&repo, &sig).unwrap();
|
let oid = git::commit(
|
||||||
let head = git::commit(&repo, &head, "Second commit", "anonymous").unwrap();
|
&repo,
|
||||||
let _branch = repo.branch("master", &head, false).unwrap();
|
&head,
|
||||||
}
|
git::refname!("refs/heads/master").as_refstr(),
|
||||||
repo
|
"Second commit",
|
||||||
|
"anonymous",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.id();
|
||||||
|
|
||||||
|
// Look, I don't really understand why we have to do this, but we do.
|
||||||
|
drop(head);
|
||||||
|
|
||||||
|
(repo, oid)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ use crate::git;
|
||||||
use crate::identity::{Id, Project};
|
use crate::identity::{Id, Project};
|
||||||
use crate::storage::{refs, RefUpdate};
|
use crate::storage::{refs, RefUpdate};
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
Error, Inventory, ReadRepository, ReadStorage, Remote, RemoteId, WriteRepository, WriteStorage,
|
Error, FetchError, Inventory, ReadRepository, ReadStorage, Remote, RemoteId, WriteRepository,
|
||||||
|
WriteStorage,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -119,7 +120,7 @@ impl ReadRepository<'_> for MockRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WriteRepository<'_> for MockRepository {
|
impl WriteRepository<'_> for MockRepository {
|
||||||
fn fetch(&mut self, _url: &Url) -> Result<Vec<RefUpdate>, git2::Error> {
|
fn fetch(&mut self, _url: &Url) -> Result<Vec<RefUpdate>, FetchError> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -386,7 +386,7 @@ fn test_push_and_pull() {
|
||||||
let tempdir = tempfile::tempdir().unwrap();
|
let tempdir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
let storage_alice = Storage::open(tempdir.path().join("alice").join("storage")).unwrap();
|
let storage_alice = Storage::open(tempdir.path().join("alice").join("storage")).unwrap();
|
||||||
let repo = fixtures::repository(tempdir.path().join("working"));
|
let (repo, _) = fixtures::repository(tempdir.path().join("working"));
|
||||||
let mut alice = Peer::new("alice", [7, 7, 7, 7], storage_alice);
|
let mut alice = Peer::new("alice", [7, 7, 7, 7], storage_alice);
|
||||||
|
|
||||||
let storage_bob = Storage::open(tempdir.path().join("bob").join("storage")).unwrap();
|
let storage_bob = Storage::open(tempdir.path().join("bob").join("storage")).unwrap();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue