diff --git a/radicle-cli/src/git.rs b/radicle-cli/src/git.rs index 2268c363..e547ab1d 100644 --- a/radicle-cli/src/git.rs +++ b/radicle-cli/src/git.rs @@ -10,12 +10,14 @@ use std::str::FromStr; use anyhow::anyhow; use anyhow::Context as _; +use thiserror::Error; use radicle::crypto::ssh; use radicle::git; use radicle::git::raw as git2; use radicle::git::{Version, VERSION_REQUIRED}; use radicle::prelude::{Id, NodeId}; +use radicle::storage::git::transport; pub use radicle::git::raw::{ build::CheckoutBuilder, AnnotatedCommit, Commit, Direction, ErrorCode, MergeAnalysis, @@ -60,6 +62,45 @@ impl From for Rev { } } +#[derive(Error, Debug)] +pub enum RemoteError { + #[error("url malformed: {0}")] + ParseUrl(#[from] transport::local::UrlError), + #[error("remote `url` not found")] + MissingUrl, + #[error("remote `name` not found")] + MissingName, +} + +#[derive(Clone)] +pub struct Remote<'a> { + pub(crate) name: String, + pub(crate) url: radicle::git::Url, + inner: git2::Remote<'a>, +} + +impl Remote<'_> { + pub fn refspecs(&self) -> git2::Refspecs { + self.inner.refspecs() + } +} + +impl<'a> TryFrom> for Remote<'a> { + type Error = RemoteError; + + fn try_from(value: git2::Remote<'a>) -> Result { + let url = value.url().map_or(Err(RemoteError::MissingUrl), |url| { + Ok(radicle::git::Url::from_str(url)?) + })?; + let name = value.name().ok_or(RemoteError::MissingName)?; + Ok(Self { + name: name.to_owned(), + url, + inner: value, + }) + } +} + /// Get the git repository in the current directory. pub fn repository() -> Result { match Repository::open(".") { @@ -175,24 +216,27 @@ pub fn is_signing_configured(repo: &Path) -> Result { } /// Return the list of radicle remotes for the given repository. -pub fn remotes(repo: &git2::Repository) -> anyhow::Result> { - let mut remotes = Vec::new(); - - for name in repo.remotes().iter().flatten().flatten() { - let remote = repo.find_remote(name)?; - for refspec in remote.refspecs() { - if refspec.direction() != git2::Direction::Fetch { - continue; - } - if let Some((peer, _)) = refspec.src().and_then(self::parse_remote) { - remotes.push((name.to_owned(), peer)); - } - } - } - +pub fn rad_remotes(repo: &git2::Repository) -> anyhow::Result> { + let remotes: Vec<_> = repo + .remotes()? + .iter() + .filter_map(|name| { + let remote = repo.find_remote(name?).ok()?; + Remote::try_from(remote).ok() + }) + .collect(); Ok(remotes) } +/// Check if the git remote is configured for the `Repository`. +pub fn is_remote(repo: &git2::Repository, alias: &str) -> anyhow::Result { + match repo.find_remote(alias) { + Ok(_) => Ok(true), + Err(err) if err.code() == git2::ErrorCode::NotFound => Ok(false), + Err(err) => Err(err.into()), + } +} + /// Get the repository's "rad" remote. pub fn rad_remote(repo: &Repository) -> anyhow::Result<(git2::Remote, Id)> { match radicle::rad::remote(repo) { @@ -204,6 +248,15 @@ pub fn rad_remote(repo: &Repository) -> anyhow::Result<(git2::Remote, Id)> { } } +pub fn add_remote<'a>( + repo: &'a Repository, + name: &'a str, + url: &'a radicle::git::Url, +) -> anyhow::Result> { + let remote = repo.remote(name, &url.to_string())?; + Ok(Remote::try_from(remote)?) +} + /// Setup an upstream tracking branch for the given remote and branch. /// Creates the tracking branch if it does not exist. /// diff --git a/radicle-cli/src/terminal/args.rs b/radicle-cli/src/terminal/args.rs index 547c8250..2ab37b55 100644 --- a/radicle-cli/src/terminal/args.rs +++ b/radicle-cli/src/terminal/args.rs @@ -99,6 +99,14 @@ pub fn rid(val: &OsString) -> anyhow::Result { Id::from_str(&val).map_err(|_| anyhow!("invalid Repository ID '{}'", val)) } +pub fn pubkey(val: &OsString) -> anyhow::Result { + let Ok(did) = did(val) else { + let nid = nid(val)?; + return Ok(nid) + }; + Ok(did.as_key().to_owned()) +} + pub fn addr(val: &OsString) -> anyhow::Result
{ let val = val.to_string_lossy(); Address::from_str(&val).map_err(|_| anyhow!("invalid address '{}'", val))