improve host url and repo name detection

This commit is contained in:
Cyborus 2024-04-17 15:41:06 -04:00
parent a014a4e87a
commit 46cd32ebd8
No known key found for this signature in database
4 changed files with 319 additions and 135 deletions

View file

@ -1,14 +1,24 @@
use clap::Subcommand; use clap::{Args, Subcommand};
use eyre::eyre; use eyre::{eyre, OptionExt};
use forgejo_api::structs::{ use forgejo_api::structs::{
Comment, CreateIssueCommentOption, CreateIssueOption, EditIssueOption, IssueGetCommentsQuery, Comment, CreateIssueCommentOption, CreateIssueOption, EditIssueOption, IssueGetCommentsQuery,
}; };
use forgejo_api::Forgejo; use forgejo_api::Forgejo;
use crate::repo::RepoInfo; use crate::repo::{RepoInfo, RepoName};
#[derive(Args, Clone, Debug)]
pub struct IssueCommand {
#[clap(long, short = 'R')]
remote: Option<String>,
#[clap(long, short)]
repo: Option<String>,
#[clap(subcommand)]
command: IssueSubcommand,
}
#[derive(Subcommand, Clone, Debug)] #[derive(Subcommand, Clone, Debug)]
pub enum IssueCommand { pub enum IssueSubcommand {
Create { Create {
title: String, title: String,
#[clap(long)] #[clap(long)]
@ -86,11 +96,12 @@ pub enum ViewCommand {
} }
impl IssueCommand { impl IssueCommand {
pub async fn run(self, keys: &crate::KeyInfo, remote_name: Option<&str>) -> eyre::Result<()> { pub async fn run(self, keys: &crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
use IssueCommand::*; use IssueSubcommand::*;
let repo = RepoInfo::get_current(remote_name)?; let repo = RepoInfo::get_current(host_name, self.repo.as_deref(), self.remote.as_deref())?;
let api = keys.get_api(&repo.host_url())?; let api = keys.get_api(repo.host_url())?;
match self { let repo = repo.name().ok_or_eyre("couldn't get repo name, try specifying with --repo")?;
match self.command {
Create { title, body } => create_issue(&repo, &api, title, body).await?, Create { title, body } => create_issue(&repo, &api, title, body).await?,
View { id, command } => match command.unwrap_or(ViewCommand::Body) { View { id, command } => match command.unwrap_or(ViewCommand::Body) {
ViewCommand::Body => view_issue(&repo, &api, id).await?, ViewCommand::Body => view_issue(&repo, &api, id).await?,
@ -122,7 +133,7 @@ impl IssueCommand {
} }
async fn create_issue( async fn create_issue(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
title: String, title: String,
body: Option<String>, body: Option<String>,
@ -163,7 +174,7 @@ async fn create_issue(
Ok(()) Ok(())
} }
async fn view_issue(repo: &RepoInfo, api: &Forgejo, id: u64) -> eyre::Result<()> { async fn view_issue(repo: &RepoName, api: &Forgejo, id: u64) -> eyre::Result<()> {
let issue = api.issue_get_issue(repo.owner(), repo.name(), id).await?; let issue = api.issue_get_issue(repo.owner(), repo.name(), id).await?;
let title = issue let title = issue
.title .title
@ -186,7 +197,7 @@ async fn view_issue(repo: &RepoInfo, api: &Forgejo, id: u64) -> eyre::Result<()>
Ok(()) Ok(())
} }
async fn view_issues( async fn view_issues(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
query_str: Option<String>, query_str: Option<String>,
labels: Option<String>, labels: Option<String>,
@ -240,7 +251,7 @@ async fn view_issues(
Ok(()) Ok(())
} }
async fn view_comment(repo: &RepoInfo, api: &Forgejo, id: u64, idx: usize) -> eyre::Result<()> { async fn view_comment(repo: &RepoName, api: &Forgejo, id: u64, idx: usize) -> eyre::Result<()> {
let query = IssueGetCommentsQuery { let query = IssueGetCommentsQuery {
since: None, since: None,
before: None, before: None,
@ -255,7 +266,7 @@ async fn view_comment(repo: &RepoInfo, api: &Forgejo, id: u64, idx: usize) -> ey
Ok(()) Ok(())
} }
async fn view_comments(repo: &RepoInfo, api: &Forgejo, id: u64) -> eyre::Result<()> { async fn view_comments(repo: &RepoName, api: &Forgejo, id: u64) -> eyre::Result<()> {
let query = IssueGetCommentsQuery { let query = IssueGetCommentsQuery {
since: None, since: None,
before: None, before: None,
@ -294,7 +305,7 @@ fn print_comment(comment: &Comment) -> eyre::Result<()> {
Ok(()) Ok(())
} }
async fn browse_issue(repo: &RepoInfo, api: &Forgejo, id: Option<u64>) -> eyre::Result<()> { async fn browse_issue(repo: &RepoName, api: &Forgejo, id: Option<u64>) -> eyre::Result<()> {
match id { match id {
Some(id) => { Some(id) => {
let issue = api.issue_get_issue(repo.owner(), repo.name(), id).await?; let issue = api.issue_get_issue(repo.owner(), repo.name(), id).await?;
@ -317,7 +328,7 @@ async fn browse_issue(repo: &RepoInfo, api: &Forgejo, id: Option<u64>) -> eyre::
} }
async fn add_comment( async fn add_comment(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
issue: u64, issue: u64,
body: Option<String>, body: Option<String>,
@ -344,7 +355,7 @@ async fn add_comment(
} }
async fn edit_title( async fn edit_title(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
issue: u64, issue: u64,
new_title: Option<String>, new_title: Option<String>,
@ -390,7 +401,7 @@ async fn edit_title(
} }
async fn edit_body( async fn edit_body(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
issue: u64, issue: u64,
new_body: Option<String>, new_body: Option<String>,
@ -430,7 +441,7 @@ async fn edit_body(
} }
async fn edit_comment( async fn edit_comment(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
issue: u64, issue: u64,
idx: usize, idx: usize,
@ -478,7 +489,7 @@ async fn edit_comment(
} }
async fn close_issue( async fn close_issue(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
issue: u64, issue: u64,
message: Option<Option<String>>, message: Option<Option<String>>,

View file

@ -1,7 +1,6 @@
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use eyre::eyre; use eyre::{eyre, Context};
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use url::Url;
mod keys; mod keys;
use keys::*; use keys::*;
@ -13,8 +12,8 @@ mod repo;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
pub struct App { pub struct App {
#[clap(long, short = 'R')] #[clap(long, short = 'H')]
remote: Option<String>, host: Option<String>,
#[clap(subcommand)] #[clap(subcommand)]
command: Command, command: Command,
} }
@ -23,15 +22,13 @@ pub struct App {
pub enum Command { pub enum Command {
#[clap(subcommand)] #[clap(subcommand)]
Repo(repo::RepoCommand), Repo(repo::RepoCommand),
#[clap(subcommand)]
Issue(issues::IssueCommand), Issue(issues::IssueCommand),
User { User {
#[clap(long, short)] #[clap(long, short)]
host: Option<String>, remote: Option<String>,
}, },
#[clap(subcommand)] #[clap(subcommand)]
Auth(auth::AuthCommand), Auth(auth::AuthCommand),
#[clap(subcommand)]
Release(release::ReleaseCommand), Release(release::ReleaseCommand),
} }
@ -40,21 +37,18 @@ async fn main() -> eyre::Result<()> {
let args = App::parse(); let args = App::parse();
let mut keys = KeyInfo::load().await?; let mut keys = KeyInfo::load().await?;
let remote_name = args.remote.as_deref(); let host_name = args.host.as_deref();
// let remote = repo::RepoInfo::get_current(host_name, remote_name)?;
match args.command { match args.command {
Command::Repo(subcommand) => subcommand.run(&keys, remote_name).await?, Command::Repo(subcommand) => subcommand.run(&keys, host_name).await?,
Command::Issue(subcommand) => subcommand.run(&keys, remote_name).await?, Command::Issue(subcommand) => subcommand.run(&keys, host_name).await?,
Command::User { host } => { Command::User { remote } => {
let host = host.map(|host| Url::parse(&host)).transpose()?; let url = repo::RepoInfo::get_current(host_name, None, remote.as_deref()).wrap_err("could not find host, try specifying with --host")?.host_url().clone();
let url = match host {
Some(url) => url,
None => repo::RepoInfo::get_current(remote_name)?.url().clone(),
};
let name = keys.get_login(&url)?.username(); let name = keys.get_login(&url)?.username();
eprintln!("currently signed in to {name}@{url}"); eprintln!("currently signed in to {name}@{url}");
} }
Command::Auth(subcommand) => subcommand.run(&mut keys).await?, Command::Auth(subcommand) => subcommand.run(&mut keys).await?,
Command::Release(subcommand) => subcommand.run(&mut keys, remote_name).await?, Command::Release(subcommand) => subcommand.run(&mut keys, host_name).await?,
} }
keys.save().await?; keys.save().await?;

View file

@ -1,15 +1,25 @@
use clap::Subcommand; use clap::{Subcommand, Args};
use eyre::{bail, eyre}; use eyre::{bail, eyre, OptionExt};
use forgejo_api::{ use forgejo_api::{
structs::{RepoCreateReleaseAttachmentQuery, RepoListReleasesQuery}, structs::{RepoCreateReleaseAttachmentQuery, RepoListReleasesQuery},
Forgejo, Forgejo,
}; };
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use crate::{keys::KeyInfo, repo::RepoInfo}; use crate::{keys::KeyInfo, repo::{RepoInfo, RepoName}};
#[derive(Args, Clone, Debug)]
pub struct ReleaseCommand {
#[clap(long, short = 'R')]
remote: Option<String>,
#[clap(long, short)]
repo: Option<String>,
#[clap(subcommand)]
command: ReleaseSubcommand,
}
#[derive(Subcommand, Clone, Debug)] #[derive(Subcommand, Clone, Debug)]
pub enum ReleaseCommand { pub enum ReleaseSubcommand {
Create { Create {
name: String, name: String,
#[clap(long, short = 'T')] #[clap(long, short = 'T')]
@ -103,10 +113,11 @@ pub enum AssetCommand {
impl ReleaseCommand { impl ReleaseCommand {
pub async fn run(self, keys: &KeyInfo, remote_name: Option<&str>) -> eyre::Result<()> { pub async fn run(self, keys: &KeyInfo, remote_name: Option<&str>) -> eyre::Result<()> {
let repo = RepoInfo::get_current(remote_name)?; let repo = RepoInfo::get_current(remote_name, self.repo.as_deref(), self.remote.as_deref())?;
let api = keys.get_api(&repo.host_url())?; let api = keys.get_api(&repo.host_url())?;
match self { let repo = repo.name().ok_or_eyre("couldn't get repo name, try specifying with --repo")?;
Self::Create { match self.command {
ReleaseSubcommand::Create {
name, name,
create_tag, create_tag,
tag, tag,
@ -121,7 +132,7 @@ impl ReleaseCommand {
) )
.await? .await?
} }
Self::Edit { ReleaseSubcommand::Edit {
name, name,
rename, rename,
tag, tag,
@ -129,14 +140,14 @@ impl ReleaseCommand {
draft, draft,
prerelease, prerelease,
} => edit_release(&repo, &api, name, rename, tag, body, draft, prerelease).await?, } => edit_release(&repo, &api, name, rename, tag, body, draft, prerelease).await?,
Self::Delete { name, by_tag } => delete_release(&repo, &api, name, by_tag).await?, ReleaseSubcommand::Delete { name, by_tag } => delete_release(&repo, &api, name, by_tag).await?,
Self::List { ReleaseSubcommand::List {
include_prerelease, include_prerelease,
include_draft, include_draft,
} => list_releases(&repo, &api, include_prerelease, include_draft).await?, } => list_releases(&repo, &api, include_prerelease, include_draft).await?,
Self::View { name, by_tag } => view_release(&repo, &api, name, by_tag).await?, ReleaseSubcommand::View { name, by_tag } => view_release(&repo, &api, name, by_tag).await?,
Self::Browse { name } => browse_release(&repo, &api, name).await?, ReleaseSubcommand::Browse { name } => browse_release(&repo, &api, name).await?,
Self::Asset(subcommand) => match subcommand { ReleaseSubcommand::Asset(subcommand) => match subcommand {
AssetCommand::Create { AssetCommand::Create {
release, release,
path, path,
@ -157,7 +168,7 @@ impl ReleaseCommand {
} }
async fn create_release( async fn create_release(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
name: String, name: String,
create_tag: Option<Option<String>>, create_tag: Option<Option<String>>,
@ -241,7 +252,7 @@ async fn create_release(
} }
async fn edit_release( async fn edit_release(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
name: String, name: String,
rename: Option<String>, rename: Option<String>,
@ -280,7 +291,7 @@ async fn edit_release(
} }
async fn list_releases( async fn list_releases(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
prerelease: bool, prerelease: bool,
draft: bool, draft: bool,
@ -321,7 +332,7 @@ async fn list_releases(
} }
async fn view_release( async fn view_release(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
name: String, name: String,
by_tag: bool, by_tag: bool,
@ -384,7 +395,7 @@ async fn view_release(
Ok(()) Ok(())
} }
async fn browse_release(repo: &RepoInfo, api: &Forgejo, name: Option<String>) -> eyre::Result<()> { async fn browse_release(repo: &RepoName, api: &Forgejo, name: Option<String>) -> eyre::Result<()> {
match name { match name {
Some(name) => { Some(name) => {
let release = find_release(repo, api, &name).await?; let release = find_release(repo, api, &name).await?;
@ -395,16 +406,20 @@ async fn browse_release(repo: &RepoInfo, api: &Forgejo, name: Option<String>) ->
open::that(html_url.as_str())?; open::that(html_url.as_str())?;
} }
None => { None => {
let mut url = repo.url().clone(); let repo_data = api.repo_get(repo.owner(), repo.name()).await?;
url.path_segments_mut().unwrap().push("releases"); let mut html_url = repo_data
open::that(url.as_str())?; .html_url
.clone()
.ok_or_else(|| eyre::eyre!("repository does not have html_url"))?;
html_url.path_segments_mut().unwrap().push("releases");
open::that(html_url.as_str())?;
} }
} }
Ok(()) Ok(())
} }
async fn create_asset( async fn create_asset(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
release: String, release: String,
file: std::path::PathBuf, file: std::path::PathBuf,
@ -441,7 +456,7 @@ async fn create_asset(
} }
async fn delete_asset( async fn delete_asset(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
release: String, release: String,
asset: String, asset: String,
@ -467,7 +482,7 @@ async fn delete_asset(
} }
async fn download_asset( async fn download_asset(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
release: String, release: String,
asset: String, asset: String,
@ -526,7 +541,7 @@ async fn download_asset(
} }
async fn find_release( async fn find_release(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
name: &str, name: &str,
) -> eyre::Result<forgejo_api::structs::Release> { ) -> eyre::Result<forgejo_api::structs::Release> {
@ -548,7 +563,7 @@ async fn find_release(
} }
async fn delete_release( async fn delete_release(
repo: &RepoInfo, repo: &RepoName,
api: &Forgejo, api: &Forgejo,
name: String, name: String,
by_tag: bool, by_tag: bool,

View file

@ -1,32 +1,228 @@
use clap::Subcommand; use clap::Subcommand;
use eyre::eyre; use eyre::{eyre, OptionExt};
use forgejo_api::structs::CreateRepoOption; use forgejo_api::structs::CreateRepoOption;
use url::Url; use url::Url;
pub struct RepoInfo { pub struct RepoInfo {
owner: String,
name: String,
url: Url, url: Url,
name: Option<RepoName>,
} }
impl RepoInfo { impl RepoInfo {
pub fn get_current(remote: Option<&str>) -> eyre::Result<Self> { pub fn get_current(host: Option<&str>, repo: Option<&str>, remote: Option<&str>) -> eyre::Result<Self> {
let repo = git2::Repository::open(".")?; // l = domain/owner/name
let url = get_remote(&repo, remote)?; // s = owner/name
// x = is present
// i = found locally by git
//
// | repo | host | remote | ans-host | ans-repo |
// |------|------|--------|----------|----------|
// | l | x | x | repo | repo |
// | l | x | i | repo | repo |
// | l | x | | repo | repo |
// | l | | x | repo | repo |
// | l | | i | repo | repo |
// | l | | | repo | repo |
// | s | x | x | host | repo |
// | s | x | i | host | repo |
// | s | x | | host | repo |
// | s | | x | remote | repo |
// | s | | i | remote | repo |
// | s | | | err | repo |
// | | x | x | remote | remote |
// | | x | i | host | ?remote |
// | | x | | host | none |
// | | | x | remote | remote |
// | | | i | remote | remote |
// | | | | err | remote |
//
// | repo | host | remote | ans-host | ans-repo |
// |------|------|--------|----------|----------|
// | l | x | x | repo | repo |
// | l | x | | repo | repo |
// | l | | x | repo | repo |
// | l | | | repo | repo |
// | s | x | x | host | repo |
// | s | x | | host | repo |
// | s | | x | remote | repo |
// | s | | | err | repo |
// | | x | x | remote | remote |
// | | x | | remote | remote |
// | | | x | remote | remote |
// | | | | err | remote |
let mut path = url.path_segments().ok_or_else(|| eyre!("bad path"))?; // let repo_name;
let owner = path //
.next() // let repo_url;
.ok_or_else(|| eyre!("path does not have owner name"))? // let remote;
.to_string(); // let host;
let name = path //
.next() // let url = if repo_url { repo_url }
.ok_or_else(|| eyre!("path does not have repo name"))?; // else if repo_name { host.or(remote) }
let name = name.strip_suffix(".git").unwrap_or(name).to_string(); // else { remote.or_host() }
//
// let name = repo_name.or(remote)
let repo_info = RepoInfo { owner, name, url }; let mut repo_url: Option<Url> = None;
Ok(repo_info) let mut repo_name: Option<RepoName> = None;
if let Some(repo) = repo {
let (head, name) = repo.rsplit_once("/").ok_or_eyre("repo name must contain owner and name")?;
let name = name.strip_suffix(".git").unwrap_or(name);
match head.rsplit_once("/") {
Some((url, owner)) => {
if let Ok(url) = Url::parse(url) {
repo_url = Some(url);
} else if let Ok(url) = Url::parse(&format!("https://{url}/")) {
repo_url = Some(url);
}
repo_name = Some(RepoName {
owner: owner.to_owned(),
name: name.to_owned(),
});
},
None => {
repo_name = Some(RepoName {
owner: head.to_owned(),
name: name.to_owned(),
});
},
}
}
let repo_url = repo_url;
let repo_name = repo_name;
let host_url = host.and_then(|host| Url::parse(host).ok().or_else(|| Url::parse(&format!("https://{host}/")).ok()));
let (remote_url, remote_repo_name) = {
let mut out = (None, None);
if let Ok(local_repo) = git2::Repository::open(".") {
let tmp;
let mut name = remote;
// if the user didn't specify a remote, try guessing other ways
let mut tmp2;
if name.is_none() {
let all_remotes = local_repo.remotes()?;
// if there's only one remote, use that
if all_remotes.len() == 1 {
if let Some(remote_name) = all_remotes.get(0) {
tmp2 = Some(remote_name.to_owned());
name = tmp2.as_deref();
}
// if there's a remote whose host url matches the one
// specified with `--host`, use that
//
// This is different than using `--host` itself, since this
// will include the repo name, which `--host` can't do.
} else if let Some(host_url) = &host_url {
for remote_name in all_remotes.iter() {
let Some(remote_name) = remote_name else { continue };
let remote = local_repo.find_remote(remote_name)?;
if let Some(url) = remote.url() {
let (url, _) = url_strip_repo_name(Url::parse(url)?)?;
if url.host_str() == host_url.host_str() && url.path() == host_url.path() {
tmp2 = Some(remote_name.to_owned());
name = tmp2.as_deref();
}
}
}
}
}
// if there isn't an obvious answer, guess from the current
// branch's tracking remote
if name.is_none() {
let head = local_repo.head()?;
let branch_name = head.name().ok_or_else(|| eyre!("branch name not UTF-8"))?;
tmp = local_repo.branch_upstream_remote(branch_name).ok();
name = tmp.as_ref().map(|remote| {
remote
.as_str()
.ok_or_else(|| eyre!("remote name not UTF-8"))
}).transpose()?;
}
if let Some(name) = name {
if let Ok(remote) = local_repo.find_remote(name) {
let url_s = std::str::from_utf8(remote.url_bytes())?;
let url = Url::parse(url_s)?;
let (url, name) = url_strip_repo_name(url)?;
out = (Some(url), Some(name))
}
}
} else {
eyre::ensure!(remote.is_none(), "remote specified but no git repo found");
}
out
};
let (url, name) = if repo_url.is_some() {
(repo_url, repo_name)
} else if repo_name.is_some() {
(host_url.or(remote_url), repo_name)
} else {
if remote.is_some() {
(remote_url, remote_repo_name)
} else if host_url.is_none() || remote_url == host_url {
(remote_url, remote_repo_name)
} else {
(host_url, None)
}
};
let info = match (url, name) {
(Some(url), name) => RepoInfo {
url,
name,
},
(None, Some(_)) => eyre::bail!("cannot find repo, no host specified"),
(None, None) => eyre::bail!("no repo info specified"),
};
Ok(info)
} }
pub fn name(&self) -> Option<&RepoName> {
self.name.as_ref()
}
pub fn host_url(&self) -> &Url {
&self.url
}
}
fn url_strip_repo_name(mut url: Url) -> eyre::Result<(Url, RepoName)> {
let mut iter = url
.path_segments()
.ok_or_eyre("repo url cannot be a base")?
.rev();
let name = iter.next().ok_or_eyre("repo url too short")?;
let name = name.strip_suffix(".git").unwrap_or(name).to_owned();
let owner = iter.next().ok_or_eyre("repo url too short")?.to_owned();
// Remove the username and repo name from the url
url.path_segments_mut()
.map_err(|_| eyre!("repo url cannot be a base"))?
.pop()
.pop();
Ok((url, RepoName { owner, name }))
}
#[derive(Debug)]
pub struct RepoName {
owner: String,
name: String,
}
impl RepoName {
pub fn owner(&self) -> &str { pub fn owner(&self) -> &str {
&self.owner &self.owner
} }
@ -34,48 +230,11 @@ impl RepoInfo {
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
&self.name &self.name
} }
pub fn url(&self) -> &Url {
&self.url
}
pub fn host_url(&self) -> Url {
let mut url = self.url.clone();
url.path_segments_mut()
.expect("invalid url: cannot be a base")
.pop()
.pop();
url
}
}
fn get_remote(repo: &git2::Repository, name: Option<&str>) -> eyre::Result<Url> {
if let Some(name) = name {
if let Ok(url) = Url::parse(name) {
return Ok(url);
}
}
let remote_name;
let remote_name = match name {
Some(name) => name,
None => {
let head = repo.head()?;
let branch_name = head.name().ok_or_else(|| eyre!("branch name not UTF-8"))?;
remote_name = repo.branch_upstream_remote(branch_name)?;
remote_name
.as_str()
.ok_or_else(|| eyre!("remote name not UTF-8"))?
}
};
let remote = repo.find_remote(remote_name)?;
let url = Url::parse(std::str::from_utf8(remote.url_bytes())?)?;
Ok(url)
} }
#[derive(Subcommand, Clone, Debug)] #[derive(Subcommand, Clone, Debug)]
pub enum RepoCommand { pub enum RepoCommand {
Create { Create {
host: String,
repo: String, repo: String,
// flags // flags
@ -91,15 +250,22 @@ pub enum RepoCommand {
#[clap(long, short)] #[clap(long, short)]
push: bool, push: bool,
}, },
Info, Info {
Browse, name: Option<String>,
#[clap(long, short = 'R')]
remote: Option<String>,
},
Browse {
name: Option<String>,
#[clap(long, short = 'R')]
remote: Option<String>,
},
} }
impl RepoCommand { impl RepoCommand {
pub async fn run(self, keys: &crate::KeyInfo, remote_name: Option<&str>) -> eyre::Result<()> { pub async fn run(self, keys: &crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
match self { match self {
RepoCommand::Create { RepoCommand::Create {
host,
repo, repo,
description, description,
@ -107,8 +273,8 @@ impl RepoCommand {
set_upstream, set_upstream,
push, push,
} => { } => {
let host = Url::parse(&host)?; let host = RepoInfo::get_current(host_name, None, None)?;
let api = keys.get_api(&host)?; let api = keys.get_api(host.host_url())?;
let repo_spec = CreateRepoOption { let repo_spec = CreateRepoOption {
auto_init: Some(false), auto_init: Some(false),
default_branch: Some("main".into()), default_branch: Some("main".into()),
@ -127,7 +293,7 @@ impl RepoCommand {
.full_name .full_name
.as_ref() .as_ref()
.ok_or_else(|| eyre::eyre!("new_repo does not have full_name"))?; .ok_or_else(|| eyre::eyre!("new_repo does not have full_name"))?;
eprintln!("created new repo at {}", host.join(&full_name)?); eprintln!("created new repo at {}", host.host_url().join(&full_name)?);
if set_upstream.is_some() || push { if set_upstream.is_some() || push {
let repo = git2::Repository::open(".")?; let repo = git2::Repository::open(".")?;
@ -158,22 +324,20 @@ impl RepoCommand {
} }
} }
} }
RepoCommand::Info => { RepoCommand::Info { name, remote } => {
let repo = RepoInfo::get_current(remote_name)?; let repo = RepoInfo::get_current(host_name, name.as_deref(), remote.as_deref())?;
let api = keys.get_api(&repo.host_url())?; let api = keys.get_api(&repo.host_url())?;
let repo = repo.name().ok_or_eyre("couldn't get repo name, please specify")?;
let repo = api.repo_get(repo.owner(), repo.name()).await?; let repo = api.repo_get(repo.owner(), repo.name()).await?;
dbg!(repo); dbg!(repo);
} }
RepoCommand::Browse => { RepoCommand::Browse { name, remote } => {
let repo = RepoInfo::get_current(remote_name)?; let repo = RepoInfo::get_current(host_name, name.as_deref(), remote.as_deref())?;
let mut url = repo.host_url().clone(); let mut url = repo.host_url().clone();
let new_path = format!( let repo = repo.name().ok_or_eyre("couldn't get repo name, please specify")?;
"{}/{}/{}", url.path_segments_mut().map_err(|_| eyre!("url invalid"))?.extend([repo.owner(), repo.name()]);
url.path().strip_suffix("/").unwrap_or(url.path()),
repo.owner(),
repo.name(),
);
url.set_path(&new_path);
open::that(url.as_str())?; open::that(url.as_str())?;
} }
}; };