mirror of
https://codeberg.org/Cyborus/forgejo-cli.git
synced 2024-11-10 03:59:31 +01:00
improve host url and repo name detection
This commit is contained in:
parent
a014a4e87a
commit
46cd32ebd8
4 changed files with 319 additions and 135 deletions
|
@ -1,14 +1,24 @@
|
|||
use clap::Subcommand;
|
||||
use eyre::eyre;
|
||||
use clap::{Args, Subcommand};
|
||||
use eyre::{eyre, OptionExt};
|
||||
use forgejo_api::structs::{
|
||||
Comment, CreateIssueCommentOption, CreateIssueOption, EditIssueOption, IssueGetCommentsQuery,
|
||||
};
|
||||
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)]
|
||||
pub enum IssueCommand {
|
||||
pub enum IssueSubcommand {
|
||||
Create {
|
||||
title: String,
|
||||
#[clap(long)]
|
||||
|
@ -86,11 +96,12 @@ pub enum ViewCommand {
|
|||
}
|
||||
|
||||
impl IssueCommand {
|
||||
pub async fn run(self, keys: &crate::KeyInfo, remote_name: Option<&str>) -> eyre::Result<()> {
|
||||
use IssueCommand::*;
|
||||
let repo = RepoInfo::get_current(remote_name)?;
|
||||
let api = keys.get_api(&repo.host_url())?;
|
||||
match self {
|
||||
pub async fn run(self, keys: &crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
|
||||
use IssueSubcommand::*;
|
||||
let repo = RepoInfo::get_current(host_name, self.repo.as_deref(), self.remote.as_deref())?;
|
||||
let api = keys.get_api(repo.host_url())?;
|
||||
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?,
|
||||
View { id, command } => match command.unwrap_or(ViewCommand::Body) {
|
||||
ViewCommand::Body => view_issue(&repo, &api, id).await?,
|
||||
|
@ -122,7 +133,7 @@ impl IssueCommand {
|
|||
}
|
||||
|
||||
async fn create_issue(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
title: String,
|
||||
body: Option<String>,
|
||||
|
@ -163,7 +174,7 @@ async fn create_issue(
|
|||
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 title = issue
|
||||
.title
|
||||
|
@ -186,7 +197,7 @@ async fn view_issue(repo: &RepoInfo, api: &Forgejo, id: u64) -> eyre::Result<()>
|
|||
Ok(())
|
||||
}
|
||||
async fn view_issues(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
query_str: Option<String>,
|
||||
labels: Option<String>,
|
||||
|
@ -240,7 +251,7 @@ async fn view_issues(
|
|||
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 {
|
||||
since: None,
|
||||
before: None,
|
||||
|
@ -255,7 +266,7 @@ async fn view_comment(repo: &RepoInfo, api: &Forgejo, id: u64, idx: usize) -> ey
|
|||
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 {
|
||||
since: None,
|
||||
before: None,
|
||||
|
@ -294,7 +305,7 @@ fn print_comment(comment: &Comment) -> eyre::Result<()> {
|
|||
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 {
|
||||
Some(id) => {
|
||||
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(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
issue: u64,
|
||||
body: Option<String>,
|
||||
|
@ -344,7 +355,7 @@ async fn add_comment(
|
|||
}
|
||||
|
||||
async fn edit_title(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
issue: u64,
|
||||
new_title: Option<String>,
|
||||
|
@ -390,7 +401,7 @@ async fn edit_title(
|
|||
}
|
||||
|
||||
async fn edit_body(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
issue: u64,
|
||||
new_body: Option<String>,
|
||||
|
@ -430,7 +441,7 @@ async fn edit_body(
|
|||
}
|
||||
|
||||
async fn edit_comment(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
issue: u64,
|
||||
idx: usize,
|
||||
|
@ -478,7 +489,7 @@ async fn edit_comment(
|
|||
}
|
||||
|
||||
async fn close_issue(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
issue: u64,
|
||||
message: Option<Option<String>>,
|
||||
|
|
28
src/main.rs
28
src/main.rs
|
@ -1,7 +1,6 @@
|
|||
use clap::{Parser, Subcommand};
|
||||
use eyre::eyre;
|
||||
use eyre::{eyre, Context};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use url::Url;
|
||||
|
||||
mod keys;
|
||||
use keys::*;
|
||||
|
@ -13,8 +12,8 @@ mod repo;
|
|||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct App {
|
||||
#[clap(long, short = 'R')]
|
||||
remote: Option<String>,
|
||||
#[clap(long, short = 'H')]
|
||||
host: Option<String>,
|
||||
#[clap(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
@ -23,15 +22,13 @@ pub struct App {
|
|||
pub enum Command {
|
||||
#[clap(subcommand)]
|
||||
Repo(repo::RepoCommand),
|
||||
#[clap(subcommand)]
|
||||
Issue(issues::IssueCommand),
|
||||
User {
|
||||
#[clap(long, short)]
|
||||
host: Option<String>,
|
||||
remote: Option<String>,
|
||||
},
|
||||
#[clap(subcommand)]
|
||||
Auth(auth::AuthCommand),
|
||||
#[clap(subcommand)]
|
||||
Release(release::ReleaseCommand),
|
||||
}
|
||||
|
||||
|
@ -40,21 +37,18 @@ async fn main() -> eyre::Result<()> {
|
|||
let args = App::parse();
|
||||
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 {
|
||||
Command::Repo(subcommand) => subcommand.run(&keys, remote_name).await?,
|
||||
Command::Issue(subcommand) => subcommand.run(&keys, remote_name).await?,
|
||||
Command::User { host } => {
|
||||
let host = host.map(|host| Url::parse(&host)).transpose()?;
|
||||
let url = match host {
|
||||
Some(url) => url,
|
||||
None => repo::RepoInfo::get_current(remote_name)?.url().clone(),
|
||||
};
|
||||
Command::Repo(subcommand) => subcommand.run(&keys, host_name).await?,
|
||||
Command::Issue(subcommand) => subcommand.run(&keys, host_name).await?,
|
||||
Command::User { remote } => {
|
||||
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 name = keys.get_login(&url)?.username();
|
||||
eprintln!("currently signed in to {name}@{url}");
|
||||
}
|
||||
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?;
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
use clap::Subcommand;
|
||||
use eyre::{bail, eyre};
|
||||
use clap::{Subcommand, Args};
|
||||
use eyre::{bail, eyre, OptionExt};
|
||||
use forgejo_api::{
|
||||
structs::{RepoCreateReleaseAttachmentQuery, RepoListReleasesQuery},
|
||||
Forgejo,
|
||||
};
|
||||
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)]
|
||||
pub enum ReleaseCommand {
|
||||
pub enum ReleaseSubcommand {
|
||||
Create {
|
||||
name: String,
|
||||
#[clap(long, short = 'T')]
|
||||
|
@ -103,10 +113,11 @@ pub enum AssetCommand {
|
|||
|
||||
impl ReleaseCommand {
|
||||
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())?;
|
||||
match self {
|
||||
Self::Create {
|
||||
let repo = repo.name().ok_or_eyre("couldn't get repo name, try specifying with --repo")?;
|
||||
match self.command {
|
||||
ReleaseSubcommand::Create {
|
||||
name,
|
||||
create_tag,
|
||||
tag,
|
||||
|
@ -121,7 +132,7 @@ impl ReleaseCommand {
|
|||
)
|
||||
.await?
|
||||
}
|
||||
Self::Edit {
|
||||
ReleaseSubcommand::Edit {
|
||||
name,
|
||||
rename,
|
||||
tag,
|
||||
|
@ -129,14 +140,14 @@ impl ReleaseCommand {
|
|||
draft,
|
||||
prerelease,
|
||||
} => edit_release(&repo, &api, name, rename, tag, body, draft, prerelease).await?,
|
||||
Self::Delete { name, by_tag } => delete_release(&repo, &api, name, by_tag).await?,
|
||||
Self::List {
|
||||
ReleaseSubcommand::Delete { name, by_tag } => delete_release(&repo, &api, name, by_tag).await?,
|
||||
ReleaseSubcommand::List {
|
||||
include_prerelease,
|
||||
include_draft,
|
||||
} => list_releases(&repo, &api, include_prerelease, include_draft).await?,
|
||||
Self::View { name, by_tag } => view_release(&repo, &api, name, by_tag).await?,
|
||||
Self::Browse { name } => browse_release(&repo, &api, name).await?,
|
||||
Self::Asset(subcommand) => match subcommand {
|
||||
ReleaseSubcommand::View { name, by_tag } => view_release(&repo, &api, name, by_tag).await?,
|
||||
ReleaseSubcommand::Browse { name } => browse_release(&repo, &api, name).await?,
|
||||
ReleaseSubcommand::Asset(subcommand) => match subcommand {
|
||||
AssetCommand::Create {
|
||||
release,
|
||||
path,
|
||||
|
@ -157,7 +168,7 @@ impl ReleaseCommand {
|
|||
}
|
||||
|
||||
async fn create_release(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
name: String,
|
||||
create_tag: Option<Option<String>>,
|
||||
|
@ -241,7 +252,7 @@ async fn create_release(
|
|||
}
|
||||
|
||||
async fn edit_release(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
name: String,
|
||||
rename: Option<String>,
|
||||
|
@ -280,7 +291,7 @@ async fn edit_release(
|
|||
}
|
||||
|
||||
async fn list_releases(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
prerelease: bool,
|
||||
draft: bool,
|
||||
|
@ -321,7 +332,7 @@ async fn list_releases(
|
|||
}
|
||||
|
||||
async fn view_release(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
name: String,
|
||||
by_tag: bool,
|
||||
|
@ -384,7 +395,7 @@ async fn view_release(
|
|||
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 {
|
||||
Some(name) => {
|
||||
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())?;
|
||||
}
|
||||
None => {
|
||||
let mut url = repo.url().clone();
|
||||
url.path_segments_mut().unwrap().push("releases");
|
||||
open::that(url.as_str())?;
|
||||
let repo_data = api.repo_get(repo.owner(), repo.name()).await?;
|
||||
let mut html_url = repo_data
|
||||
.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(())
|
||||
}
|
||||
|
||||
async fn create_asset(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
release: String,
|
||||
file: std::path::PathBuf,
|
||||
|
@ -441,7 +456,7 @@ async fn create_asset(
|
|||
}
|
||||
|
||||
async fn delete_asset(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
release: String,
|
||||
asset: String,
|
||||
|
@ -467,7 +482,7 @@ async fn delete_asset(
|
|||
}
|
||||
|
||||
async fn download_asset(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
release: String,
|
||||
asset: String,
|
||||
|
@ -526,7 +541,7 @@ async fn download_asset(
|
|||
}
|
||||
|
||||
async fn find_release(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
name: &str,
|
||||
) -> eyre::Result<forgejo_api::structs::Release> {
|
||||
|
@ -548,7 +563,7 @@ async fn find_release(
|
|||
}
|
||||
|
||||
async fn delete_release(
|
||||
repo: &RepoInfo,
|
||||
repo: &RepoName,
|
||||
api: &Forgejo,
|
||||
name: String,
|
||||
by_tag: bool,
|
||||
|
|
308
src/repo.rs
308
src/repo.rs
|
@ -1,32 +1,228 @@
|
|||
use clap::Subcommand;
|
||||
use eyre::eyre;
|
||||
use eyre::{eyre, OptionExt};
|
||||
use forgejo_api::structs::CreateRepoOption;
|
||||
use url::Url;
|
||||
|
||||
pub struct RepoInfo {
|
||||
owner: String,
|
||||
name: String,
|
||||
url: Url,
|
||||
name: Option<RepoName>,
|
||||
}
|
||||
|
||||
impl RepoInfo {
|
||||
pub fn get_current(remote: Option<&str>) -> eyre::Result<Self> {
|
||||
let repo = git2::Repository::open(".")?;
|
||||
let url = get_remote(&repo, remote)?;
|
||||
pub fn get_current(host: Option<&str>, repo: Option<&str>, remote: Option<&str>) -> eyre::Result<Self> {
|
||||
// l = domain/owner/name
|
||||
// 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 owner = path
|
||||
.next()
|
||||
.ok_or_else(|| eyre!("path does not have owner name"))?
|
||||
.to_string();
|
||||
let name = path
|
||||
.next()
|
||||
.ok_or_else(|| eyre!("path does not have repo name"))?;
|
||||
let name = name.strip_suffix(".git").unwrap_or(name).to_string();
|
||||
// let repo_name;
|
||||
//
|
||||
// let repo_url;
|
||||
// let remote;
|
||||
// let host;
|
||||
//
|
||||
// let url = if repo_url { repo_url }
|
||||
// else if repo_name { host.or(remote) }
|
||||
// else { remote.or_host() }
|
||||
//
|
||||
// let name = repo_name.or(remote)
|
||||
|
||||
let repo_info = RepoInfo { owner, name, url };
|
||||
Ok(repo_info)
|
||||
let mut repo_url: Option<Url> = None;
|
||||
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 {
|
||||
&self.owner
|
||||
}
|
||||
|
@ -34,48 +230,11 @@ impl RepoInfo {
|
|||
pub fn name(&self) -> &str {
|
||||
&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)]
|
||||
pub enum RepoCommand {
|
||||
Create {
|
||||
host: String,
|
||||
repo: String,
|
||||
|
||||
// flags
|
||||
|
@ -91,15 +250,22 @@ pub enum RepoCommand {
|
|||
#[clap(long, short)]
|
||||
push: bool,
|
||||
},
|
||||
Info,
|
||||
Browse,
|
||||
Info {
|
||||
name: Option<String>,
|
||||
#[clap(long, short = 'R')]
|
||||
remote: Option<String>,
|
||||
},
|
||||
Browse {
|
||||
name: Option<String>,
|
||||
#[clap(long, short = 'R')]
|
||||
remote: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
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 {
|
||||
RepoCommand::Create {
|
||||
host,
|
||||
repo,
|
||||
|
||||
description,
|
||||
|
@ -107,8 +273,8 @@ impl RepoCommand {
|
|||
set_upstream,
|
||||
push,
|
||||
} => {
|
||||
let host = Url::parse(&host)?;
|
||||
let api = keys.get_api(&host)?;
|
||||
let host = RepoInfo::get_current(host_name, None, None)?;
|
||||
let api = keys.get_api(host.host_url())?;
|
||||
let repo_spec = CreateRepoOption {
|
||||
auto_init: Some(false),
|
||||
default_branch: Some("main".into()),
|
||||
|
@ -127,7 +293,7 @@ impl RepoCommand {
|
|||
.full_name
|
||||
.as_ref()
|
||||
.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 {
|
||||
let repo = git2::Repository::open(".")?;
|
||||
|
@ -158,22 +324,20 @@ impl RepoCommand {
|
|||
}
|
||||
}
|
||||
}
|
||||
RepoCommand::Info => {
|
||||
let repo = RepoInfo::get_current(remote_name)?;
|
||||
RepoCommand::Info { name, remote } => {
|
||||
let repo = RepoInfo::get_current(host_name, name.as_deref(), remote.as_deref())?;
|
||||
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?;
|
||||
|
||||
dbg!(repo);
|
||||
}
|
||||
RepoCommand::Browse => {
|
||||
let repo = RepoInfo::get_current(remote_name)?;
|
||||
RepoCommand::Browse { name, remote } => {
|
||||
let repo = RepoInfo::get_current(host_name, name.as_deref(), remote.as_deref())?;
|
||||
let mut url = repo.host_url().clone();
|
||||
let new_path = format!(
|
||||
"{}/{}/{}",
|
||||
url.path().strip_suffix("/").unwrap_or(url.path()),
|
||||
repo.owner(),
|
||||
repo.name(),
|
||||
);
|
||||
url.set_path(&new_path);
|
||||
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()]);
|
||||
|
||||
open::that(url.as_str())?;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue