Skip to content
This repository has been archived by the owner on Aug 1, 2022. It is now read-only.

feat(proxy): peer branches #678

Merged
merged 20 commits into from
Jul 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions proxy/src/coco.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ pub use radicle_surf::vcs::git::Stats;
pub mod config;
pub mod control;
mod peer;
pub use peer::{verify_user, Api, User, UserRevisions};
pub use peer::{verify_user, Api, User};

mod source;
pub use source::{
blob, branches, commit, commit_header, commits, local_state, tags, tree, Blob, BlobContent,
Branch, Commit, CommitHeader, Info, ObjectType, Person, Revision, Tag, Tree, TreeEntry,
blob, branches, commit, commit_header, commits, into_branch_type, local_state, revisions, tags,
tree, Blob, BlobContent, Branch, Commit, CommitHeader, Info, ObjectType, Person, Revision,
Revisions, Tag, Tree, TreeEntry,
};
111 changes: 26 additions & 85 deletions proxy/src/coco/peer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};

use nonempty::NonEmpty;
use serde::Serialize;

use librad::keys;
use librad::meta::entity;
use librad::meta::project;
Expand All @@ -17,28 +14,14 @@ pub use librad::net::peer::{PeerApi, PeerConfig};
use librad::paths;
use librad::peer::PeerId;
use librad::uri::RadUrn;
use radicle_surf::vcs::git::{self, git2, BranchType};
use radicle_surf::vcs::git::{self, git2};

use super::source;
use crate::error;
use crate::identity;
use crate::project::Project;

/// Export a verified [`user::User`] type.
pub type User = user::User<entity::Verified>;

/// Bundled response to retrieve both branches and tags for a user repo.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UserRevisions {
/// Owner of the repo.
pub(crate) identity: identity::Identity,
/// List of [`source::Branch`].
pub(crate) branches: Vec<source::Branch>,
/// List of [`source::Tag`].
pub(crate) tags: Vec<source::Tag>,
}

/// High-level interface to the coco monorepo and gossip layer.
pub struct Api {
/// Thread-safe wrapper around [`PeerApi`].
Expand Down Expand Up @@ -213,73 +196,6 @@ impl Api {
Ok(entities)
}

/// Get all [`UserRevisions`] for a given project.
///
/// # Parameters
///
/// * `owner` - the owner of this peer, i.e. the current user
/// * `urn` - the [`RadUrn`] pointing to the project we're interested in
///
/// # Errors
///
/// * [`error::Error::LibradLock`]
/// * [`error::Error::Git`]
pub fn revisions(
&self,
owner: &User,
urn: &RadUrn,
) -> Result<NonEmpty<UserRevisions>, error::Error> {
let project = self.get_project(urn)?;
let mut user_revisions = vec![];

let (local_branches, local_tags) = self.with_browser(urn, |browser| {
Ok((
source::branches(browser, Some(BranchType::Local))?,
source::tags(browser)?,
))
})?;

if !local_branches.is_empty() {
user_revisions.push(UserRevisions {
identity: (self.peer_id(), owner.clone()).into(),
branches: local_branches,
tags: local_tags,
})
}

let tracked_peers = {
let api = self.peer_api.lock().expect("unable to acquire lock");
let storage = api.storage().reopen()?;
let repo = storage.open_repo(urn.clone())?;
repo.tracked()?
};

for peer_id in tracked_peers {
let remote_branches = self.with_browser(&project.urn(), |browser| {
source::branches(
browser,
Some(BranchType::Remote {
name: Some(format!("{}/heads", peer_id)),
}),
)
})?;

let api = self.peer_api.lock().expect("unable to acquire lock");
let storage = api.storage().reopen()?;
let user = storage.get_rad_self_of(urn, peer_id.clone())?;

user_revisions.push(UserRevisions {
identity: (peer_id, user).into(),
branches: remote_branches,
// TODO(rudolfs): implement remote peer tags once we decide how
// https://radicle.community/t/git-tags/214
tags: vec![],
});
}

NonEmpty::from_vec(user_revisions).ok_or(error::Error::EmptyUserRevisions)
}

/// Get the project found at `urn`.
///
/// # Errors
Expand Down Expand Up @@ -434,6 +350,31 @@ impl Api {
let api = self.peer_api.lock().expect("unable to acquire lock");
Ok(api.storage().track(urn, remote)?)
}

/// Get the [`user::User`]s that are tracking this project, including their [`PeerId`].
///
/// # Errors
///
/// * If we could not acquire the lock
/// * If we could not open the storage
/// * If did not have the `urn` in storage
/// * If we could not fetch the tracked peers
/// * If we could not get the `rad/self` of the peer
pub fn tracked(
&self,
urn: &RadUrn,
) -> Result<Vec<(PeerId, user::User<entity::Draft>)>, error::Error> {
let api = self.peer_api.lock().expect("unable to acquire lock");
let storage = api.storage().reopen()?;
let repo = storage.open_repo(urn.clone())?;
repo.tracked()?
.map(move |peer_id| {
repo.get_rad_self_of(peer_id.clone())
.map(|user| (peer_id.clone(), user))
.map_err(error::Error::from)
})
.collect()
}
}

/// Verify a user using a fake resolver that resolves the user to itself.
Expand Down
107 changes: 96 additions & 11 deletions proxy/src/coco/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use std::convert::TryFrom;
use std::fmt;
use std::str::FromStr;

use nonempty::NonEmpty;
use serde::{Deserialize, Serialize};

use librad::peer;
use radicle_surf::{
diff, file_system,
vcs::git::{self, git2, BranchType, Browser, Rev},
Expand Down Expand Up @@ -205,7 +205,7 @@ pub struct TreeEntry {
/// A revision selector for a `Browser`.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "type")]
pub enum Revision {
pub enum Revision<P> {
/// Select a tag under the name provided.
Tag {
/// Name of the tag.
Expand All @@ -216,7 +216,7 @@ pub enum Revision {
/// Name of the branch.
name: String,
/// The remote peer, if specified.
peer_id: Option<peer::PeerId>,
peer_id: Option<P>,
xla marked this conversation as resolved.
Show resolved Hide resolved
},
/// Select a SHA1 under the name provided.
Sha {
Expand All @@ -225,10 +225,13 @@ pub enum Revision {
},
}

impl TryFrom<Revision> for Rev {
impl<P> TryFrom<Revision<P>> for Rev
where
P: ToString,
{
type Error = error::Error;

fn try_from(other: Revision) -> Result<Self, Self::Error> {
fn try_from(other: Revision<P>) -> Result<Self, Self::Error> {
match other {
Revision::Tag { name } => Ok(git::TagName::new(&name).into()),
Revision::Branch { name, peer_id } => Ok(match peer_id {
Expand All @@ -240,18 +243,35 @@ impl TryFrom<Revision> for Rev {
}
}

/// Bundled response to retrieve both [`Branch`]es and [`Tag`]s for a user's repo.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Revisions<P, U> {
/// The peer identifier for the user.
pub peer_id: P,
/// The user who owns these revisions.
pub user: U,
/// List of [`git::Branch`].
pub branches: Vec<Branch>,
/// List of [`git::Tag`].
pub tags: Vec<Tag>,
}

/// Returns the [`Blob`] for a file at `revision` under `path`.
///
/// # Errors
///
/// Will return [`error::Error`] if the project doesn't exist or a surf interaction fails.
pub fn blob(
pub fn blob<P>(
browser: &mut Browser,
default_branch: git::Branch,
maybe_revision: Option<Revision>,
maybe_revision: Option<Revision<P>>,
path: &str,
theme: Option<&Theme>,
) -> Result<Blob, error::Error> {
) -> Result<Blob, error::Error>
where
P: ToString,
{
let maybe_revision = maybe_revision.map(Rev::try_from).transpose()?;
browser.rev(maybe_revision.unwrap_or_else(|| default_branch.into()))?;

Expand Down Expand Up @@ -492,12 +512,15 @@ pub fn tags<'repo>(browser: &Browser<'repo>) -> Result<Vec<Tag>, error::Error> {
/// # Errors
///
/// Will return [`error::Error`] if any of the surf interactions fail.
pub fn tree<'repo>(
pub fn tree<'repo, P>(
browser: &mut Browser<'repo>,
default_branch: git::Branch,
maybe_revision: Option<Revision>,
maybe_revision: Option<Revision<P>>,
maybe_prefix: Option<String>,
) -> Result<Tree, error::Error> {
) -> Result<Tree, error::Error>
where
P: ToString,
{
let maybe_revision = maybe_revision.map(Rev::try_from).transpose()?;
let revision = maybe_revision.unwrap_or_else(|| default_branch.into());
let prefix = maybe_prefix.unwrap_or_default();
Expand Down Expand Up @@ -582,6 +605,68 @@ pub fn tree<'repo>(
})
}

/// Get all [`Revisions`] for a given project.
///
/// # Parameters
///
/// * `peer_id` - the identifier of this peer
/// * `owner` - the owner of this peer, i.e. the current user
/// * `peers` - an iterator of a peer and the default self it used for this project
///
/// # Errors
///
/// * [`error::Error::LibradLock`]
/// * [`error::Error::Git`]
pub fn revisions<P, U>(
browser: &Browser,
peer_id: P,
owner: U,
peers: Vec<(P, U)>,
) -> Result<NonEmpty<Revisions<P, U>>, error::Error>
where
P: Clone + ToString,
{
let mut user_revisions = vec![];

let local_branches = branches(browser, Some(BranchType::Local))?;
if !local_branches.is_empty() {
user_revisions.push(Revisions {
peer_id,
user: owner,
branches: local_branches,
tags: tags(browser)?,
})
}

for (peer_id, user) in peers {
let remote_branches = branches(browser, Some(into_branch_type(Some(peer_id.clone()))))?;

user_revisions.push(Revisions {
peer_id,
user,
branches: remote_branches,
// TODO(rudolfs): implement remote peer tags once we decide how
// https://radicle.community/t/git-tags/214
tags: vec![],
});
}

NonEmpty::from_vec(user_revisions).ok_or(error::Error::EmptyRevisions)
}

/// Turn an `Option<P>` into a [`BranchType`]. If the `P` is present then this is
/// set as the remote of the `BranchType`. Otherwise, it's local branch.
#[must_use]
pub fn into_branch_type<P>(peer_id: Option<P>) -> BranchType
where
P: ToString,
{
peer_id.map_or(BranchType::Local, |peer_id| BranchType::Remote {
// We qualify the remotes as the PeerId + heads, otherwise we would grab the tags too.
name: Some(format!("{}/heads", peer_id.to_string())),
})
}

#[cfg(test)]
mod tests {
use librad::keys::SecretKey;
Expand Down
4 changes: 2 additions & 2 deletions proxy/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,12 @@ pub enum Error {
#[error("while calculating the number of confirmed transactions, we encountered an overflow")]
TransactionConfirmationOverflow,

/// We expect at least one [`coco::UserRevisions`] when looking at a project, however the
/// We expect at least one [`coco::Revisions`] when looking at a project, however the
/// computation found none.
#[error(
"while trying to get user revisions we could not find any, there should be at least one"
)]
EmptyUserRevisions,
EmptyRevisions,
}

impl From<registry::DispatchError> for Error {
Expand Down
Loading